diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c5f4599 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +target +.sonar +TESTS-TestSuites.xml diff --git a/README.md b/README.md index 94cec95..e8b67dd 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,161 @@ -# tdrules-st-tdg -Test Data Generation for APIs (TDG) - System and Integration Tests +# Test Data Generation for APIs - System Tests + +This project contains the system and integration tests of *Test Data Generation for APIs* (TDG). +Includes the required test classes, result files and the SUT projects, +along with an additional project (to convert into module) to evaluate the mutation +score of the tests for one of the SUTs. + +## Systems Under Test (SUTs) + +- Swagger Petstore (`swagger-petstore-main-fork`). + This is a detached fork of https://github.com/swagger-api/swagger-petstore at v1.0.17 (2022-09-30) with some additions +- Market (`swagger-market-fork`). + This is a detached fork of https://github.com/aleksey-lukyanets/market (2022-02-07) with some additions +- Gestao Hospital (`swagger-gestaoHospital-fork`). + This is a detached fork of https://github.com/ValchanOficial/GestaoHospital (2023-07-05) with some additions + +## Structure of this project + +- Module `st-tdg-test`: + - Java tests (`src/test/java`) for each SUT. + - Files for test result comparison (`src/test/resources`) +- A folder for each of the SUTs that are evaluated in this replication package. +- A folder `setup` with scripts to execute each SUT (backend and databases). +- Module `st-tdg-eval`: To evaluate the effectiveness of the test data my measuring + the mutation score of the tests using PIT (pitest.org). + +## How to execute the System Tests + +All experiments are enclosed in the Java tests. + +To run the tests of a SUT in your local development environment: + - Ensure that the port that uses the SUT is not used by other SUT + - Run the server from the `setup` folder and wait until it is up + - Execute the tests in the namespace that corresponds with the project + - To execute from maven (e.g. for the petstore): `mvn -pl st-tdg-test` + +Port reservation and endpoints: To avoid conflicts when running in local, +different host port are reserved for each SUT. +Below are the ports and the main endpoint urls to check that SUTs are working: + - Petstore: 8081:8080 (api) | + [api example](http://localhost:8081/api/v3/pet/findByStatus?status=available) | + [swagger-ui](http://localhost:8081/) | + [api-docs](http://localhost:8081/api/v3/openapi.json) + - Market: 8082 (web), 8083 (api), 8084 (db) + [api example](http://localhost:8083/products) | + [swagger-ui](http://localhost:8083/swagger-ui/index.html) | + [api-docs](http://localhost:8083/v2/api-docs) + - Gestao Hospital: 8085:8080 (api), 8086:27017 (default mongo) (db) | + [api example](http://localhost:8085/v1/hospitais/) | + [api-docs](http://localhost:8085/v2/api-docs) + +The whole sequence of tests for all SUTs is run when executed in CI + +## How to evaluate the mutation score + +At `st-tdg-eval` run this maven command: +``` +mvn test-compile org.pitest:pitest-maven:mutationCoverage +``` + +Test results are in the `target` folder, and mutation report in `target/pit-reports` + +## Graphical TDM models + +- Swagger Petstore (src/test/resources/petstore/schema-petstore.md) + +```mermaid +classDiagram + Pet <--"*" Order + Customer <--"*" Order + Customer *--"*" Customer_address_xa + Category <--"*" Pet_category_xt + Pet *--"*" Pet_photoUrls_xa + Pet *--"*" Pet_tags_xa + Pet *--"1" Pet_category_xt + Pet0 <--"*" Order0 + Customer0 <--"*" Order0 + Pet0 *--"1" Pet0_category_xt + Category <--"*" Pet1_category_xt + Pet1 *--"1" Pet1_category_xt + Customer_address_xa ..|> Address + Pet_category_xt ..|> Category + Pet_tags_xa ..|> Tag + Pet1_category_xt ..|> Category + class User + class ApiResponse + Order: +post(/store/order) + Customer: +post(/store/customer) + Category: +post(/category) + Category: +post(/backid/category) + User: +post(/user) + User: +put(/user/{username}) + Pet: +post(/pet) + Pet: +put(/pet) + Pet: +post(/backid/pet) + Customer0: +post(/store/customer0) + Order0: +post(/store/order0) + Pet0: +post(/pet0) + Pet1: +post(/pet1) +``` + +- Market (/src/test/resources/market/schema-marketWithoutArrays.md) + +```mermaid +classDiagram + UserDTOReq <--"*" CartDTO + CartDTO <--"*" CartItemDTOReq + ProductDTOReq <--"*" CartItemDTOReq + ProductDTORes <--"*" CartItemDTORes + CartDTO <--"*" CartItemDTORes + UserDTOReq <--"*" ContactsDTOReq + UserDTORes <--"*" ContactsDTORes + RegionDTOReq <--"*" DistilleryDTOReq + RegionDTORes <--"*" DistilleryDTORes + CreditCardDTO <--"*" OrderDTO + UserDTOReq <--"*" OrderDTO + DistilleryDTOReq <--"*" ProductDTOReq + DistilleryDTORes <--"*" ProductDTORes + CartItemDTOReq: +put(/customer/cart) + ContactsDTOReq: +put(/customer/contacts) + CreditCardDTO: +post(/customer/cart/pay) + DistilleryDTOReq: +post(/distillerydto) + ProductDTOReq: +post(/products/productdto) + RegionDTOReq: +post(/regiondto) + UserDTOReq: +post(/register) +``` + +- Gestao Hospital (src/test/resources/gestaoHospital/schema-hospital.md) + +```mermaid +classDiagram + GeoJsonPoint *--"*" GeoJsonPoint_coordinates_xa + Location_location_xt *--"*" Location_location_xt_coordinates_xa + GeoJsonPoint <--"*" Location_location_xt + Location_position_xt *--"*" Location_position_xt_coordinates_xa + GeoJsonPoint <--"*" Location_position_xt + Patient_location_xt_location_xt *--"*" Patient_location_xt_location_xt_coordinates_xa + GeoJsonPoint <--"*" Patient_location_xt_location_xt + Patient_location_xt_position_xt *--"*" Patient_location_xt_position_xt_coordinates_xa + GeoJsonPoint <--"*" Patient_location_xt_position_xt + Location <--"*" Patient_location_xt + Patient_location_xt *--"1" Patient_location_xt_location_xt + Patient_location_xt *--"1" Patient_location_xt_position_xt + HospitalDTO <--"*" Patient + Patient *--"1" Patient_location_xt + HospitalDTO <--"*" ProductDTO + Location *--"1" Location_location_xt + Location *--"1" Location_position_xt + Location_location_xt ..|> GeoJsonPoint + Location_position_xt ..|> GeoJsonPoint + Patient_location_xt_location_xt ..|> GeoJsonPoint + Patient_location_xt_position_xt ..|> GeoJsonPoint + Patient_location_xt ..|> Location + class LocationDTO + HospitalDTO: +post(/v1/hospitais/) + HospitalDTO: +put(/v1/hospitais/{hospital_id}) + Patient: +post(/v1/hospitais/{hospital_id}/pacientes/checkin) + Patient: +put(/v1/hospitais/{hospital_id}/pacientes/{patientId}) + ProductDTO: +post(/v1/hospitais/{hospital_id}/estoque) + ProductDTO: +put(/v1/hospitais/{hospital_id}/estoque/{produto_id}) +``` diff --git a/setup/run-gestaoHospital.bat b/setup/run-gestaoHospital.bat new file mode 100644 index 0000000..1702c70 --- /dev/null +++ b/setup/run-gestaoHospital.bat @@ -0,0 +1,7 @@ +@echo on +SET GESTAOHOSPITAL_DIR=%~dp0%\..\swagger-gestaoHospital-fork +cd %GESTAOHOSPITAL_DIR% +cmd /c mvn package -DskipTests=true +docker-compose down +docker-compose up -d --force-recreate --build +pause \ No newline at end of file diff --git a/setup/run-market.bat b/setup/run-market.bat new file mode 100644 index 0000000..968f44c --- /dev/null +++ b/setup/run-market.bat @@ -0,0 +1,10 @@ +@echo on +SET MARKET_DIR=%~dp0%\..\swagger-market-fork +cd %MARKET_DIR% +cmd /c mvn package -DskipTests=true +docker build -t market-web --build-arg module=market-web . +docker build -t market-rest --build-arg module=market-rest . +docker-compose -f docker-compose.yaml down +docker volume rm swagger-market-fork_postgres-data +docker-compose -f docker-compose.yaml up -d +pause \ No newline at end of file diff --git a/setup/run-swagger-petstore.bat b/setup/run-swagger-petstore.bat new file mode 100644 index 0000000..f9f6908 --- /dev/null +++ b/setup/run-swagger-petstore.bat @@ -0,0 +1,9 @@ +@echo on +SET PETSTORE_DIR=%~dp0%\..\swagger-petstore-main-fork +cd %PETSTORE_DIR% +cmd /c mvn package -DskipTests=true +docker build -t swagger-petstore . +docker stop swagger-petstore +docker rm swagger-petstore +docker run -d -p 8081:8080 --name swagger-petstore swagger-petstore +pause \ No newline at end of file diff --git a/st-tdg-eval/pom.xml b/st-tdg-eval/pom.xml new file mode 100644 index 0000000..bbeceb8 --- /dev/null +++ b/st-tdg-eval/pom.xml @@ -0,0 +1,201 @@ + + + + 4.0.0 + + giis + swagger-petstore-eval + 1.0.0-SNAPSHOT + + + 1.8 + 1.8 + UTF-8 + + 4.3.0 + 1.4.266 + + + + + + + io.github.giis-uniovi + tdrules-client + ${tdrules.version} + test + + + io.github.giis-uniovi + tdrules-client-oa + ${tdrules.version} + test + + + io.github.giis-uniovi + tdrules-store-loader + ${tdrules.version} + test + + + giis + qagrow + ${qagrow.version} + test + + + + org.slf4j + slf4j-api + 1.7.36 + + + ch.qos.logback + logback-classic + 1.2.13 + test + + + junit + junit + 4.13.2 + test + + + io.github.javiertuya + visual-assert + 2.4.1 + test + + + org.projectlombok + lombok + 1.18.34 + provided + + + + + + + + + + org.codehaus.mojo + build-helper-maven-plugin + 3.6.0 + + + add-source + generate-sources + + add-source + + + + + ../swagger-petstore-main-fork/src/main/java + + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.13.0 + + + **/io/swagger/petstore/controller/** + **/io/swagger/petstore/exception/** + **/io/swagger/petstore/utils/** + + + + + + org.pitest + pitest-maven + 1.16.1 + + + io.swagger.petstore.data.Pet* + io.swagger.petstore.data.Order* + + + + + + + + + + giis + https://in2test.lsi.uniovi.es/xlib/maven/ + + false + + + true + + + + giis-snapshots + https://in2test.lsi.uniovi.es/xlib/maven-snapshots/ + + true + + + false + + + + + github-giis-uniovi + https://maven.pkg.github.com/giis-uniovi/* + + true + + + false + + + + + diff --git a/st-tdg-eval/src/main/java/io/swagger/v3/oas/annotations/media/Schema.java b/st-tdg-eval/src/main/java/io/swagger/v3/oas/annotations/media/Schema.java new file mode 100644 index 0000000..24989a4 --- /dev/null +++ b/st-tdg-eval/src/main/java/io/swagger/v3/oas/annotations/media/Schema.java @@ -0,0 +1,6 @@ +package io.swagger.v3.oas.annotations.media; + +public @interface Schema { + String description() default ""; + String allowableValues() default ""; +} diff --git a/st-tdg-eval/src/main/java/javax/xml/bind/annotation/XmlElement.java b/st-tdg-eval/src/main/java/javax/xml/bind/annotation/XmlElement.java new file mode 100644 index 0000000..cbd4b17 --- /dev/null +++ b/st-tdg-eval/src/main/java/javax/xml/bind/annotation/XmlElement.java @@ -0,0 +1,5 @@ +package javax.xml.bind.annotation; + +public @interface XmlElement { + String name() default "##default"; +} diff --git a/st-tdg-eval/src/main/java/javax/xml/bind/annotation/XmlElementWrapper.java b/st-tdg-eval/src/main/java/javax/xml/bind/annotation/XmlElementWrapper.java new file mode 100644 index 0000000..746a472 --- /dev/null +++ b/st-tdg-eval/src/main/java/javax/xml/bind/annotation/XmlElementWrapper.java @@ -0,0 +1,5 @@ +package javax.xml.bind.annotation; + +public @interface XmlElementWrapper { + String name() default "##default"; +} diff --git a/st-tdg-eval/src/main/java/javax/xml/bind/annotation/XmlRootElement.java b/st-tdg-eval/src/main/java/javax/xml/bind/annotation/XmlRootElement.java new file mode 100644 index 0000000..78802e7 --- /dev/null +++ b/st-tdg-eval/src/main/java/javax/xml/bind/annotation/XmlRootElement.java @@ -0,0 +1,5 @@ +package javax.xml.bind.annotation; + +public @interface XmlRootElement { + String name() default "##default"; +} diff --git a/st-tdg-eval/src/main/java/javax/xml/bind/annotation/XmlTransient.java b/st-tdg-eval/src/main/java/javax/xml/bind/annotation/XmlTransient.java new file mode 100644 index 0000000..2ae697e --- /dev/null +++ b/st-tdg-eval/src/main/java/javax/xml/bind/annotation/XmlTransient.java @@ -0,0 +1,4 @@ +package javax.xml.bind.annotation; + +public @interface XmlTransient { +} diff --git a/st-tdg-eval/src/test/java/test4giis/tdrules/eval/petstore/Base.java b/st-tdg-eval/src/test/java/test4giis/tdrules/eval/petstore/Base.java new file mode 100644 index 0000000..319bc5b --- /dev/null +++ b/st-tdg-eval/src/test/java/test4giis/tdrules/eval/petstore/Base.java @@ -0,0 +1,93 @@ +package test4giis.tdrules.eval.petstore; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.rules.TestName; + +import giis.tdrules.client.TdRulesApi; +import giis.tdrules.client.oa.OaSchemaApi; +import giis.tdrules.client.oa.OaSchemaIdResolver; +import giis.tdrules.openapi.model.TdSchema; +import giis.tdrules.store.loader.DataLoader; +import giis.tdrules.store.loader.IAttrGen; +import giis.tdrules.store.loader.gen.DictionaryAttrGen; +import giis.tdrules.store.loader.oa.ApiWriter; +import giis.tdrules.store.loader.oa.IPathResolver; +import giis.tdrules.store.loader.oa.OaLiveAdapter; +import giis.tdrules.store.loader.oa.OaPathResolver; +import giis.visualassert.Framework; +import giis.visualassert.VisualAssert; +import giis.visualassert.portable.FileUtil; +import in2test.application.qagrow.QAGrowApiProcess; +import lombok.extern.slf4j.Slf4j; + +/** + * Utilities and creation of the main objects to generate, load and evaluate the petstore + * Test Data Generation (TDG) + */ +@Slf4j +public class Base { + protected static final String PETSTORE_SCHEMA_LOCAL = "../swagger-petstore-main-fork/src/main/resources/openapi.yaml"; + + protected static TdSchema model; // readonly, created before all tests + + @Rule + public TestName testName = new TestName(); + + @Before + public void setUp() { + log.info("****** Running test: {} ******", testName.getMethodName()); + } + + protected void load(ApiWriter writer, String query) { + // Configure the schema id resolver to use id attribute as uid. + // The exception is the Tag entity, that has an id attribute, + // but looking at the source code, a post inserts unconditionally, + // allowing repeated id values. This is considered as a non uid. + OaSchemaApi schemaApi=new OaSchemaApi(PETSTORE_SCHEMA_LOCAL) + .setIdResolver(new OaSchemaIdResolver().setIdName("id").excludeEntity("Tag").excludeEntity("Order0")); + model = schemaApi.getSchema(); + + // Check the schema, because their changes may invalidate all results + String modelStr = new giis.tdrules.model.io.ModelJsonSerializer().serialize(model, true); + FileUtil.fileWrite("target/schema-petstore.json", modelStr); + VisualAssert va = new VisualAssert().setFramework(Framework.JUNIT4); + va.assertEquals(FileUtil.fileRead("src/test/resources/schema-petstore.json"), modelStr); + // Until here, this should be run once in BeforeClass, but some side effects on QAGrow#40 prevent doing that + + // The path resolver is created using a controller mock (writer) + // and the data loader using a dictionary + IPathResolver pathResolver = new OaPathResolver().setSchemaModel(model).setServerUrl("").setApiWriter(writer); + DataLoader loader = new DataLoader(model, new OaLiveAdapter(pathResolver)).setAttrGen(getPetstoreDictionary().setMinYear(2024)); + + // Generation and loading: Each test first delete all data previous to the generation and load + writer.delete("/test/deleteAll"); + QAGrowApiProcess qagrow = new QAGrowApiProcess(model, new TdRulesApi(), loader); + qagrow.genData4ApiQuery(query); + } + + /** + * Instancia un generador de datos configurado con un diccionario para que los + * datos generados no sean solo numeros, sino valores procedentes de un + * diccionario o mascaras + */ + protected IAttrGen getPetstoreDictionary() { + return new DictionaryAttrGen() + //https://www.southernliving.com/most-popular-pet-names-rover-6829769 + .with("Pet", "name").dictionary("Max", "Luna", "Charlie", "Bella", "Cooper", "Daisy", "Milo", "Lucy") + .with("Pet_photoUrls_xa", "photoUrls").padLeft('0', 6).mask("http://localhost/photos/{}.jpg") + .with("Pet_Tags_xa", "name").dictionary("Puppy", "Young", "Old") + .with("Category", "name").dictionary("Tiger", "Lion", "Monkey", "Snake") + .with("Customer_address_xa", "street").dictionary("Main St", "Broadway", "Park Ave", "Fulton St", "Madison Ave", "Pine St", "Amsterdam Ave", "Wall St") + //https://www.ssa.gov/oact/babynames/decades/century.html + //https://www.al.com/news/2019/10/50-most-common-last-names-in-america.html + .with("Customer", "username").dictionary("James Smith", "Mary Johnson", "Robert Williams", "Patricia Brown", "David Garcia", "Elizabeth Miller", "William Davis", "Barbara Wilson") + //https://www.worldatlas.com/articles/most-common-town-and-city-names-in-the-u-s-a.html#:~:text=Washington,this%20way%20is%20no%20surprise. + .with("Customer_address_xa", "city").dictionary("Springfield", "Franklin", "Greenville", "Bristol", "Clinton", "Fairview", "Salem", "Madison") + .with("Customer_address_xa", "state").dictionary("California", "Texas", "Florida", "New York", "Pennsylvania", "Illinois", "Ohio", "Georgia") + .with("Customer_address_xa", "zip").padLeft('0', 6) + .with("Order", "quantity").setInterval(1, 13) + ; + } + +} diff --git a/st-tdg-eval/src/test/java/test4giis/tdrules/eval/petstore/MockController.java b/st-tdg-eval/src/test/java/test4giis/tdrules/eval/petstore/MockController.java new file mode 100644 index 0000000..b613170 --- /dev/null +++ b/st-tdg-eval/src/test/java/test4giis/tdrules/eval/petstore/MockController.java @@ -0,0 +1,110 @@ +package test4giis.tdrules.eval.petstore; + +import java.net.URLDecoder; +import java.text.SimpleDateFormat; +import java.util.HashMap; +import java.util.Map; + +import com.fasterxml.jackson.annotation.JsonInclude.Include; +import com.fasterxml.jackson.databind.ObjectMapper; + +import giis.tdrules.store.loader.oa.ApiResponse; +import giis.tdrules.store.loader.oa.ApiWriter; +import io.swagger.petstore.data.OrderData; +import io.swagger.petstore.data.PetData; +import io.swagger.petstore.data.TestData; +import io.swagger.petstore.model.Category; +import io.swagger.petstore.model.Customer; +import io.swagger.petstore.model.Order; +import io.swagger.petstore.model.Pet; +import lombok.SneakyThrows; + +public class MockController extends ApiWriter { + + @Override + @SneakyThrows + public ApiResponse post(String url, String requestBody, boolean usePut) { + Map params = new HashMap<>(); + url = parseUrl(url, params); + if ("/pet".equals(url)) { + Pet pet = (Pet) deserialize(requestBody, Pet.class); + new PetData().addPet(pet); + return new ApiResponse(200, "OK", serialize(pet)); + } else if ("/category".equals(url)) { + Category category = (Category) deserialize(requestBody, Category.class); + new PetData().addCategory(category); + return new ApiResponse(200, "OK", serialize(category)); + } else if ("/store/customer".equals(url)) { + Customer customer = (Customer) deserialize(requestBody, Customer.class); + new OrderData().addCustomer(customer); + return new ApiResponse(200, "OK", serialize(customer)); + } else if ("/store/order".equals(url)) { + Order order = (Order) deserialize(requestBody, Order.class); + new OrderData().addOrder(order); + return new ApiResponse(200, "OK", serialize(order)); + } else if ("/store/updateDeliveryToCustomer".equals(url)) { + java.util.List orders = new OrderData().updateDeliveryToCustomer(Long.valueOf(params.get("customerId"))); + return new ApiResponse(200, "OK", serialize(orders)); + } else + return new ApiResponse(404, "Not Found", "No controller for " + url); + } + + @Override + public ApiResponse get(String url) { + Map params = new HashMap<>(); + url = parseUrl(url, params); + if ("/test/getAll".equals(url)) + return new ApiResponse(200, "OK", serialize(new TestData().getAll())); + else if ("/pet/findByStatus".equals(url)) + return new ApiResponse(200, "OK", serialize(new PetData().findPetByStatus(params.get("status")))); + else if ("/pet/findByCategoryAndStatus".equals(url)) + return new ApiResponse(200, "OK", serialize(new PetData().findPetByCategoryAndStatus(params.get("category"), params.get("status")))); + else if ("/store/findOrdersByCategoryAndOrderStatus".equals(url)) + return new ApiResponse(200, "OK", serialize(new OrderData().findOrdersByCategoryAndOrderStatus(params.get("category"), params.get("status")))); + else if ("/store/totalPetsToDeliverByAddress".equals(url)) + return new ApiResponse(200, "OK", serialize(new OrderData().totalPetsToDeliverByAddress())); + else + return new ApiResponse(404, "Not Found", "No controller for " + url); + } + + @Override + public ApiResponse delete(String url) { + if ("/test/deleteAll".equals(url)) { + new TestData().deleteAll(); + return new ApiResponse(200, "OK", ""); + } else + return new ApiResponse(404, "Not Found", "No controller for " + url); + } + + @SneakyThrows + private String parseUrl(String urlAndParams, Map parameters) { + String[] urlSplit = urlAndParams.split("\\?"); + if (urlSplit.length < 2) + return urlSplit[0];// no args + String[] pairs = urlSplit[1].split("&"); + for (String pair : pairs) { + int idx = pair.indexOf("="); + parameters.put(URLDecoder.decode(pair.substring(0, idx), "UTF-8"), + URLDecoder.decode(pair.substring(idx + 1), "UTF-8")); + } + return urlSplit[0]; + } + + /** + * Serializa un objeto cualquiera a json mostrando los atributos vacios o nulos + */ + @SneakyThrows + protected String serialize(Object dto) { + ObjectMapper mapper = new ObjectMapper().setSerializationInclusion(Include.NON_NULL) + .setSerializationInclusion(Include.NON_EMPTY) + .setDateFormat(new SimpleDateFormat("yyyy-MM-dd")); + return mapper.writeValueAsString(dto); + } + + @SneakyThrows + protected Object deserialize(String json, Class clazz) { + ObjectMapper mapper = new ObjectMapper(); + return mapper.readValue(json, clazz); + } + +} diff --git a/st-tdg-eval/src/test/java/test4giis/tdrules/eval/petstore/TestPetstore.java b/st-tdg-eval/src/test/java/test4giis/tdrules/eval/petstore/TestPetstore.java new file mode 100644 index 0000000..9f18a1e --- /dev/null +++ b/st-tdg-eval/src/test/java/test4giis/tdrules/eval/petstore/TestPetstore.java @@ -0,0 +1,153 @@ +package test4giis.tdrules.eval.petstore; + +import org.junit.Test; + +import giis.tdrules.store.loader.oa.ApiResponse; +import giis.tdrules.store.loader.oa.Reserializer; +import giis.visualassert.Framework; +import giis.visualassert.SoftVisualAssert; +import giis.visualassert.portable.FileUtil; +import lombok.extern.slf4j.Slf4j; + +/** + * Tests that generate, load and evaluate the petstore Test Data Generation + * (TDG), containing a subset of the main project's tests. To evaluate the + * mutation score of the business processes run this command fron maven: + * + * mvn test-compile org.pitest:pitest-maven:mutationCoverage + * + * As Pitest runs in the same process of the tested methods, the controller is + * mocked. Note that petstore does not use Spring, so that MockMvc can be used. + * A mock controller has been created for this purpose. + */ +@Slf4j +public class TestPetstore extends Base { + + private MockController mvc = new MockController(); + private SoftVisualAssert sva = new SoftVisualAssert().setFramework(Framework.JUNIT4); + + // All tests that are readonly follow the same pattern, by defining a static + // variable to prevent multiple data loads when mutants are evaluated. + // The result assertions check both the output produced by the tested method and + // the data loaded. All these outputs are saved in files at target folder and + // compared against the expected files at src/test/resources folder. + + private static boolean testFindPetByStatusLoaded = false; + + @Test + public void testFindPetByStatus() { + if (!testFindPetByStatusLoaded) { + load(mvc, "tds Pet where Pet.status='available'"); + testFindPetByStatusLoaded = true; + } + ApiResponse pets = mvc.get("/pet/findByStatus?status=available"); + assertReadResults(pets); + } + + private static boolean testFindPetByCategoryAndStatusLoaded = false; + + @Test + public void testFindPetByCategoryAndStatus() { + if (!testFindPetByCategoryAndStatusLoaded) { + load(mvc, "tds Pet where Pet.category::name='Cats' and Pet.status='available'"); + testFindPetByCategoryAndStatusLoaded = true; + } + ApiResponse pets = mvc.get("/pet/findByCategoryAndStatus?category=Cats&status=available"); + assertReadResults(pets); + } + + private static boolean testFindOrdersByCategoryAndOrderStatusLoaded = false; + + @Test + public void testFindOrdersByCategoryAndOrderStatus() { + if (!testFindOrdersByCategoryAndOrderStatusLoaded) { + load(mvc, "tds Customer, \"Order\", Pet where Pet.category::name='Dogs' and \"Order\".status='placed'"); + testFindOrdersByCategoryAndOrderStatusLoaded = true; + } + // Por que si anyado and \"Order\".complete=true, excepcion en qagrow, pero si pongo 1 en vez de true no. + ApiResponse pets = mvc.get("/store/findOrdersByCategoryAndOrderStatus?category=Dogs&status=placed"); + assertReadResults(pets); + } + + private static boolean testTotalPetsToDeliverByAddressLoaded = false; + + @Test + public void testTotalPetsToDeliverByAddress() { + if (!testTotalPetsToDeliverByAddressLoaded) { + load(mvc, "tds Customer, \"Order\", Pet" + + " where \"Order\".status='approved'" + + " group by Customer.address[]::zip"); + testTotalPetsToDeliverByAddressLoaded = true; + } + // Por que si uso select Customer.address[]::zip, sum(\"Order\".quantity) from + // quantity da varios valores cero, incluso al poner un minimo de uno en el diccionario? + // y si solo pongo tds no? + // por que zip no tiene mascara aplicada? + ApiResponse pets = mvc.get("/store/totalPetsToDeliverByAddress"); + assertReadResults(pets); + } + + // This is a put request that updates the db, do not save generated data + @Test + public void testUpdateDeliveryToCustomer() { + load(mvc, "tds Customer, \"Order\", Pet where Customer.id=1 and \"Order\".status='approved'"); + sva.assertClear(); + assertResults(true, false, false, null); + ApiResponse pets = mvc.post("/store/updateDeliveryToCustomer?customerId=1", "", true); + assertResults(false, true, true, pets); + sva.assertAll(); + } + + // to check get operations that do not modify the database + private void assertReadResults(ApiResponse result) { + sva.assertClear(); + assertResults(true, false, true, result); + sva.assertAll(); + } + + private void assertResults(boolean checkBefore, boolean checkAfter, boolean checkResult, ApiResponse result) { + if (checkBefore) { + ApiResponse data = mvc.get("/test/getAll"); + saveOutput(getResultString(data, "data"), "data"); + assertFiles(sva, "data"); + } + if (checkAfter) { + ApiResponse data = mvc.get("/test/getAll"); + saveOutput(getResultString(data, "dataout"), "dataout"); + assertFiles(sva, "dataout"); + } + if (checkResult) { + saveOutput(getResultString(result, "list"), "output"); + assertFiles(sva, "output"); + } + } + + private String getResultString(ApiResponse result, String format) { + String ret = ""; + if (result.getStatus() == 200) { + if ("data".equals(format) || "dataout".equals(format)) + ret = new Reserializer().reserializeData(result.getBody()); + else if ("list".equals(format)) + ret = new Reserializer().reserializeList(result.getBody()); + else + ret = result.getBody(); + } else { + ret = result.getStatus() + " " + result.getBody(); + } + return ret; + } + + private void saveOutput(String content, String type) { + log.info("*** Test {}:\n{}", type, content.trim()); + FileUtil.fileWrite("target/" + testName.getMethodName() + "-" + type + ".txt", content); + } + + private void assertFiles(SoftVisualAssert sva, String type) { + String expected = FileUtil.fileRead("src/test/resources/" + testName.getMethodName() + "-" + type + ".txt", + false); + String actual = FileUtil.fileRead("target/" + testName.getMethodName() + "-" + type + ".txt", false); + sva.assertEquals(expected == null ? "" : expected.replace("\r", ""), + actual == null ? "" : actual.replace("\r", "")); + } + +} diff --git a/st-tdg-eval/src/test/resources/logback.xml b/st-tdg-eval/src/test/resources/logback.xml new file mode 100644 index 0000000..b06b9a5 --- /dev/null +++ b/st-tdg-eval/src/test/resources/logback.xml @@ -0,0 +1,28 @@ + + + + %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n + + + + + target/swagger-petstore-test.log + + %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/st-tdg-eval/src/test/resources/schema-petstore.json b/st-tdg-eval/src/test/resources/schema-petstore.json new file mode 100644 index 0000000..ab828fc --- /dev/null +++ b/st-tdg-eval/src/test/resources/schema-petstore.json @@ -0,0 +1,451 @@ +{ + "storetype" : "openapi", + "entities" : [ { + "name" : "Order", + "entitytype" : "table", + "attributes" : [ { + "name" : "id", + "datatype" : "int64", + "uid" : "true", + "notnull" : "true" + }, { + "name" : "petId", + "datatype" : "int64", + "notnull" : "true", + "rid" : "Pet.id", + "ridname" : "fk_Order_petId" + }, { + "name" : "customerId", + "datatype" : "int64", + "notnull" : "true", + "rid" : "Customer.id", + "ridname" : "fk_Order_customerId" + }, { + "name" : "quantity", + "datatype" : "int32", + "notnull" : "true" + }, { + "name" : "shipDate", + "datatype" : "date-time", + "notnull" : "true" + }, { + "name" : "status", + "datatype" : "string", + "notnull" : "true", + "checkin" : "placed,approved,delivered" + }, { + "name" : "complete", + "datatype" : "boolean", + "notnull" : "true" + } ], + "ddls" : [ { + "command" : "post", + "query" : "/store/order" + } ] + }, { + "name" : "Customer_address_xa", + "entitytype" : "array", + "subtype" : "Address", + "attributes" : [ { + "name" : "pk_xa", + "datatype" : "integer", + "uid" : "true", + "notnull" : "true" + }, { + "name" : "fk_xa", + "datatype" : "int64", + "notnull" : "true", + "rid" : "Customer.id" + }, { + "name" : "street", + "datatype" : "string", + "notnull" : "true" + }, { + "name" : "city", + "datatype" : "string", + "notnull" : "true" + }, { + "name" : "state", + "datatype" : "string", + "notnull" : "true" + }, { + "name" : "zip", + "datatype" : "string", + "notnull" : "true" + } ] + }, { + "name" : "Customer", + "entitytype" : "table", + "attributes" : [ { + "name" : "id", + "datatype" : "int64", + "uid" : "true", + "notnull" : "true" + }, { + "name" : "username", + "datatype" : "string", + "notnull" : "true" + }, { + "name" : "address", + "datatype" : "Customer_address_xa", + "compositetype" : "array", + "subtype" : "object", + "notnull" : "true" + } ], + "ddls" : [ { + "command" : "post", + "query" : "/store/customer" + } ] + }, { + "name" : "Address", + "entitytype" : "table", + "attributes" : [ { + "name" : "street", + "datatype" : "string", + "notnull" : "true" + }, { + "name" : "city", + "datatype" : "string", + "notnull" : "true" + }, { + "name" : "state", + "datatype" : "string", + "notnull" : "true" + }, { + "name" : "zip", + "datatype" : "string", + "notnull" : "true" + } ] + }, { + "name" : "Category", + "entitytype" : "table", + "attributes" : [ { + "name" : "id", + "datatype" : "int64", + "uid" : "true", + "notnull" : "true" + }, { + "name" : "name", + "datatype" : "string", + "notnull" : "true" + } ], + "ddls" : [ { + "command" : "post", + "query" : "/category" + }, { + "command" : "post", + "query" : "/backid/category" + } ] + }, { + "name" : "User", + "entitytype" : "table", + "attributes" : [ { + "name" : "id", + "datatype" : "int64", + "uid" : "true", + "notnull" : "true" + }, { + "name" : "username", + "datatype" : "string", + "notnull" : "true" + }, { + "name" : "firstName", + "datatype" : "string", + "notnull" : "true" + }, { + "name" : "lastName", + "datatype" : "string", + "notnull" : "true" + }, { + "name" : "email", + "datatype" : "string", + "notnull" : "true" + }, { + "name" : "password", + "datatype" : "string", + "notnull" : "true" + }, { + "name" : "phone", + "datatype" : "string", + "notnull" : "true" + }, { + "name" : "userStatus", + "datatype" : "int32", + "notnull" : "true" + } ], + "ddls" : [ { + "command" : "post", + "query" : "/user" + }, { + "command" : "put", + "query" : "/user/{username}" + } ] + }, { + "name" : "Tag", + "entitytype" : "table", + "attributes" : [ { + "name" : "id", + "datatype" : "int64", + "notnull" : "true" + }, { + "name" : "name", + "datatype" : "string", + "notnull" : "true" + } ] + }, { + "name" : "Pet_category_xt", + "entitytype" : "type", + "subtype" : "Category", + "attributes" : [ { + "name" : "id", + "datatype" : "int64", + "notnull" : "true", + "rid" : "Category.id" + }, { + "name" : "name", + "datatype" : "string", + "notnull" : "true" + } ] + }, { + "name" : "Pet_photoUrls_xa", + "entitytype" : "array", + "attributes" : [ { + "name" : "pk_xa", + "datatype" : "integer", + "uid" : "true", + "notnull" : "true" + }, { + "name" : "fk_xa", + "datatype" : "int64", + "notnull" : "true", + "rid" : "Pet.id" + }, { + "name" : "photoUrls", + "datatype" : "string", + "notnull" : "true" + } ] + }, { + "name" : "Pet_tags_xa", + "entitytype" : "array", + "subtype" : "Tag", + "attributes" : [ { + "name" : "pk_xa", + "datatype" : "integer", + "uid" : "true", + "notnull" : "true" + }, { + "name" : "fk_xa", + "datatype" : "int64", + "notnull" : "true", + "rid" : "Pet.id" + }, { + "name" : "id", + "datatype" : "int64", + "notnull" : "true" + }, { + "name" : "name", + "datatype" : "string", + "notnull" : "true" + } ] + }, { + "name" : "Pet", + "entitytype" : "table", + "attributes" : [ { + "name" : "id", + "datatype" : "int64", + "uid" : "true", + "notnull" : "true" + }, { + "name" : "name", + "datatype" : "string", + "notnull" : "true" + }, { + "name" : "category", + "datatype" : "Pet_category_xt", + "compositetype" : "type", + "notnull" : "true" + }, { + "name" : "photoUrls", + "datatype" : "Pet_photoUrls_xa", + "compositetype" : "array", + "subtype" : "string", + "notnull" : "true" + }, { + "name" : "tags", + "datatype" : "Pet_tags_xa", + "compositetype" : "array", + "subtype" : "object", + "notnull" : "true" + }, { + "name" : "status", + "datatype" : "string", + "notnull" : "true", + "checkin" : "available,pending,sold" + } ], + "ddls" : [ { + "command" : "post", + "query" : "/pet" + }, { + "command" : "put", + "query" : "/pet" + }, { + "command" : "post", + "query" : "/backid/pet" + } ] + }, { + "name" : "Customer0", + "entitytype" : "table", + "attributes" : [ { + "name" : "id", + "datatype" : "int64", + "uid" : "true", + "notnull" : "true" + }, { + "name" : "username", + "datatype" : "string", + "notnull" : "true" + } ], + "ddls" : [ { + "command" : "post", + "query" : "/store/customer0" + } ] + }, { + "name" : "Order0", + "entitytype" : "table", + "attributes" : [ { + "name" : "id", + "datatype" : "int64", + "uid" : "true", + "notnull" : "true" + }, { + "name" : "petId", + "datatype" : "int64", + "notnull" : "true", + "rid" : "Pet0.id", + "ridname" : "fk_Order0_petId" + }, { + "name" : "customerId", + "datatype" : "int64", + "notnull" : "true", + "rid" : "Customer0.id", + "ridname" : "fk_Order0_customerId" + }, { + "name" : "quantity", + "datatype" : "int32", + "notnull" : "true" + }, { + "name" : "shipDate", + "datatype" : "date-time", + "notnull" : "true" + }, { + "name" : "status", + "datatype" : "string", + "notnull" : "true", + "checkin" : "placed,approved,delivered" + }, { + "name" : "complete", + "datatype" : "boolean", + "notnull" : "true" + } ], + "ddls" : [ { + "command" : "post", + "query" : "/store/order0" + } ] + }, { + "name" : "Pet0_category_xt", + "entitytype" : "type", + "attributes" : [ { + "name" : "id", + "datatype" : "int64", + "notnull" : "true" + }, { + "name" : "name", + "datatype" : "string", + "notnull" : "true" + } ] + }, { + "name" : "Pet0", + "entitytype" : "table", + "attributes" : [ { + "name" : "id", + "datatype" : "int64", + "uid" : "true", + "notnull" : "true" + }, { + "name" : "name", + "datatype" : "string", + "notnull" : "true" + }, { + "name" : "category", + "datatype" : "Pet0_category_xt", + "compositetype" : "type", + "notnull" : "true" + }, { + "name" : "status", + "datatype" : "string", + "notnull" : "true", + "checkin" : "available,pending,sold" + } ], + "ddls" : [ { + "command" : "post", + "query" : "/pet0" + } ] + }, { + "name" : "Pet1_category_xt", + "entitytype" : "type", + "subtype" : "Category", + "attributes" : [ { + "name" : "id", + "datatype" : "int64", + "notnull" : "true", + "rid" : "Category.id" + }, { + "name" : "name", + "datatype" : "string", + "notnull" : "true" + } ] + }, { + "name" : "Pet1", + "entitytype" : "table", + "attributes" : [ { + "name" : "id", + "datatype" : "int64", + "uid" : "true", + "notnull" : "true" + }, { + "name" : "name", + "datatype" : "string", + "notnull" : "true" + }, { + "name" : "category", + "datatype" : "Pet1_category_xt", + "compositetype" : "type", + "notnull" : "true" + }, { + "name" : "status", + "datatype" : "string", + "notnull" : "true", + "checkin" : "available,pending,sold" + } ], + "ddls" : [ { + "command" : "post", + "query" : "/pet1" + } ] + }, { + "name" : "ApiResponse", + "entitytype" : "table", + "attributes" : [ { + "name" : "code", + "datatype" : "int32", + "notnull" : "true" + }, { + "name" : "type", + "datatype" : "string", + "notnull" : "true" + }, { + "name" : "message", + "datatype" : "string", + "notnull" : "true" + } ] + } ] +} \ No newline at end of file diff --git a/st-tdg-eval/src/test/resources/testFindOrdersByCategoryAndOrderStatus-data.txt b/st-tdg-eval/src/test/resources/testFindOrdersByCategoryAndOrderStatus-data.txt new file mode 100644 index 0000000..f29c077 --- /dev/null +++ b/st-tdg-eval/src/test/resources/testFindOrdersByCategoryAndOrderStatus-data.txt @@ -0,0 +1,17 @@ +"category":{"id":1,"name":"Dogs"} +"category":{"id":2,"name":"Tiger"} +"category":{"id":3,"name":"Lion"} +"pet":{"id":1,"category":{"id":1,"name":"Dogs"},"name":"Max","photoUrls":["http://localhost/photos/000303.jpg"],"tags":[{"id":703,"name":"Puppy"}],"status":"available"} +"pet":{"id":2,"category":{"id":2,"name":"Tiger"},"name":"Luna","photoUrls":["http://localhost/photos/000403.jpg"],"tags":[{"id":803,"name":"Young"}],"status":"pending"} +"pet":{"id":3,"category":{"id":1,"name":"Dogs"},"name":"Charlie","photoUrls":["http://localhost/photos/000503.jpg"],"tags":[{"id":903,"name":"Old"}],"status":"sold"} +"pet":{"id":4,"category":{"id":1,"name":"Dogs"},"name":"Bella","photoUrls":["http://localhost/photos/000603.jpg"],"status":"available"} +"pet":{"id":5,"category":{"id":1,"name":"Dogs"},"name":"Cooper","tags":[{"id":1003,"name":"Puppy-1"}],"status":"pending"} +"order":{"id":1,"petId":1,"customerId":1,"quantity":11,"shipDate":"2024-01-26","status":"placed","complete":false} +"order":{"id":2,"petId":2,"customerId":1,"quantity":7,"shipDate":"2024-01-27","status":"placed","complete":true} +"order":{"id":3,"petId":1,"customerId":1,"quantity":3,"shipDate":"2024-01-28","status":"approved","complete":false} +"order":{"id":4,"petId":1,"customerId":2,"quantity":12,"shipDate":"2024-01-29","status":"placed","complete":true} +"order":{"id":5,"petId":4,"customerId":1,"quantity":8,"shipDate":"2024-01-30","status":"placed","complete":false} +"order":{"id":6,"petId":5,"customerId":1,"quantity":4,"shipDate":"2024-02-01","status":"placed","complete":true} +"customer":{"id":1,"username":"James Smith","address":[{"street":"Main St","city":"Springfield","state":"California","zip":"001606"}]} +"customer":{"id":2,"username":"Mary Johnson"} +"customer":{"id":3,"username":"Robert Williams","address":[{"street":"Broadway","city":"Franklin","state":"Texas","zip":"001706"}]} diff --git a/st-tdg-eval/src/test/resources/testFindOrdersByCategoryAndOrderStatus-output.txt b/st-tdg-eval/src/test/resources/testFindOrdersByCategoryAndOrderStatus-output.txt new file mode 100644 index 0000000..67a270d --- /dev/null +++ b/st-tdg-eval/src/test/resources/testFindOrdersByCategoryAndOrderStatus-output.txt @@ -0,0 +1,4 @@ +{"id":1,"petId":1,"customerId":1,"quantity":11,"shipDate":"2024-01-26","status":"placed","complete":false} +{"id":4,"petId":1,"customerId":2,"quantity":12,"shipDate":"2024-01-29","status":"placed","complete":true} +{"id":5,"petId":4,"customerId":1,"quantity":8,"shipDate":"2024-01-30","status":"placed","complete":false} +{"id":6,"petId":5,"customerId":1,"quantity":4,"shipDate":"2024-02-01","status":"placed","complete":true} diff --git a/st-tdg-eval/src/test/resources/testFindPetByCategoryAndStatus-data.txt b/st-tdg-eval/src/test/resources/testFindPetByCategoryAndStatus-data.txt new file mode 100644 index 0000000..2d766c1 --- /dev/null +++ b/st-tdg-eval/src/test/resources/testFindPetByCategoryAndStatus-data.txt @@ -0,0 +1,8 @@ +"category":{"id":1,"name":"Cats"} +"category":{"id":2,"name":"Tiger"} +"category":{"id":3,"name":"Lion"} +"pet":{"id":1,"category":{"id":1,"name":"Cats"},"name":"Max","photoUrls":["http://localhost/photos/000303.jpg"],"tags":[{"id":703,"name":"Puppy"}],"status":"available"} +"pet":{"id":2,"category":{"id":2,"name":"Tiger"},"name":"Luna","photoUrls":["http://localhost/photos/000403.jpg"],"tags":[{"id":803,"name":"Young"}],"status":"available"} +"pet":{"id":3,"category":{"id":1,"name":"Cats"},"name":"Charlie","photoUrls":["http://localhost/photos/000503.jpg"],"tags":[{"id":903,"name":"Old"}],"status":"pending"} +"pet":{"id":4,"category":{"id":1,"name":"Cats"},"name":"Bella","photoUrls":["http://localhost/photos/000603.jpg"],"status":"available"} +"pet":{"id":5,"category":{"id":1,"name":"Cats"},"name":"Cooper","tags":[{"id":1003,"name":"Puppy-1"}],"status":"available"} diff --git a/st-tdg-eval/src/test/resources/testFindPetByCategoryAndStatus-output.txt b/st-tdg-eval/src/test/resources/testFindPetByCategoryAndStatus-output.txt new file mode 100644 index 0000000..0d241c3 --- /dev/null +++ b/st-tdg-eval/src/test/resources/testFindPetByCategoryAndStatus-output.txt @@ -0,0 +1,3 @@ +{"id":1,"category":{"id":1,"name":"Cats"},"name":"Max","photoUrls":["http://localhost/photos/000303.jpg"],"tags":[{"id":703,"name":"Puppy"}],"status":"available"} +{"id":4,"category":{"id":1,"name":"Cats"},"name":"Bella","photoUrls":["http://localhost/photos/000603.jpg"],"status":"available"} +{"id":5,"category":{"id":1,"name":"Cats"},"name":"Cooper","tags":[{"id":1003,"name":"Puppy-1"}],"status":"available"} diff --git a/st-tdg-eval/src/test/resources/testFindPetByStatus-data.txt b/st-tdg-eval/src/test/resources/testFindPetByStatus-data.txt new file mode 100644 index 0000000..57f094e --- /dev/null +++ b/st-tdg-eval/src/test/resources/testFindPetByStatus-data.txt @@ -0,0 +1,6 @@ +"category":{"id":1,"name":"Tiger"} +"category":{"id":2,"name":"Lion"} +"pet":{"id":1,"category":{"id":1,"name":"Tiger"},"name":"Max","photoUrls":["http://localhost/photos/000203.jpg"],"tags":[{"id":503,"name":"Puppy"}],"status":"pending"} +"pet":{"id":2,"category":{"id":1,"name":"Tiger"},"name":"Luna","photoUrls":["http://localhost/photos/000303.jpg"],"tags":[{"id":603,"name":"Young"}],"status":"available"} +"pet":{"id":3,"category":{"id":1,"name":"Tiger"},"name":"Charlie","photoUrls":["http://localhost/photos/000403.jpg"],"status":"available"} +"pet":{"id":4,"category":{"id":1,"name":"Tiger"},"name":"Bella","tags":[{"id":703,"name":"Old"}],"status":"available"} diff --git a/st-tdg-eval/src/test/resources/testFindPetByStatus-output.txt b/st-tdg-eval/src/test/resources/testFindPetByStatus-output.txt new file mode 100644 index 0000000..bdc2711 --- /dev/null +++ b/st-tdg-eval/src/test/resources/testFindPetByStatus-output.txt @@ -0,0 +1,3 @@ +{"id":2,"category":{"id":1,"name":"Tiger"},"name":"Luna","photoUrls":["http://localhost/photos/000303.jpg"],"tags":[{"id":603,"name":"Young"}],"status":"available"} +{"id":3,"category":{"id":1,"name":"Tiger"},"name":"Charlie","photoUrls":["http://localhost/photos/000403.jpg"],"status":"available"} +{"id":4,"category":{"id":1,"name":"Tiger"},"name":"Bella","tags":[{"id":703,"name":"Old"}],"status":"available"} diff --git a/st-tdg-eval/src/test/resources/testTotalPetsToDeliverByAddress-data.txt b/st-tdg-eval/src/test/resources/testTotalPetsToDeliverByAddress-data.txt new file mode 100644 index 0000000..35fa035 --- /dev/null +++ b/st-tdg-eval/src/test/resources/testTotalPetsToDeliverByAddress-data.txt @@ -0,0 +1,17 @@ +"category":{"id":1,"name":"Tiger"} +"category":{"id":2,"name":"Lion"} +"category":{"id":3,"name":"Monkey"} +"pet":{"id":1,"category":{"id":1,"name":"Tiger"},"name":"Max","photoUrls":["http://localhost/photos/000303.jpg"],"tags":[{"id":703,"name":"Puppy"}],"status":"available"} +"pet":{"id":2,"category":{"id":2,"name":"Lion"},"name":"Luna","photoUrls":["http://localhost/photos/000403.jpg"],"tags":[{"id":803,"name":"Young"}],"status":"pending"} +"pet":{"id":3,"category":{"id":2,"name":"Lion"},"name":"Charlie","photoUrls":["http://localhost/photos/000503.jpg"],"tags":[{"id":903,"name":"Old"}],"status":"sold"} +"pet":{"id":4,"category":{"id":1,"name":"Tiger"},"name":"Bella","photoUrls":["http://localhost/photos/000603.jpg"],"status":"available"} +"pet":{"id":5,"category":{"id":1,"name":"Tiger"},"name":"Cooper","tags":[{"id":1003,"name":"Puppy-1"}],"status":"pending"} +"order":{"id":1,"petId":1,"customerId":1,"quantity":11,"shipDate":"2024-01-26","status":"placed","complete":false} +"order":{"id":2,"petId":1,"customerId":1,"quantity":7,"shipDate":"2024-01-27","status":"approved","complete":true} +"order":{"id":3,"petId":2,"customerId":1,"quantity":3,"shipDate":"2024-01-28","status":"approved","complete":false} +"order":{"id":4,"petId":1,"customerId":2,"quantity":12,"shipDate":"2024-01-29","status":"approved","complete":true} +"order":{"id":5,"petId":4,"customerId":1,"quantity":8,"shipDate":"2024-01-30","status":"approved","complete":false} +"order":{"id":6,"petId":5,"customerId":1,"quantity":4,"shipDate":"2024-02-01","status":"approved","complete":true} +"customer":{"id":1,"username":"James Smith","address":[{"street":"Main St","city":"Springfield","state":"California","zip":"0"}]} +"customer":{"id":2,"username":"Mary Johnson"} +"customer":{"id":3,"username":"Robert Williams","address":[{"street":"Broadway","city":"Franklin","state":"Texas","zip":"1"}]} diff --git a/st-tdg-eval/src/test/resources/testTotalPetsToDeliverByAddress-output.txt b/st-tdg-eval/src/test/resources/testTotalPetsToDeliverByAddress-output.txt new file mode 100644 index 0000000..b4ab982 --- /dev/null +++ b/st-tdg-eval/src/test/resources/testTotalPetsToDeliverByAddress-output.txt @@ -0,0 +1,2 @@ +{"zip":"0","total":22} +{"zip":"1","total":0} diff --git a/st-tdg-eval/src/test/resources/testUpdateDeliveryToCustomer-data.txt b/st-tdg-eval/src/test/resources/testUpdateDeliveryToCustomer-data.txt new file mode 100644 index 0000000..3339e9c --- /dev/null +++ b/st-tdg-eval/src/test/resources/testUpdateDeliveryToCustomer-data.txt @@ -0,0 +1,13 @@ +"category":{"id":1,"name":"Tiger"} +"category":{"id":2,"name":"Lion"} +"pet":{"id":1,"category":{"id":1,"name":"Tiger"},"name":"Max","photoUrls":["http://localhost/photos/000203.jpg"],"tags":[{"id":503,"name":"Puppy"}],"status":"available"} +"pet":{"id":2,"category":{"id":1,"name":"Tiger"},"name":"Luna","photoUrls":["http://localhost/photos/000303.jpg"],"tags":[{"id":603,"name":"Young"}],"status":"pending"} +"pet":{"id":3,"category":{"id":1,"name":"Tiger"},"name":"Charlie","photoUrls":["http://localhost/photos/000403.jpg"],"status":"sold"} +"pet":{"id":4,"category":{"id":1,"name":"Tiger"},"name":"Bella","tags":[{"id":703,"name":"Old"}],"status":"available"} +"order":{"id":1,"petId":1,"customerId":1,"quantity":5,"shipDate":"2024-01-21","status":"approved","complete":true} +"order":{"id":2,"petId":1,"customerId":2,"quantity":1,"shipDate":"2024-01-22","status":"approved","complete":false} +"order":{"id":3,"petId":1,"customerId":1,"quantity":10,"shipDate":"2024-01-23","status":"placed","complete":true} +"order":{"id":4,"petId":3,"customerId":1,"quantity":6,"shipDate":"2024-01-24","status":"approved","complete":false} +"order":{"id":5,"petId":4,"customerId":1,"quantity":2,"shipDate":"2024-01-25","status":"approved","complete":true} +"customer":{"id":1,"username":"James Smith","address":[{"street":"Main St","city":"Springfield","state":"California","zip":"001206"}]} +"customer":{"id":2,"username":"Mary Johnson","address":[{"street":"Broadway","city":"Franklin","state":"Texas","zip":"001306"}]} diff --git a/st-tdg-eval/src/test/resources/testUpdateDeliveryToCustomer-dataout.txt b/st-tdg-eval/src/test/resources/testUpdateDeliveryToCustomer-dataout.txt new file mode 100644 index 0000000..5b4af3b --- /dev/null +++ b/st-tdg-eval/src/test/resources/testUpdateDeliveryToCustomer-dataout.txt @@ -0,0 +1,13 @@ +"category":{"id":1,"name":"Tiger"} +"category":{"id":2,"name":"Lion"} +"pet":{"id":1,"category":{"id":1,"name":"Tiger"},"name":"Max","photoUrls":["http://localhost/photos/000203.jpg"],"tags":[{"id":503,"name":"Puppy"}],"status":"available"} +"pet":{"id":2,"category":{"id":1,"name":"Tiger"},"name":"Luna","photoUrls":["http://localhost/photos/000303.jpg"],"tags":[{"id":603,"name":"Young"}],"status":"pending"} +"pet":{"id":3,"category":{"id":1,"name":"Tiger"},"name":"Charlie","photoUrls":["http://localhost/photos/000403.jpg"],"status":"sold"} +"pet":{"id":4,"category":{"id":1,"name":"Tiger"},"name":"Bella","tags":[{"id":703,"name":"Old"}],"status":"available"} +"order":{"id":1,"petId":1,"customerId":1,"quantity":5,"shipDate":"2024-01-21","status":"delivered","complete":true} +"order":{"id":2,"petId":1,"customerId":2,"quantity":1,"shipDate":"2024-01-22","status":"approved","complete":false} +"order":{"id":3,"petId":1,"customerId":1,"quantity":10,"shipDate":"2024-01-23","status":"placed","complete":true} +"order":{"id":4,"petId":3,"customerId":1,"quantity":6,"shipDate":"2024-01-24","status":"delivered","complete":false} +"order":{"id":5,"petId":4,"customerId":1,"quantity":2,"shipDate":"2024-01-25","status":"delivered","complete":true} +"customer":{"id":1,"username":"James Smith","address":[{"street":"Main St","city":"Springfield","state":"California","zip":"001206"}]} +"customer":{"id":2,"username":"Mary Johnson","address":[{"street":"Broadway","city":"Franklin","state":"Texas","zip":"001306"}]} diff --git a/st-tdg-eval/src/test/resources/testUpdateDeliveryToCustomer-output.txt b/st-tdg-eval/src/test/resources/testUpdateDeliveryToCustomer-output.txt new file mode 100644 index 0000000..d86dfea --- /dev/null +++ b/st-tdg-eval/src/test/resources/testUpdateDeliveryToCustomer-output.txt @@ -0,0 +1,3 @@ +{"id":1,"petId":1,"customerId":1,"quantity":5,"shipDate":"2024-01-21","status":"delivered","complete":true} +{"id":4,"petId":3,"customerId":1,"quantity":6,"shipDate":"2024-01-24","status":"delivered","complete":false} +{"id":5,"petId":4,"customerId":1,"quantity":2,"shipDate":"2024-01-25","status":"delivered","complete":true} diff --git a/st-tdg-test/pom.xml b/st-tdg-test/pom.xml new file mode 100644 index 0000000..ecfe7f4 --- /dev/null +++ b/st-tdg-test/pom.xml @@ -0,0 +1,203 @@ + + 4.0.0 + giis + tdrules-oa-rp + 1.4.0-SNAPSHOT + jar + + Grupo de Investigacion en Ingenieria del Software - Universidad de Oviedo + http://giis.uniovi.es/ + + + 1.8 + 1.8 + UTF-8 + 3.3.0 + + 4.3.0 + + 1.4.266 + + + + + + org.slf4j + slf4j-api + 1.7.36 + + + ch.qos.logback + logback-classic + 1.2.13 + test + + + junit + junit + 4.13.2 + test + + + io.github.giis-uniovi + tdrules-client + ${tdrules.version} + test + + + io.github.giis-uniovi + tdrules-client-oa + ${tdrules.version} + test + + + io.github.giis-uniovi + tdrules-store-loader + ${tdrules.version} + test + + + giis + qagrow + ${qagrow.version} + test + + + io.github.javiertuya + visual-assert + 2.4.2 + test + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.13.0 + + 1.8 + 1.8 + + + + org.apache.maven.plugins + maven-surefire-plugin + ${surefire.version} + + true + + ${surefireArgLine} + true + + false + ${skipTests} + + + + + org.apache.maven.plugins + maven-antrun-plugin + 3.1.0 + + + + junit-reports + test + + + + + + + + + + + + + run + + + + + + org.apache.ant + ant-junit + 1.10.14 + + + org.apache.ant + ant-trax + 1.8.0 + + + + + + + + + giis + https://in2test.lsi.uniovi.es/xlib/maven/ + + false + + + true + + + + giis-snapshots + https://in2test.lsi.uniovi.es/xlib/maven-snapshots/ + + true + + + false + + + + + github-giis-uniovi + https://maven.pkg.github.com/giis-uniovi/* + + true + + + false + + + + + diff --git a/st-tdg-test/src/test/java/test4giis/tdrules/BaseAll.java b/st-tdg-test/src/test/java/test4giis/tdrules/BaseAll.java new file mode 100644 index 0000000..a3e89cf --- /dev/null +++ b/st-tdg-test/src/test/java/test4giis/tdrules/BaseAll.java @@ -0,0 +1,287 @@ +package test4giis.tdrules; + +import static org.junit.Assert.assertEquals; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.util.Arrays; + +import org.apache.commons.io.FileUtils; +import org.apache.commons.io.FilenameUtils; +import org.junit.Before; +import org.junit.Rule; +import org.junit.rules.TestName; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import giis.tdrules.client.TdRulesApi; +import giis.tdrules.model.io.ModelJsonSerializer; +import giis.tdrules.openapi.model.TdRules; +import giis.tdrules.openapi.model.TdSchema; +import giis.tdrules.store.loader.DataLoader; +import giis.tdrules.store.loader.IAttrGen; +import giis.tdrules.store.loader.oa.ApiWriter; +import giis.tdrules.store.loader.oa.IPathResolver; +import giis.tdrules.store.loader.oa.OaLiveAdapter; +import giis.tdrules.store.loader.oa.OaLocalAdapter; +import giis.tdrules.store.loader.oa.OaPathResolver; +import giis.tdrules.store.loader.oa.Reserializer; +import giis.visualassert.Framework; +import giis.visualassert.VisualAssert; +import in2test.application.qagrow.QAGrowApiProcess; + +/** + * This is a base class for all system and integration tests that + * defines basic configurations to manage the different items involved in the tests: + * + * - Abstract methods to configure main test parameters + * - Methods to configure the main test objects (can be overriden for fine tuning) + * - Test utilities (making assertions, serialization, etc.) + * + * The two main configuration methods are getSubjectName (to identify the SUT) and + * isLiveBackend to indicate whether the test generates and sends data to a live backend + * or everything is performed in local (no SUT needed). + * These values in combination with the test inheritance, allow creating four + * different flavours of the tests: + * + * - datagen-local: a simulation of the data generation that manually specifies + * the commands sent to the Data Loader and gets the data that would be loaded. + * - datagen-live: manually specifies the commands sent to the Data Loader, + * but loads the data in a live SUT backend + * - qagrow-local: Automatically generates the test data, but working in local + * - qagrow-live: Automatically generates the test data and loads the data + * in a live SUT backend. + * + * The last is the true system test that integrates all main components: + * data loader, qagrow generator, fpc rule generator and the SUT backend + */ +public abstract class BaseAll { + protected Logger log = LoggerFactory.getLogger(this.getClass()); + + private static final String TEST_PATH_BENCHMARK = "src/test/resources"; + private static final String TEST_PATH_OUTPUT = "target/out"; + + @Rule + public TestName testName = new TestName(); + + @Before + public void setUp() { + log.info("****** Running {} test: {} ******", getSutName(), testName.getMethodName()); + if (isLiveBackend()) { + log.info("Clear out data stored in the backend"); + deleteAllLiveData(); + } + } + + /** + * Returns a string to identify the test subject (e.g. petstore, market) + * to allow identify separate the expected and actual outputs + */ + protected abstract String getSutName(); + + /** + * Returns true if the tests are running against a live backend, false by default + * (must be overriden by all live test methods) + */ + protected boolean isLiveBackend() { + return false; + } + + /** + * Returns the url of the backend + */ + protected abstract String getServerUrl(); + + /** + * Returns the endpoint to get all data from the backend + */ + protected abstract String getAllDataLiveEndpoint(); + + /** + * Returns the endpoint to reset all data in the backend + */ + protected abstract String getDeleteAllDataLiveEndpoint(); + + /** + * Returns the data schema for each test. + * Each base test should configure the appropriate IdResolver and location of the OpenApi specification + */ + protected abstract TdSchema getSchema(); + + /** + * Gets the TdRules client used to obtain the FPC coverage rules + */ + protected TdRulesApi getRulesApi() { + return new TdRulesApi(); + } + + /** + * Gets the TdRules model for a given query, and reprocess the version numbers + * to allow comparison of expected test results + */ + protected TdRules getRules(String query) { + TdRules rules = getRulesApi() + .getRules(getSchema(), query, "noboundaries gettransformedquery formatquery clientname=" + getSutName()); + // remove version to allow result comparison + return filterRulesVersion(rules); + } + + /** + * Gets a data loader for the current configuration: + * - In local tests uses the default OaLocalAdapter. + * - In live tests uses the default OaLiveAdapter and the default OaPathResolver + * configured to resolve paths from the schema model. + * If a custom path resolver is needed the test must override the getLiveDataLoader() + * method with the appropriate configuration. Calling getDataLoader() will invoke + * the overriden method. + */ + protected DataLoader getDataLoader() { + return isLiveBackend() ? getLiveDataLoader() : getLocalDataLoader(); + } + protected DataLoader getLocalDataLoader() { + return new DataLoader(getSchema(), new OaLocalAdapter()); + } + protected DataLoader getLiveDataLoader() { + TdSchema model = getSchema(); + IPathResolver pathResolver = new OaPathResolver().setSchemaModel(model).setServerUrl(getServerUrl()); + return new DataLoader(model, new OaLiveAdapter(pathResolver)); + } + + /** + * Generates test data for a given query using QAGrow and loads + * the data as indicated by the specified data loader (that can be + * either local or live) + */ + protected void generateAndLoad(DataLoader loader, String query) { + log.info("Generate test data for query\n{}", query); + QAGrowApiProcess qagrow = new QAGrowApiProcess(getSchema(), getRulesApi(), loader); + qagrow.genData4ApiQuery(query); + } + + protected void generateAndLoad(DataLoader loader, String query, IAttrGen dictionary) { + log.info("Generate test data for query\n{}", query); + QAGrowApiProcess qagrow = new QAGrowApiProcess(getSchema(), getRulesApi(), loader, dictionary); + qagrow.genData4ApiQuery(query); + } + + protected void generateAndLoad(DataLoader loader, String[] queries, IAttrGen dictionary) { + log.info("Generate test data for query\n{}", queries.toString()); + QAGrowApiProcess qagrow = new QAGrowApiProcess(getSchema(), getRulesApi(), loader, dictionary); + qagrow.genData4ApiQueries(Arrays.asList(queries)); + } + + + /** + * Gets all data from the backend + */ + protected String getAllLiveData() { + ApiWriter api=new ApiWriter(); + return api.get(getAllDataLiveEndpoint()).getBody(); + } + /** + * Resets all data in the backend + */ + protected String deleteAllLiveData() { + ApiWriter api=new ApiWriter(); + return api.delete(getDeleteAllDataLiveEndpoint()).getBody(); + } + + /** + * General assert on the content of a model (as string) against the expected. + * Actual outputs are saved and then comparison is made between the content of + * the expected and actual files. + */ + protected void assertModel(String fileName, String actualModel) { + String outFolder = FilenameUtils.concat(TEST_PATH_OUTPUT, getSutName()); + String bmkFolder = FilenameUtils.concat(TEST_PATH_BENCHMARK, getSutName()); + actualModel = actualModel.replace("\r", ""); // normalize end of line + fileWrite(outFolder, fileName, actualModel); + + String expected = fileRead(bmkFolder, fileName).replace("\r", ""); + new VisualAssert().setFramework(Framework.JUNIT4) + .assertEquals(expected, actualModel, "failed " + fileName, "diff-" + getSutName() + "-" + fileName + ".html"); + assertEquals(expected, actualModel); + } + + /** + * Assert to compare the test data that has been generated or loaded. + * Comparison is different if the test data is locally generated, + * or if it has been loaded to a live backend (in this case + * a call to get the data content is made before comparison) + */ + /** + * Gets a data loader according to the current configuration. + * The actual output data is obtanied as indicated: + * - In local uses the output produced by the data adapter. + * - In live tests queries the backend to get all stored data. + */ + protected void assertData(String fileName, DataLoader dg) { + if (isLiveBackend()) + assertLiveData(fileName, dg); + else + assertLocalData(fileName, dg); + } + protected void assertLocalData(String fileName, DataLoader dg) { + assertModel(fileName, dg.getDataAdapter().getAllAsString()); + } + protected void assertLiveData(String fileName, DataLoader dg) { + // Gets the data from the backend and + // uses a more compact presentation for easier comparison (an object per line) + String payload=getAllLiveData(); + payload=reserializeStoredData(payload); + log.info("Actual data stored in the backend\n{}", reserializeStoredData(payload)); + // Rename the file to separate locally generated from live loaded + assertModel(fileName.replace("-local-", "-live-"), payload); + } + + /** + * Removes the version number of the FPC rules to allow repeatable comparisons + * (saves the version in target to use during debugging) + */ + protected TdRules filterRulesVersion(TdRules rules) { + String version=rules.getVersion(); + String outputPath = FilenameUtils.concat(TEST_PATH_OUTPUT, getSutName()); + fileWrite(outputPath, "last-fpc-version.txt", version); + rules.version("0.0.0").environment("development"); + return rules; + } + + /** + * Serializa un objeto cualquiera a json mostrando los atributos vacios o nulos + */ + protected String serialize(TdSchema model) { + return new ModelJsonSerializer().serialize(model, true); + } + + /** + * Serializa el contenido de toda la base de datos como: + * - un objeto cuyos items son el contenido de cada una de las tablas + * - cada item es un objeto de clave nombre de tabla y valor un array + * con el contenido de cada fila de la tabla + * Devuelve un string donde cada linea es un objeto de clave + * nombre de tabla y valor el objeto con los valores de la fila + */ + protected String reserializeStoredData(String payload) { + return new Reserializer().reserializeData(payload); + } + + protected void fileWrite(String path, String fileName, String value) { + try { + FileUtils.writeStringToFile(new File(FilenameUtils.concat(path, fileName)), value, "UTF-8"); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + protected String fileRead(String path, String fileName) { + try { + return FileUtils.readFileToString(new File(FilenameUtils.concat(path, fileName)), "UTF-8"); + } catch (FileNotFoundException e) { + return ""; + } catch (IOException e) { + throw new RuntimeException(e); + } + } + +} diff --git a/st-tdg-test/src/test/java/test4giis/tdrules/gestaohospital/BaseGestaoHospital.java b/st-tdg-test/src/test/java/test4giis/tdrules/gestaohospital/BaseGestaoHospital.java new file mode 100644 index 0000000..9095ccf --- /dev/null +++ b/st-tdg-test/src/test/java/test4giis/tdrules/gestaohospital/BaseGestaoHospital.java @@ -0,0 +1,63 @@ +package test4giis.tdrules.gestaohospital; + +import giis.tdrules.client.oa.OaSchemaApi; +import giis.tdrules.client.oa.OaSchemaIdResolver; +import giis.tdrules.openapi.model.TdSchema; +import giis.tdrules.store.loader.IAttrGen; +import giis.tdrules.store.loader.gen.DictionaryAttrGen; +import test4giis.tdrules.BaseAll; + +public class BaseGestaoHospital extends BaseAll{ + + protected static final String GESTAOHOSPITAL_SCHEMA_LOCAL = "swagger-gestaoHospital-fork/src/main/resources/gestaohospital-rest.json"; + protected static final String GESTAOHOSPITAL_SCHEMA_LIVE = "http://localhost:8085/v2/api-docs"; + private static final String GESTAOHOSPITAL_URL_LIVE = "http://localhost:8085"; + + @Override + protected String getSutName() { + return "gestaoHospital"; + } + + @Override + protected String getServerUrl() { + return GESTAOHOSPITAL_URL_LIVE; + } + + @Override + protected String getAllDataLiveEndpoint() { + return GESTAOHOSPITAL_URL_LIVE + "/v1/hospitais/test/getAll"; + } + + @Override + protected String getDeleteAllDataLiveEndpoint() { + return GESTAOHOSPITAL_URL_LIVE + "/v1/hospitais/test/deleteAll"; + } + + @Override + protected TdSchema getSchema() { + OaSchemaApi api = new OaSchemaApi(GESTAOHOSPITAL_SCHEMA_LOCAL) + .setIdResolver(new OaSchemaIdResolver().setIdName("id")); + return api.getSchema(); + } + + /** + * Instancia un generador de datos configurado con un diccionario para que los datos + * generados no sean solo numeros, sino valores procedentes de un diccionario o mascaras + */ + protected IAttrGen getDictionaryAttrGen() { + return new DictionaryAttrGen() + //https://forbes.es/forbes-panel/121316/los-20-mejores-hospitales-del-mundo/ + .with("HospitalDTO", "name").dictionary("Mayo Clinic", "John Hopkins Hospital", "Singapore General Hospital", "Karolinska University Hospital", "Cleveland Clinic") + .with("HospitalDTO", "address").dictionary("2nd Avenue Southwest, Rochester, Olmsted County, Minnesota, 55902, USA", // 44.0181799, -92.4659151 + "5th Avenue South, Downtown, Saint Petersburg, Pinellas County, Florida, 33701, USA", //27.7657,-82.6385709 + "Everton Road, Bukit Merah, Singapur, Central Region, 088860, Singapore", //1.276579,103.837679 + "Maria Aspmans gata, Solna Kyrkby, Solna kommun, Provincia de Estocolmo, 113 66, Sweden", //59.3506023,18.0363573 + "Cleveland Clinic, 9500, Euclid Avenue, Fairfax, Cleveland, Cuyahoga County, Ohio, 44106, USA") //41.50217195,-81.61981946090904 + .with("ProductDTO", "name").dictionary("Rice","Blood A+","Blood A-","Milk","Bread","Blood B+","Meat","Blood B-") + .with("ProductDTO", "productName").dictionary("Rice","Blood A+","Blood A-","Milk","Bread","Blood B+","Meat","Blood B-") + .with("ProductDTO", "description").dictionary ("Food","Blood Bank","Blood Bank","Food","Food","Blood Bank","Food","Blood Bank") + ; + } + +} + diff --git a/st-tdg-test/src/test/java/test4giis/tdrules/gestaohospital/TestGestaoHospitalDatagenLocal.java b/st-tdg-test/src/test/java/test4giis/tdrules/gestaohospital/TestGestaoHospitalDatagenLocal.java new file mode 100644 index 0000000..e0d2757 --- /dev/null +++ b/st-tdg-test/src/test/java/test4giis/tdrules/gestaohospital/TestGestaoHospitalDatagenLocal.java @@ -0,0 +1,78 @@ +package test4giis.tdrules.gestaohospital; + +import org.junit.Test; + +import giis.tdrules.model.io.TdRulesXmlSerializer; +import giis.tdrules.openapi.model.TdRules; +import giis.tdrules.store.loader.DataLoader; + +/** + * Generación de datos para gestaoHospital utilizando Datagen + * Utiliza un un esquema y un DataAdapter local, que no requiere una conexion activa a un servidor. + * + */ +public class TestGestaoHospitalDatagenLocal extends BaseGestaoHospital { + + /** + * Una query simple con una unica tabla para la prueba inicial que comprueba que se genera correctamente + */ + public static String querySmoke = "select * from HospitalDTO where availableBeds=10"; + @Test + public void testSmoke() { + TdRules rules=getRules(querySmoke); + assertModel("rules-smoke.xml", new TdRulesXmlSerializer().serialize(rules)); + + //Las reglas buscan una Hospital con availableBeds=10 y otro que no + DataLoader dg = getDataLoader(); + dg.load("HospitalDTO","availableBeds=10"); + dg.load("HospitalDTO","availableBeds=1"); + assertData("datagen-local-smoke.txt", dg); + } + + /** + * Una query que busca Productos de tipo "COMMON" que tenga disponibilidad : + * TDS ProductDTO where ProductDTO.productType='COMMON' and ProductDTO.quantity>0 + */ + public static String queryProductByProductTypeAndQuantity = + "tds ProductDTO where ProductDTO.productType='COMMON' and ProductDTO.quantity>0"; + @Test + public void testProductbyProductTypeAndQuantity() { + TdRules rules = getRules(queryProductByProductTypeAndQuantity); + assertModel("rules-product-by-producttype-and-quantity.xml", new TdRulesXmlSerializer().serialize(rules)); + + DataLoader dg = getDataLoader(); + //Una fila ProductType=COMMON y quantity >0 + dg.load("ProductDTO","productType=COMMON, quantity=1"); + //dos filas en las que falla la igualdad en cada una de estas propiedades + dg.load("ProductDTO","productType=BLOOD, quantity=1"); + dg.load("ProductDTO","productType=COMMON, quantity=0"); + + assertData("datagen-local-product-by-productype-quantity.txt", dg); + } + + /** + * Una query similar a la anterior buscando Productos de tipo "COMMON" que tenga disponibilidad + * pero que estén en algún Hospital (join relacional de dos tablas) + * TDS ProductDTO, HospitalDTO where ProductDTO.productType='COMMON' and ProductDTO.quantity>0 + */ + public static String queryHospitalProductByProductTypeAndQuantity = + "tds ProductDTO,HospitalDTO where ProductDTO.productType='COMMON' and ProductDTO.quantity>0"; + @Test + public void testHospitalProductbyProductTypeAndQuantity() { + TdRules rules = getRules(queryHospitalProductByProductTypeAndQuantity); + assertModel("rules-hospital-product-by-producttype-and-quantity.xml", new TdRulesXmlSerializer().serialize(rules)); + + DataLoader dg = getDataLoader(); + //La primera regla debe generar un maestro HospitalDTO con Productos + //que cumplan la decisión del where + dg.load("HospitalDTO","id=@hid1"); + dg.load("ProductDTO","id=@pid1,hospitalDTOId=@hid1 ,productType=COMMON, quantity=1"); + //Reutiliza el mismo hospital pero con productos que no cumplen las condiciones + dg.load("ProductDTO","id=@pid2,hospitalDTOId=@hid1,productType=BLOOD,quantity=1"); + dg.load("ProductDTO","id=@pid3,hospitalDTOId=@hid1,productType=COMMON,quantity=0"); + //Un hospital sin productos + dg.load("HospitalDTO","id=@hid2"); + + assertData("datagen-local-hospital-product-by-productype-quantity.txt", dg); + } +} diff --git a/st-tdg-test/src/test/java/test4giis/tdrules/gestaohospital/TestGestaoHospitalQagrowLocal.java b/st-tdg-test/src/test/java/test4giis/tdrules/gestaohospital/TestGestaoHospitalQagrowLocal.java new file mode 100644 index 0000000..894c9bc --- /dev/null +++ b/st-tdg-test/src/test/java/test4giis/tdrules/gestaohospital/TestGestaoHospitalQagrowLocal.java @@ -0,0 +1,36 @@ +package test4giis.tdrules.gestaohospital; + +import org.junit.Test; + +import giis.tdrules.store.loader.DataLoader; + +/** + * Generación de datos para gestaoHospital utilizando QAGrow + * Mismos test que TestGestaoHospitalDatagenLocal, + * generando los datos de prueba en un archivo utilizando QAGrow. + * Utiliza un un esquema y un DataAdapter local, que no requiere una conexion activa a un servidor. + * + */ +public class TestGestaoHospitalQagrowLocal extends BaseGestaoHospital { + + @Test + public void testSmoke() { + DataLoader dg = getDataLoader(); + generateAndLoad(dg, TestGestaoHospitalDatagenLocal.querySmoke); + assertData("qagrow-local-smoke.txt", dg); + } + + @Test + public void testProductbyProductTypeAndQuantity() { + DataLoader dg = getDataLoader(); + generateAndLoad(dg, TestGestaoHospitalDatagenLocal.queryProductByProductTypeAndQuantity); + assertData("qagrow-local-product-by-productype-quantity.txt", dg); + } + + @Test + public void testHospitalProductbyProductTypeAndQuantity() { + DataLoader dg = getDataLoader(); + generateAndLoad(dg, TestGestaoHospitalDatagenLocal.queryHospitalProductByProductTypeAndQuantity); + assertData("qagrow-local-hospital-product-by-productype-quantity.txt", dg); + } +} diff --git a/st-tdg-test/src/test/java/test4giis/tdrules/gestaohospital/TestGestaoHospitalSchemaLocal.java b/st-tdg-test/src/test/java/test4giis/tdrules/gestaohospital/TestGestaoHospitalSchemaLocal.java new file mode 100644 index 0000000..4244e81 --- /dev/null +++ b/st-tdg-test/src/test/java/test4giis/tdrules/gestaohospital/TestGestaoHospitalSchemaLocal.java @@ -0,0 +1,33 @@ +package test4giis.tdrules.gestaohospital; + +import org.junit.Test; + +import com.fasterxml.jackson.core.JsonProcessingException; + +import giis.tdrules.client.oa.MermaidWriter; +import giis.tdrules.model.io.TdSchemaXmlSerializer; +import giis.tdrules.openapi.model.TdSchema; + +/** + * Genera el esquema a partir de la especificacion (leida de archivo) y lo comprueba en formato json y xml + */ +public class TestGestaoHospitalSchemaLocal extends BaseGestaoHospital { + + @Test + public void testSchemaLocalJson() throws JsonProcessingException { + TdSchema schema = getSchema(); + assertModel("schema-hospital.json", serialize(schema)); + } + + @Test + public void testSchemaLocalXml () { + TdSchema schema = getSchema(); + assertModel("schema-hospital.xml", new TdSchemaXmlSerializer().serialize(schema)); + } + + @Test + public void testSchemaLocalMermaid () { + String mermaid = new MermaidWriter(getSchema()).getMermaid(); + assertModel("schema-hospital.md", mermaid); + } +} diff --git a/st-tdg-test/src/test/java/test4giis/tdrules/gestaohospital/TestGestoHospitalQAGrowLive.java b/st-tdg-test/src/test/java/test4giis/tdrules/gestaohospital/TestGestoHospitalQAGrowLive.java new file mode 100644 index 0000000..3fc6b82 --- /dev/null +++ b/st-tdg-test/src/test/java/test4giis/tdrules/gestaohospital/TestGestoHospitalQAGrowLive.java @@ -0,0 +1,44 @@ +package test4giis.tdrules.gestaohospital; + +import org.junit.Test; + +import giis.tdrules.store.loader.DataLoader; + +public class TestGestoHospitalQAGrowLive extends BaseGestaoHospital { + + @Override + protected boolean isLiveBackend() { + return true; + } + + //Una query sencilla sobre hospital para prueba inicial de generación live + //con datos representativos de hospitales tomados de un diccionario + @Test + public void testSmokewithDict() { + String query = "tds HospitalDTO where HospitalDTO.availableBeds=10"; + DataLoader dg = getDataLoader().setAttrGen(getDictionaryAttrGen()); + generateAndLoad(dg, query); + assertData("qagrow-live-smoke-dict.txt", dg); + } + + //Una query sobre los objetos de Hospital y Productos para comprobar que guarda hospitales y productos asociados + //Lista de productos que tienen existencias (estoque) + @Test + public void testAvalProductsbyHospital () { + String query = "tds HospitalDTO, ProductDTO where ProductDTO.quantity>0"; + DataLoader dg = getDataLoader().setAttrGen(getDictionaryAttrGen()); + generateAndLoad(dg, query); + assertData("qagrow-live-aval-products-by-hospital.txt", dg); + } + + //Otra sobre Hospital y Productos + //Lista de productos sin existencias en hospitales con camas disponibles + @Test + public void testUnavProductsbyHospital () { + String query = "tds HospitalDTO, ProductDTO where HospitalDTO.availableBeds>0 and ProductDTO.quantity=0"; + DataLoader dg = getDataLoader().setAttrGen(getDictionaryAttrGen()); + generateAndLoad(dg, query); + assertData("qagrow-live-unav-products-by-hospital.txt", dg); + } + +} diff --git a/st-tdg-test/src/test/java/test4giis/tdrules/market/BaseMarket.java b/st-tdg-test/src/test/java/test4giis/tdrules/market/BaseMarket.java new file mode 100644 index 0000000..932c4b2 --- /dev/null +++ b/st-tdg-test/src/test/java/test4giis/tdrules/market/BaseMarket.java @@ -0,0 +1,182 @@ +package test4giis.tdrules.market; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ObjectNode; + +import giis.tdrules.client.oa.OaSchemaApi; +import giis.tdrules.client.oa.OaSchemaIdResolver; +import giis.tdrules.client.oa.OaSchemaFilter; +import giis.tdrules.openapi.model.TdSchema; +import giis.tdrules.store.loader.DataLoader; +import giis.tdrules.store.loader.IAttrGen; +import giis.tdrules.store.loader.gen.DictionaryAttrGen; +import giis.tdrules.store.loader.oa.IPathResolver; +import giis.tdrules.store.loader.oa.OaBasicAuthStore; +import giis.tdrules.store.loader.oa.OaLiveAdapter; +import giis.tdrules.store.loader.oa.OaLiveUidGen; +import giis.tdrules.store.loader.oa.OaPathResolver; +import test4giis.tdrules.BaseAll; + +public class BaseMarket extends BaseAll { + protected static final String MARKET_SCHEMA_LOCAL = "swagger-market-fork/src/main/resources/marketWithoutArrays.json"; + private static final String MARKET_URL_LIVE = "http://localhost:8083"; + + private static final String[] FILTERED_ATTRS = {"password", "dateCreated","number"}; + + public static String queryProductByAge = "tds ProductDTORes where age=10"; + public static String queryUserByName = "tds UserDTORes where name='Pepe'"; + public static String queryUserByEmail = "tds UserDTORes where email ='pepe@email.com'"; + public static String queryCartByUserProductQuantity = "tds CartDTO,CartItemDTORes,ProductDTORes where CartDTO.user='pepe@email.com' and CartItemDTORes.productId=1 and CartItemDTORes.quantity=5 and ProductDTORes.available=1"; + + @Override + protected String getSutName() { + return "market"; + } + + @Override + protected String getServerUrl() { + return MARKET_URL_LIVE; + } + + @Override + protected String getAllDataLiveEndpoint() { + return MARKET_URL_LIVE + "/test/getAll"; + } + + @Override + protected String getDeleteAllDataLiveEndpoint() { + return MARKET_URL_LIVE + "/test/deleteAll"; + } + + @Override + protected TdSchema getSchema() { + // Configure: + // filter entities Link* and attributes _link*, and + // the schema id resolver to use id attributes as uid and + // not to use productId as product.id in entities CartItem and ProductDTO + OaSchemaApi api = new OaSchemaApi(MARKET_SCHEMA_LOCAL) + .setFilter(new OaSchemaFilter() + .add("*", "_link*") + .add("Link*", "*")) + .setIdResolver(new OaSchemaIdResolver().setIdName("id") + .excludeEntity("CartItemDTOReq") + .excludeEntity("CartItemDTORes") + .excludeEntity("ProductDTORes") + .excludeEntity("ProductDTOReq")) + ; + return api.getSchema(); + } + + /** + * Instancia un generador utilizando un Adaptador para Openapi que genera los datos directamente a traves del api + * El path resolver se configura con la url donde extraer los paths de los endpoints de LiveBackId. + * La generación de las claves se realiza en el backend (UidGen), las columnas (AttrGen) se generan de forma determinista. + * Se utliza diccionario para la generación de columnas (getDictionaryAttrGen) + */ + @Override + protected DataLoader getLiveDataLoader() { + TdSchema model = getSchema(); + IPathResolver pathResolver=new CustomPathResolver().setServerUrl(MARKET_URL_LIVE); + OaBasicAuthStore authenticator = new OaBasicAuthStore() + .setProvider("UserDTOReq", "email", "password") + .addConsumer(new String[] { "CartItemDTORes", "CartItemDTOReq", + "ContactsDTORes", "ContactsDTOReq" }, "user") + .addConsumer(new String[] { "OrderDTO"} , "userAccount"); + + return new DataLoader(model, new OaLiveAdapter(pathResolver).setAuthStore(authenticator)) + .setUidGen(new OaLiveUidGen()) + .setAttrGen(getDictionaryAttrGen()); + } + + /** + * Instancia un generador de datos configurado con un diccionario para que los datos + * generados no sean solo numeros, sino valores procedentes de un diccionario o mascaras + * Para tarjetas de crédito: https://dev.na.bambora.com/docs/references/payment_APIs/test_cards/ + */ + protected IAttrGen getDictionaryAttrGen() { + return new DictionaryAttrGen() + .with("UserDTORes", "email").padLeft('0', 2).mask("us{}@email.com") + .with("UserDTOReq", "email").padLeft('0', 2).mask("us{}@email.com") + .with("UserDTORes", "name").dictionary("Lucia","Sofia","Martina","Maria", "Jose","Juan","Luis","Antonio","Mateo") + .with("UserDTOReq", "name").dictionary("Lucia","Sofia","Martina","Maria", "Jose","Juan","Luis","Antonio","Mateo") + .with("UserDTORes", "password").dictionary("123456","1234567","12345678","123456789", "abcdef","abcdefg","abcdefgh","abcdefghi","abcdefghij") + .with("UserDTOReq", "password").dictionary("123456","1234567","12345678","123456789", "abcdef","abcdefg","abcdefgh","abcdefghi","abcdefghij") + .with("UserDTORes", "phone").dictionary("+12123456789","+12112345678","+13122334568","+14123345566", "+15123344557","+16123345668","+17123456777","+18123346778","+19129346779") + .with("UserDTOReq", "phone").dictionary("+12123456789","+12112345678","+13122334568","+14123345566", "+15123344557","+16123345668","+17123456777","+18123346778","+19129346779") + .with("DistilleryDTORes", "title").dictionary("Ardbeg", "Balvenie", "Caol Ila", "Dalwhinnie", "Glenkinchie", "Lagavulin", "Laphroaig", "Springbank", "Talisker") + .with("DistilleryDTOReq", "title").dictionary("Ardbeg", "Balvenie", "Caol Ila", "Dalwhinnie", "Glenkinchie", "Lagavulin", "Laphroaig", "Springbank", "Talisker") + .with("RegionDTORes", "name").dictionary("Campbeltown", "Highland", "Island", "Islay", "Lowland", "Speyside") + .with("RegionDTOReq", "name").dictionary("Campbeltown", "Highland", "Island", "Islay", "Lowland", "Speyside") + .with("CreditCardDTO","ccNumber").dictionary("4030000010001234","5100000010001004","2223000048400011","371100001000131","6011500080009080") + ; + } + + // Overrides the default implementation to filter unwanted attributes that shouldn't participate in comparisons + @Override + protected void assertLiveData(String fileName, DataLoader dg) { + String dataLive = getAllLiveData(); + log.info("Actual data stored in the backend\n{}", reserializeStoredData(dataLive)); + // antes de comparar, se deben filtrar los atributos que no se quieren comparar + super.assertModel(fileName, filterAttributes(dataLive,FILTERED_ATTRS)); + } + + private String filterAttributes(String strJson, String... ignoreAttributes) { + ObjectMapper mapper = new ObjectMapper().configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); + StringBuilder sb = new StringBuilder(); + + try { + JsonNode tables = mapper.readTree(strJson); + for (String attr : ignoreAttributes) { + removeAttribute(tables, attr); + } + sb.append(tables.toString()); + } catch (JsonProcessingException e) { + throw new RuntimeException(e); + } + + return reserializeStoredData(sb.toString()); + } + + // actualiza json eliminando attributeName y su valor en todas las apariciones + private void removeAttribute(JsonNode node, String attributeName) { + if (node.isObject()) { + ObjectNode objectNode = (ObjectNode) node; + objectNode.remove(attributeName); + objectNode.fields().forEachRemaining(entry -> removeAttribute(entry.getValue(), attributeName)); + } else if (node.isArray()) { + node.forEach(childNode -> removeAttribute(childNode, attributeName)); + } + } + + //los endpoints estan bajo el path backid + public class CustomPathResolver extends OaPathResolver { + @Override + public String getEndpointPath(String tableName) { + //Eliminacion de Req o Res en las llamadas a los endpoints + String table = tableName.split("Re(s|q)")[0]; + if ("CartDTO".equals(table)) + return null; + else if ("CreditCardDTO".equals(table)) + return null; + else if ("ProductDTO".equals(table)) + return super.getEndpointPath("products/" + table); + else if ("UserDTO".equals(table)) + return super.getEndpointPath("register"); + else if ("CartItemDTO".equals(table)) + return super.getEndpointPath("customer/cart"); + else if ("OrderDTO".equals(table)) + return super.getEndpointPath("customer/cart/pay"); + else + return super.getEndpointPath(table); + } + + @Override public boolean usePut(String tableName) { + String table = tableName.split("Re(s|q)")[0]; + return "CartItemDTO".equals(table); + } + } + +} diff --git a/st-tdg-test/src/test/java/test4giis/tdrules/market/TestMarketFuncCarts.java b/st-tdg-test/src/test/java/test4giis/tdrules/market/TestMarketFuncCarts.java new file mode 100644 index 0000000..a706381 --- /dev/null +++ b/st-tdg-test/src/test/java/test4giis/tdrules/market/TestMarketFuncCarts.java @@ -0,0 +1,71 @@ +package test4giis.tdrules.market; + +import org.junit.Test; + +import giis.tdrules.store.loader.DataLoader; +import giis.tdrules.store.loader.IAttrGen; + +/** + * Tests de funcionalidad de Carts + * Generación: QAGrow y claves se realiza en el backend + */ +public class TestMarketFuncCarts extends BaseMarket { + + @Override + protected boolean isLiveBackend() { + return true; + } + + @Test + public void testDictUserDTOResByName() { + String query = queryUserByName; + IAttrGen dict=getDictionaryAttrGen(); + DataLoader dg = getLiveDataLoader().setAttrGen(dict); + generateAndLoad(dg, query, dict); + assertData("func-UsersByName.txt", dg); + } + + /* + * Inserta carts pero no items ni productos. Para cubrir las reglas de cobertura no son necesarios + * Son carts vacíos. + */ + @Test + public void testDictCartDTOByUser() { + String query = "tds CartDTO where user='lucia@email.com'"; + IAttrGen dict=getDictionaryAttrGen(); + DataLoader dg = getLiveDataLoader().setAttrGen(dict); + generateAndLoad(dg, query, dict); + assertData("func-CartsByUser.txt", dg); + } + + /* Inserta carts y sus items. + * Para que se inserte un producto en un carrito, debe estar disponible, necesario incluirlo en la query. + * Si no se indica en la query, se genera por DataGenerator y es no disponible (available = false), + * entonces al intentar añadirlo via api, no se inserta en la tabla (lo que es correcto) + * Hay otra regla de cobertura que no se cubre con una única base de datos: + * - user='lucia...' and productId=1 and quantity != 5 + * En este caso, desde QAGrow ya no se generan los datos. + */ + @Test + public void testDictCartDTOByUserProductQuantity() { + String query = queryCartByUserProductQuantity; + IAttrGen dict=getDictionaryAttrGen(); + DataLoader dg = getLiveDataLoader().setAttrGen(dict); + generateAndLoad(dg, query, dict); + assertData("func-CartsByUserProductQuantity.txt", dg); + } + + /* Inserta order a un usuario. + * Para que cree una order, el carrito no puede estar vacio. + * Por tanto, habrá una query1 para generar carritos con productos y query2 para generar la orden del carrito + */ + @Test + public void testOrderDTOByUser() { + String query1= queryCartByUserProductQuantity; + String query2 = "tds OrderDTO where userAccount='pepe@email.com' "; + IAttrGen dict=getDictionaryAttrGen(); + DataLoader dg = getLiveDataLoader().setAttrGen(dict); + generateAndLoad(dg, new String[] {query1, query2}, dict); + assertData("func-OrderByUser.txt", dg); + } +} diff --git a/st-tdg-test/src/test/java/test4giis/tdrules/market/TestMarketFuncProducts.java b/st-tdg-test/src/test/java/test4giis/tdrules/market/TestMarketFuncProducts.java new file mode 100644 index 0000000..b57ee74 --- /dev/null +++ b/st-tdg-test/src/test/java/test4giis/tdrules/market/TestMarketFuncProducts.java @@ -0,0 +1,104 @@ +package test4giis.tdrules.market; + +import org.junit.Test; +import giis.tdrules.store.loader.DataLoader; +import giis.tdrules.store.loader.IAttrGen; + +/** + * Tests de funcionalidad de Products + * Generación: QAGrow y claves se realiza en el backend + */ +public class TestMarketFuncProducts extends BaseMarket { + + @Override + protected boolean isLiveBackend() { + return true; + } + + /** + * Todos los productos de una destileria + */ + @Test + public void testProductsByDistillery() { + String query = "tds ProductDTOReq where distillery ='Ardbeg'"; + IAttrGen dict=getDictionaryAttrGen(); + DataLoader dg = getLiveDataLoader().setAttrGen(dict); + generateAndLoad(dg, query, dict); + assertData("func-ProductsByDistillery.txt", dg); + } + + + /** + * Productos de una destileria disponibles + */ + @Test + public void testProductsByDistilleryAvaliable() { + String query = "tds ProductDTOReq where distillery ='Ardbeg' and available = 1"; + IAttrGen dict=getDictionaryAttrGen(); + DataLoader dg = getLiveDataLoader().setAttrGen(dict); + generateAndLoad(dg, query, dict); + assertData("func-ProductsByDistilleryAvailable.txt", dg); + } + + /** + * Todos los productos de una destileria no disponibles + */ + @Test + public void testProductsByDistilleryNotAvailable() { + String query = "tds ProductDTOReq where distillery ='Ardbeg' and available = 0"; + IAttrGen dict=getDictionaryAttrGen(); + DataLoader dg = getLiveDataLoader().setAttrGen(dict); + generateAndLoad(dg, query, dict); + assertData("func-ProductsByDistilleryNotAvailable.txt", dg); + } + + /** + * Productos de una destileria disponibles entre dos precios + */ + @Test + public void testProductsByDistilleryPrice() { + String query = "tds ProductDTOReq where distillery ='Ardbeg' and price < 100 and price > 5"; + IAttrGen dict=getDictionaryAttrGen(); + DataLoader dg = getLiveDataLoader().setAttrGen(dict); + generateAndLoad(dg, query, dict); + assertData("func-ProductsByDistilleryPrice.txt", dg); + } + + /** + * Productos de una destileria disponibles entre dos precios + */ + @Test + public void testProductsByDistilleryPriceAge() { + String query = "tds ProductDTOReq where distillery ='Ardbeg' and price < 100 and price > 5 and age = 12"; + IAttrGen dict=getDictionaryAttrGen(); + DataLoader dg = getLiveDataLoader().setAttrGen(dict); + generateAndLoad(dg, query, dict); + assertData("func-ProductsByDistilleryPriceAge.txt", dg); + } + + + /** + * Prueba de generacion de distillery y region con claves en el backend, fijando distillery.title + */ + @Test + public void testDistilleryByTitle() { + String query = "tds DistilleryDTOReq where title ='Ardbeg'"; + IAttrGen dict=getDictionaryAttrGen(); + DataLoader dg = getLiveDataLoader().setAttrGen(dict); + generateAndLoad(dg, query, dict); + assertData("func-DistilleryByTitle.txt", dg); + } + + /** + * Prueba de generacion de distillery y region con claves en el backend, fijando distillery.region (region es maestro de distillery) + */ + @Test + public void testDistilleryByRegion() { + String query = "tds DistilleryDTOReq where region ='Islay'"; + IAttrGen dict=getDictionaryAttrGen(); + DataLoader dg = getLiveDataLoader().setAttrGen(dict); + generateAndLoad(dg, query, dict); + assertData("func-DistilleryByRegion.txt", dg); + } + +} diff --git a/st-tdg-test/src/test/java/test4giis/tdrules/market/TestMarketQagrowLiveBackId.java b/st-tdg-test/src/test/java/test4giis/tdrules/market/TestMarketQagrowLiveBackId.java new file mode 100644 index 0000000..4a0a8b9 --- /dev/null +++ b/st-tdg-test/src/test/java/test4giis/tdrules/market/TestMarketQagrowLiveBackId.java @@ -0,0 +1,54 @@ +package test4giis.tdrules.market; + +import org.junit.Test; +import giis.tdrules.store.loader.DataLoader; +import giis.tdrules.store.loader.IAttrGen; + +/** + * Tests para generacion de datos con QAGrow + * Las generacion de claves se realiza en el backend + */ +public class TestMarketQagrowLiveBackId extends BaseMarket { + + @Override + protected boolean isLiveBackend() { + return true; + } + + /** + * Prueba de generacion de regiones con claves en el backend + */ + @Test + public void testRegionByName() { + String query = "tds RegionDTOReq where name ='Campbeltown'"; + IAttrGen dict=getDictionaryAttrGen(); + DataLoader dg = getLiveDataLoader().setAttrGen(dict); + generateAndLoad(dg, query, dict); + assertData("qagrow-livebackid-RegionByName.txt", dg); + } + + /** + * Prueba de generacion de distillery y region con claves en el backend, fijando distillery.title + */ + @Test + public void testDistilleryByTitle() { + String query = "tds DistilleryDTOReq where title ='Balvenie'"; + IAttrGen dict=getDictionaryAttrGen(); + DataLoader dg = getLiveDataLoader().setAttrGen(dict); + generateAndLoad(dg, query, dict); + assertData("qagrow-livebackid-DistilleryByTitle.txt", dg); + } + + /** + * Prueba de generacion de distillery y region con claves en el backend, fijando distillery.region (region es maestro de distillery) + */ + @Test + public void testDistilleryByRegion() { + String query = "tds DistilleryDTOReq where region ='Highland'"; + IAttrGen dict=getDictionaryAttrGen(); + DataLoader dg = getLiveDataLoader().setAttrGen(dict); + generateAndLoad(dg, query, dict); + assertData("qagrow-livebackid-DistilleryByRegion.txt", dg); + } + +} diff --git a/st-tdg-test/src/test/java/test4giis/tdrules/market/TestMarketQagrowLocal.java b/st-tdg-test/src/test/java/test4giis/tdrules/market/TestMarketQagrowLocal.java new file mode 100644 index 0000000..5694b75 --- /dev/null +++ b/st-tdg-test/src/test/java/test4giis/tdrules/market/TestMarketQagrowLocal.java @@ -0,0 +1,33 @@ +package test4giis.tdrules.market; + +import org.junit.Test; + +import giis.tdrules.store.loader.DataLoader; + +/** + * Generacion de datos para Market utilizando QAGrow. + * Utiliza un un esquema y un DataAdapter local, que no requiere una conexion activa a un servidor. + */ +public class TestMarketQagrowLocal extends BaseMarket { + + @Test + public void testProduct() { + DataLoader dg = getDataLoader(); + generateAndLoad(dg, queryProductByAge); + assertData("qagrow-local-product.txt", dg); + } + + @Test + public void testUserByName() { + DataLoader dg = getDataLoader(); + generateAndLoad(dg, queryUserByName); + assertData("qagrow-local-userbyname.txt", dg); + } + + @Test + public void testUserByEmail() { + DataLoader dg = getDataLoader(); + generateAndLoad(dg, queryUserByEmail); + assertData("qagrow-local-userbyemail.txt", dg); + } +} diff --git a/st-tdg-test/src/test/java/test4giis/tdrules/market/TestMarketSchemaLocal.java b/st-tdg-test/src/test/java/test4giis/tdrules/market/TestMarketSchemaLocal.java new file mode 100644 index 0000000..4628fdd --- /dev/null +++ b/st-tdg-test/src/test/java/test4giis/tdrules/market/TestMarketSchemaLocal.java @@ -0,0 +1,34 @@ +package test4giis.tdrules.market; + +import org.junit.Test; + +import com.fasterxml.jackson.core.JsonProcessingException; + +import giis.tdrules.client.oa.MermaidWriter; +import giis.tdrules.model.io.TdSchemaXmlSerializer; +import giis.tdrules.openapi.model.TdSchema; + +/** + * Genera el esquema a partir de la especificacion (leida de archivo) y lo comprueba en formato json y xml + */ +public class TestMarketSchemaLocal extends BaseMarket { + + @Test + public void testSchemaLocalJson() throws JsonProcessingException { + TdSchema schema = getSchema(); + assertModel("schema-marketWithoutArrays.json", serialize(schema)); + } + + @Test + public void testSchemaLocalXml () { + TdSchema schema = getSchema(); + assertModel("schema-marketWithoutArrays.xml", new TdSchemaXmlSerializer().serialize(schema)); + } + + @Test + public void testSchemaLocalMermaid () { + TdSchema schema = getSchema(); + String mermaid = new MermaidWriter(schema).getMermaid(); + assertModel("schema-marketWithoutArrays.md", mermaid); + } +} diff --git a/st-tdg-test/src/test/java/test4giis/tdrules/petstore/BasePetstore.java b/st-tdg-test/src/test/java/test4giis/tdrules/petstore/BasePetstore.java new file mode 100644 index 0000000..894a054 --- /dev/null +++ b/st-tdg-test/src/test/java/test4giis/tdrules/petstore/BasePetstore.java @@ -0,0 +1,71 @@ +package test4giis.tdrules.petstore; + +import giis.tdrules.client.oa.OaSchemaApi; +import giis.tdrules.client.oa.OaSchemaIdResolver; +import giis.tdrules.openapi.model.TdSchema; +import giis.tdrules.store.loader.IAttrGen; +import giis.tdrules.store.loader.gen.DictionaryAttrGen; +import test4giis.tdrules.BaseAll; + +public class BasePetstore extends BaseAll{ + //Para la generacion "live" arrancar antes el container de petstore con docker-run (asegurar tener el puerto 8081 libre) + protected static final String PETSTORE_SCHEMA_LOCAL = "swagger-petstore-main-fork/src/main/resources/openapi.yaml"; + protected static final String PETSTORE_SCHEMA_LIVE = "http://localhost:8081/api/v3/openapi.json"; + private static final String PETSTORE_URL_LIVE = "http://localhost:8081/api/v3"; + + @Override + protected String getSutName() { + return "petstore"; + } + + @Override + protected String getServerUrl() { + return PETSTORE_URL_LIVE; + } + + @Override + protected String getAllDataLiveEndpoint() { + return PETSTORE_URL_LIVE + "/test/getAll"; + } + + @Override + protected String getDeleteAllDataLiveEndpoint() { + return PETSTORE_URL_LIVE + "/test/deleteAll"; + } + + @Override + protected TdSchema getSchema() { + // Configure the schema id resolver to use id attribute as uid, but there are exceptions: + // - Tag has an id, but looking at the source code, a post inserts unconditionally, + // allowing repeated id values. Considers this id as no uid + // - Order0 has been artificially created for some tests, it does not follow strictely + // the conventions (attribute petId references Pet0.id) + OaSchemaApi api = new OaSchemaApi(PETSTORE_SCHEMA_LOCAL) + .setIdResolver(new OaSchemaIdResolver().setIdName("id") + .excludeEntity("Tag").excludeEntity("Order0")); + return api.getSchema(); + } + + /** + * Instancia un generador de datos configurado con un diccionario para que los datos + * generados no sean solo numeros, sino valores procedentes de un diccionario o mascaras + */ + protected IAttrGen getDictionaryAttrGen() { + return new DictionaryAttrGen() + //https://www.southernliving.com/most-popular-pet-names-rover-6829769 + .with("Pet", "name").dictionary("Max", "Luna", "Charlie", "Bella", "Cooper", "Daisy", "Milo", "Lucy") + .with("Pet_photoUrls_xa", "photoUrls").padLeft('0', 6).mask("http://localhost/photos/{}.jpg") + .with("Pet_Tags_xa", "name").dictionary("Puppy", "Young", "Old") + .with("Category", "name").dictionary("Tiger", "Lion", "Monkey", "Snake") + .with("Customer_address_xa", "street").dictionary("Main St", "Broadway", "Park Ave", "Fulton St", "Madison Ave", "Pine St", "Amsterdam Ave", "Wall St") + //https://www.ssa.gov/oact/babynames/decades/century.html + //https://www.al.com/news/2019/10/50-most-common-last-names-in-america.html + .with("Customer", "username").dictionary("James Smith", "Mary Johnson", "Robert Williams", "Patricia Brown", "David Garcia", "Elizabeth Miller", "William Davis", "Barbara Wilson") + //https://www.worldatlas.com/articles/most-common-town-and-city-names-in-the-u-s-a.html#:~:text=Washington,this%20way%20is%20no%20surprise. + .with("Customer_address_xa", "city").dictionary("Springfield", "Franklin", "Greenville", "Bristol", "Clinton", "Fairview", "Salem", "Madison") + .with("Customer_address_xa", "state").dictionary("California", "Texas", "Florida", "New York", "Pennsylvania", "Illinois", "Ohio", "Georgia") + .with("Customer_address_xa", "zip").padLeft('0', 6) + ; + } + +} diff --git a/st-tdg-test/src/test/java/test4giis/tdrules/petstore/TestPetstoreDatagenLive.java b/st-tdg-test/src/test/java/test4giis/tdrules/petstore/TestPetstoreDatagenLive.java new file mode 100644 index 0000000..0fd6ce9 --- /dev/null +++ b/st-tdg-test/src/test/java/test4giis/tdrules/petstore/TestPetstoreDatagenLive.java @@ -0,0 +1,15 @@ +package test4giis.tdrules.petstore; + +/** + * Mismos test que TestPetstoreDatagenLocal, pero accediendo al servidor de swagger-petstore, + * heredan de la base salvo la forma de crear el generador y comparar los datos + * (asegurar que el container ha sido arrancado con docker-run) + */ +public class TestPetstoreDatagenLive extends TestPetstoreDatagenLocal { + + @Override + protected boolean isLiveBackend() { + return true; + } + +} diff --git a/st-tdg-test/src/test/java/test4giis/tdrules/petstore/TestPetstoreDatagenLiveBackId.java b/st-tdg-test/src/test/java/test4giis/tdrules/petstore/TestPetstoreDatagenLiveBackId.java new file mode 100644 index 0000000..fed4d18 --- /dev/null +++ b/st-tdg-test/src/test/java/test4giis/tdrules/petstore/TestPetstoreDatagenLiveBackId.java @@ -0,0 +1,71 @@ +package test4giis.tdrules.petstore; + +import org.junit.Test; + +import giis.tdrules.store.loader.DataLoader; +import giis.tdrules.store.loader.oa.IPathResolver; +import giis.tdrules.store.loader.oa.OaLiveAdapter; +import giis.tdrules.store.loader.oa.OaLiveUidGen; +import giis.tdrules.store.loader.oa.OaPathResolver; + +/** + * Tests adicionales para comprobar la generacion de claves en el backend + * extendiendo algunas funcionalidades del petstore para que generen valores de estas + */ +public class TestPetstoreDatagenLiveBackId extends BasePetstore { + + @Override + protected boolean isLiveBackend() { + return true; + } + + //los endpoints estan bajo el path backid + //no se pueden resolver utilizando el resolver estandar que utiliza el modelo + public class CustomPathResolver extends OaPathResolver { + @Override + public String getEndpointPath(String tableName) { + if ("Pet".equals(tableName) || "Category".equals(tableName)) + return super.getEndpointPath("backid/" + tableName); + else + return super.getEndpointPath(tableName); + } + } + @Override + protected DataLoader getLiveDataLoader() { + IPathResolver pathResolver=new CustomPathResolver().setServerUrl(getServerUrl()); + return new DataLoader(getSchema(), new OaLiveAdapter(pathResolver)).setUidGen(new OaLiveUidGen()); + } + + /** + * Prueba de con dos entidades con referencias, no genera reglas, solo los comandos de generacion + * que se tienen en tests similares para comprobacion del valor de las claves generadas en el backend + */ + @Test + public void testPetByCategoryAndStatus() { + DataLoader dg=getDataLoader(); + dg.load("Category", "id=@cid1, name=Dogs"); + dg.load("Pet_Tags_xa", "fk_xa=@pid1"); + dg.load("Pet_photoUrls_xa", "fk_xa=@pid1"); + dg.load("Pet", "id=@pid1, category::id=@cid1, status=available"); + + dg.load("Pet_Tags_xa", "fk_xa=@pid2"); + dg.load("Pet_photoUrls_xa", "fk_xa=@pid2"); + dg.load("Pet", "id=@pid2, category::id=@cid1, status=sold");//sold es un valor !=available indicado por qagrow entre los permitidos + + dg.load("Category", "id=@cid2, name=1"); //1 es un valor !=Dogs indicado por qagrow + dg.load("Pet_Tags_xa", "fk_xa=@pid3"); + dg.load("Pet_photoUrls_xa", "fk_xa=@pid3"); + dg.load("Pet", "id=@pid3, category::id=@cid2, status=available"); + + dg.load("Category", "id=@cid3"); + + dg.load("Pet_photoUrls_xa", "fk_xa=@pid4"); + dg.load("Pet", "id=@pid4, category::id=@cid1, status=available"); + + dg.load("Pet_Tags_xa", "fk_xa=@pid5"); + dg.load("Pet", "id=@pid5, category::id=@cid1, status=available"); + + assertData("datagen-livebackid-pet-by-category-status.txt", dg); + } + +} diff --git a/st-tdg-test/src/test/java/test4giis/tdrules/petstore/TestPetstoreDatagenLocal.java b/st-tdg-test/src/test/java/test4giis/tdrules/petstore/TestPetstoreDatagenLocal.java new file mode 100644 index 0000000..6239fbc --- /dev/null +++ b/st-tdg-test/src/test/java/test4giis/tdrules/petstore/TestPetstoreDatagenLocal.java @@ -0,0 +1,366 @@ +package test4giis.tdrules.petstore; + +import org.junit.Test; + +import giis.tdrules.model.io.TdRulesXmlSerializer; +import giis.tdrules.openapi.model.TdRules; +import giis.tdrules.store.loader.DataLoader; + +/** + * Prueba de concepto de generacion de datos de prueba a partir de una especificacion openapi. + * + * El escenario inicial es estamos especificando un escenario de prueba en Gherkin para una aplicacion + * que hace un uso intensivo de datos. Para ello sera necesario especificar una serie de ejemplos + * que estan formados por diferentes conjuntos de datos que representen diferentes casuisticas + * (situaciones a probar). + * + * En vez de especificar estos datos se especificara una query en un lenguaje similar a sql + * (en realidad es una extension de sql). Esta query se denomina "Test Data Specification" (TDS) + * + * El modelo openapi sera trasformado en un modelo Test Data Model (TDM) + * que se implementa como un modelo DbSchema que permite generar + * reglas de cobertura fpc, las cuales determinaran las diferentes casuisticas a partir de esta especificacion. + * Posteriormente se generaran los datos que satisfagan esas reglas + * y se insertaran en la aplicacion utilizando su propia api. + * + * Esta clase genera las reglas e cobertura fpc y establece manualmente los comandos de generacion de datos para cubrirlas. + * Utiliza un un esquema y un DataAdapter local, que no requiere una conexion activa a un servidor. + */ +public class TestPetstoreDatagenLocal extends BasePetstore { + + /** + * Una query simple con una unica tabla para la prueba inicial que comprueba que se genera correctamente + */ + public static String querySmoke = "select * from Category where name='Dogs'"; + @Test + public void testSmoke() { + TdRules rules=getRules(querySmoke); + assertModel("rules-smoke.xml", new TdRulesXmlSerializer().serialize(rules)); + //Las reglas buscan una categoria que no sea Dogs y otra que si lo sea. + DataLoader dg = getDataLoader(); + dg.load("Category","name=Dogs"); + dg.load("Category",""); + assertData("datagen-local-smoke.txt", dg); + } + + /** + * Sobre una variante simplificada Pet0 sin arrays ni referencias externas + * Una query que busca Pets con categoria Dogs y que esten disponibles para la venta, la query es: + * + * TDS Pet0 where Pet0.category::name='Dogs' and Pet0.status='available' + * + * Utiliza la notacion TDS para las queries (especificaciones) + * El tipo de datos de la propiedad category no es primitivo, sino que esta definida inline como otro objeto. + * La notacion :: indica el acceso a las propiedades un objeto interno. + */ + public static String queryPet0ByCategoryAndStatus = + "tds Pet0 where Pet0.category::name='Dogs' and Pet0.status='available'"; + @Test + public void testPet0ByCategoryAndStatus() { + TdRules rules = getRules(queryPet0ByCategoryAndStatus); + assertModel("rules-pet0-by-category-status.xml", new TdRulesXmlSerializer().serialize(rules)); + + DataLoader dg = getDataLoader(); + //Las reglas category=Dogs y status=available + dg.load("Pet0","category::name=Dogs, status=available"); + //y luego otras dos filas en las que falla la igualdad en cada una de estas propiedades + dg.load("Pet0","category::name=1, status=available"); //1 es un valor !=Dogs indicado por qagrow + dg.load("Pet0","category::name=Dogs, status=sold"); //sold es un valor !=available indicado por qagrow entre los permitidos + assertData("datagen-local-pet0-by-category-status.txt", dg); + } + + /** + * Sobre una variante simplificada Pet1 sin arrays pero con referencias externas + * La query es la misma que la anterior: + * + * tds Pet1 where Pet1.category::name='Dogs' and Pet1.status='available' + * + * La primera transformacion realizada al crear el esquema es crear un tipo Pet1_category_xt + * que tiene una FK a Category, este sera el tipo asignado a Pet1.category + * + * Como el esquema referencia un objeto independiente Category, los datos de Pet1::category + * deben ser consistentes con los que haya en Category (se esta desnormalizando/duplicando datos). + * Para ello en la generacion de reglas se hace otra transformacion anyadiendo un left join con Category + * (a la que se le asigna el alias Pet1_category_xref por si hay otros composites que referencian a Category). + * La query transformada sera: + * + * SELECT * FROM Pet1 + * LEFT JOIN Category Pet1_category_xref ON Pet1.category::id = Pet1_category_xref.id + * WHERE Pet1.category::name = 'Dogs' AND Pet1.status = 'available' + */ + public static String queryPet1ByCategoryAndStatus = + "tds Pet1 where Pet1.category::name='Dogs' and Pet1.status='available'"; + @Test + public void testPet1ByCategoryAndStatus() { + TdRules rules = getRules(queryPet1ByCategoryAndStatus); + assertModel("rules-pet1-by-category-status.xml", new TdRulesXmlSerializer().serialize(rules)); + + DataLoader dg = getDataLoader(); + //Las tres primeras reglas son como en la anterior, pero ahora los datos estan en otra tabla Category + //la primera crea una category=Dogs y Pet1 que satisface las dos condiciones del where + dg.load("Category", "id=@cid1, name=Dogs"); + dg.load("Pet1", "id=@pid1, category::id=@cid1, status=available"); + //la segunda busca Pet1.status!=available, por lo que reutiliza la categoria anterior + dg.load("Pet1", "id=@pid2, category::id=@cid1, status=sold");//sold es un valor !=available indicado por qagrow entre los permitidos + //la tercera busca Pet1.category diferente de Dogs, por lo que debe crear un maestro nuevo + dg.load("Category", "id=@cid2, name=1"); //1 es un valor !=Dogs indicado por qagrow + dg.load("Pet1", "id=@pid3, category::id=@cid2, status=available"); + + //La regla de pet sin categoria no se genera porque hay integridad referencial y la categoria en el pet es no nullable + //NOTA: el parser de swagger siempre devuelve no nullables los composites que provienen de una regla externa + + //La quinta busca una fila de Category sin Pet1, + //notar que las dos condiciones sobre pet se han reducido por estar en el outer increment + dg.load("Category", "id=@cid3"); + + assertData("datagen-local-pet1-by-category-status.txt", dg); + } + + /** + * Join relacional entre tres tablas: + * Busca el Client de un nombre dado y sus Order0 que tienen status=placed para Pet1 con category=Dogs + * La notacion de esta query seria: + * + * tds Customer0 xjoin Order0 xjoin Pet where Pet0.category::name='Dogs' and Order0.status='placed' + * + * que se representa de forma simplificada como: + * + * tds Customer0, Order0, Pet where Pet0.category::name='Dogs' and Order0.status='placed' + * + * donde cada xjoin se transforma en una join por las pk y fk de las tablas involucradas (similar a natural join), + * dando lugar a una query transformada como la siguiente (usando notacion postgres): + * + * SELECT * FROM Customer0 + * INNER JOIN "Order0" ON Customer0.id = Order0.CustomerId + * INNER JOIN Pet0 ON Order0.petId = Pet0.id + * WHERE (Pet0.category).name = 'Dogs' AND "Order0".status = 'placed' + */ + public static String queryPlacedPet0OrdersByCategoryAndOrderStatus= + "tds Customer0, \"Order0\", Pet0" + + " where Pet0.category::name='Dogs' and \"Order0\".status='placed'"; + @Test + public void testPlacedPet0OrdersByCategoryAndOrderStatus() { + TdRules rules = getRules(queryPlacedPet0OrdersByCategoryAndOrderStatus); + assertModel("rules-placed-pet0-orders-by-category-order-status.xml", new TdRulesXmlSerializer().serialize(rules)); + + DataLoader dg = getDataLoader(); + //la primera crea maestros Customer0 y Pet0 con un Order0 que satisface las dos condiciones del where + dg.load("Customer0", "id=@cid1"); + dg.load("Pet0", "id=@pid1, category::name=Dogs"); + dg.load("Order0", "id=@oid1, customerId=@cid1, petId=@pid1, status=placed"); + + //la segunda require (Pet0.category).name!=Dogs, necesita un nuevo maestro + dg.load("Pet0","id=@pid2, category::name=1"); //name indicado por qagrow !=Dogs + dg.load("Order0","id=@oid2, customerId=@cid1, petId=@pid2, status=placed"); //status indicado por qagrow !=placed + //la tercera require status!=placed, reutiliza los primeros mestros + dg.load("Order0","id=@oid3, customerId=@cid1, petId=@pid1, status=delivered"); //status indicado por qagrow !=placed + + //La cuarta es un Customer0 sin Order0, + //notar que como no hay order, tampoco habra pet y las condiciones del where han reducido por estar en el outer increment + dg.load("Customer0", "id=@cid2"); + //La quinta es un Pet0 sin Order0, en este caso debe mantenerse ((Pet0.category).name = 'Dogs') + dg.load("Pet0", "id=@pid2, category::name=Dogs"); + + assertData("datagen-local-placed-pet0-orders-by-category-order-status.txt", dg); + } + + /** + * La misma query debe procesarse correctamente si se usan alias o no se usan prefijos: + * En Pet la category se usa sin prefijo, en Order el status con alias (para distinguir del status de un Pet) + */ + public static String queryPlacedPet0OrdersWithAlias= + "tds Customer0 c, \"Order0\" o, Pet0" + + " where category::name='Dogs' and o.status='placed'"; + @Test + public void testPlacedPet0OrdersWithAlias() { + TdRules rules = getRules(queryPlacedPet0OrdersWithAlias); + assertModel("rules-placed-pet0-orders-with-alias.xml", new TdRulesXmlSerializer().serialize(rules)); + } + + ////////////////////////////// Generacion de arrays ////////////////////////////// + + /** + * La query es la misma que la que usa Pet1, pero ahora se usa Pet que contiene dos arrays + * + * tds Pet where Pet.category::name='Dogs' and Pet.status='available' + * + * Las transformaciones del esquema, ademas de crear el tipo Pet_category_xref + * crean dos arrays Pet_Tags_xa y Pet_photoUrls_xa. + * Estos arrays se muestran como tablas de tipo array + * con sus propias claves pk y fk que referencian a Pet + * + * Aunque son tablas detalle, en la generacion se deberan incluir antes que la + * generacion de la correspondiente fila de Pet usando como fk el valor simbolico de la pk de Pet + * (nota: la pk se genera automaticamente de forma secuencial, + * podria ser innecesaria, dependiendo de si la necesita o no QAGrow) + * + * Las transformaciones de reglas anyaden joins para estas dos tablas. + * La query transformada (usando notacion postgres) sera: + * + * SELECT * FROM Pet + * LEFT JOIN Pet_tags_xa ON Pet.id = Pet_tags_xa.fk_xa + * LEFT JOIN Pet_photoUrls_xa ON Pet.id = Pet_photoUrls_xa.fk_xa + * LEFT JOIN Category Pet_category_xref ON Pet.category::id = Pet_category_xref.id + * WHERE Pet.category::name = 'Dogs' AND Pet.status = 'available' + * + * Este test contiene dos variante una usando el AttrGen por defecto y otra usando un diccionario + */ + public static String queryPetByCategoryAndStatus = + "tds Pet where Pet.category::name='Dogs' and Pet.status='available'"; + @Test + public void testPetByCategoryAndStatus() { + TdRules rules = getRules(queryPetByCategoryAndStatus); + assertModel("rules-pet-by-category-status.xml", new TdRulesXmlSerializer().serialize(rules)); + DataLoader dg = getDataLoader(); + doTestPetByCategoryAndStatus(dg, "datagen-local-pet-by-category-status.txt"); + } + @Test + public void testPetByCategoryAndStatusWithDictionary() { + //no comprueba reglas aqui, son las mismas que el anterior + DataLoader dg = getDataLoader().setAttrGen(getDictionaryAttrGen()); + doTestPetByCategoryAndStatus(dg, "datagen-local-pet-by-category-status-dict.txt"); + } + private void doTestPetByCategoryAndStatus(DataLoader dg, String outputFileName) { + //Las reglas son como en testPet1ByCategoryAndStatus pero ahora hay arrays, con lo que antes de generar cada Pet + //se anyadiria al menos una fila con Pet_photoUrls_xa y Pet_Tags_xa, + //poniendo como fk la pk del pet que se generara a continuacion + dg.load("Pet_Tags_xa", "fk_xa=@pid1"); + dg.load("Pet_photoUrls_xa", "fk_xa=@pid1"); + dg.load("Category", "id=@cid1, name=Dogs"); + dg.load("Pet", "id=@pid1, category::id=@cid1, status=available"); + + dg.load("Pet_Tags_xa", "fk_xa=@pid2"); + dg.load("Pet_photoUrls_xa", "fk_xa=@pid2"); + dg.load("Pet", "id=@pid2, category::id=@cid1, status=sold");//sold es un valor !=available indicado por qagrow entre los permitidos + + dg.load("Pet_Tags_xa", "fk_xa=@pid3"); + dg.load("Pet_photoUrls_xa", "fk_xa=@pid3"); + dg.load("Category", "id=@cid2, name=1"); //1 es un valor !=Dogs indicado por qagrow + dg.load("Pet", "id=@pid3, category::id=@cid2, status=available"); + + dg.load("Category", "id=@cid3"); + + //estas dos ultimas son para dos reglas nuevas que implican pets que no tengan tags y pets que no tengan urls + dg.load("Pet_photoUrls_xa", "fk_xa=@pid4"); + dg.load("Pet", "id=@pid4, category::id=@cid1, status=available"); + + dg.load("Pet_Tags_xa", "fk_xa=@pid5"); + dg.load("Pet", "id=@pid5, category::id=@cid1, status=available"); + + assertData(outputFileName, dg); + } + + /** + * Una variante de la anterior, + * pero ahora las condiciones del where son referencias a items de los arrays + * (uno es array de primitivos y otro array de objetos) + * + * tds Pet where Pet.photoUrls[]='URL' and Pet.tags[]::name='kitty'" + * + * Ademas de las joins con los arrays se mantiene la join con Pet_category_xref + * puesto que aunque no se referencie en el where es necesaria para tener integridad referencial. + * Las transformaciones de los joins tambien transforma estas referencias en el where + * + * SELECT * FROM Pet + * LEFT JOIN Pet_tags_xa ON Pet.id = Pet_tags_xa.fk_xa + * LEFT JOIN Pet_photoUrls_xa ON Pet.id = Pet_photoUrls_xa.fk_xa + * LEFT JOIN Category Pet_category_xref ON Pet.category::id = Pet_category_xref.id + * WHERE Pet_photoUrls_xa.photoUrls = 'URL' AND Pet_tags_xa.name = 'kitty' + */ + public static String queryPetByUrlAndTag = + "tds Pet where Pet.photoUrls[]='URL' and Pet.tags[]::name='kitty'"; + @Test + public void testPetByUrlAndTag() { + TdRules rules = getRules(queryPetByUrlAndTag); + assertModel("rules-pet-by-url-tag.xml", new TdRulesXmlSerializer().serialize(rules)); + //a partir de aqui ya compruebo solo las reglas en la mayor parte de los tests + } + + /** + * Join relacional entre tres tablas utilizando los objetos orignales Customer, Order y Pet + * + * tds Customer, Order, Pet where Pet.category::name='Dogs' and Order.status='placed' + * + * Es la misma que testPlacedPet0OrdersByCategoryAndOrderStatus) pero ahora saldran otras tablas + * debido a los arrays (dos de Pet mas uno de Customer) mas el maestro de Category: + * + * SELECT * FROM Customer + * LEFT JOIN Customer_address_xa ON Customer.id = Customer_address_xa.fk_xa + * INNER JOIN "Order" ON Customer.id = Order.customerId + * INNER JOIN Pet ON Order.petId = Pet.id + * LEFT JOIN Pet_tags_xa ON Pet.id = Pet_tags_xa.fk_xa + * LEFT JOIN Pet_photoUrls_xa ON Pet.id = Pet_photoUrls_xa.fk_xa + * LEFT JOIN Category Pet_category_xref ON Pet.category::id = Pet_category_xref.id + * WHERE Pet.category::name = 'Dogs' AND "Order".status = 'placed' + */ + public static String queryPlacedPetOrdersByCategoryAndOrderStatus= + "tds Customer, \"Order\", Pet" + + " where Pet.category::name='Dogs' and \"Order\".status='placed'"; + @Test + public void testPlacedPetOrdersByCategoryAndOrderStatus() { + TdRules rules = getRules(queryPlacedPetOrdersByCategoryAndOrderStatus); + assertModel("rules-placed-pet-orders-by-category-order-status.xml", new TdRulesXmlSerializer().serialize(rules)); + } + + /** + * Otra join similar para ver las Orders a enviar a un zip de cliente dado, + * incluyen condiciones sobre otros atributos diferentes de la anterior, uno de ellos es array + * + * En esta se comprobara la generacion de una fila sin y con diccionario + */ + public static String queryPlacedPetOrdersByAddressAndOrderStatus= + "tds Customer, \"Order\", Pet" + + " where Customer.address[]::zip='99999' and \"Order\".status='placed'"; + @Test + public void testPlacedPetOrdersByAddressAndOrderStatus() { + TdRules rules = getRules(queryPlacedPetOrdersByAddressAndOrderStatus); + assertModel("rules-placed-pet-orders-by-address-order-status.xml", new TdRulesXmlSerializer().serialize(rules)); + DataLoader dg = getDataLoader(); + doPlacedPetOrdersByAddressAndOrderStatus(dg, "datagen-local-placed-pet-orders-by-address-order-status.txt"); + } + @Test + public void testPlacedPetOrdersByAddressAndOrderStatusWithDictionary() { + DataLoader dg=getDataLoader().setAttrGen(getDictionaryAttrGen()); + doPlacedPetOrdersByAddressAndOrderStatus(dg, "datagen-local-placed-pet-orders-by-address-order-status-dict.txt"); + } + private void doPlacedPetOrdersByAddressAndOrderStatus(DataLoader dg, String outputFileName) { + dg.load("Pet_Tags_xa", "fk_xa=@pid1"); + dg.load("Pet_photoUrls_xa", "fk_xa=@pid1"); + dg.load("Category", "id=@cid1, name=Dogs"); + dg.load("Pet", "id=@pid1, category::id=@cid1, status=available"); + dg.load("Customer_address_xa", "fk_xa=@cuid1"); + dg.load("Customer", "id=@cuid1,"); + dg.load("Order", "id=@oid1,petId=@pid1,customerId=@cuid1"); + assertData(outputFileName, dg); + } + + ////////////////////////////// Generacion de datos con group by ////////////////////////////// + + /** + * Total de Orders a enviar (estado approved) agrupadas por zip de la direccion del cliente. + * Notar que la transformacion de la clausula tds introduce en el select las columnas del groupby y un count + */ + public static String queryTotalOrdersToDeliverByAddress= + "tds Customer, \"Order\", Pet" + + " where \"Order\".status='approved'" + + " group by Customer.address[]::zip"; + @Test + public void testTotalOrdersToDeliverByAddress() { + TdRules rules = getRules(queryTotalOrdersToDeliverByAddress); + assertModel("rules-total-pet-orders-by-address.xml", new TdRulesXmlSerializer().serialize(rules)); + } + + /** + * Como la anterior pero suma todas las unidades pedidas en cada Order, + * en este caso se pone un select explícito + */ + public static String queryTotalPetsToDeliverByAddress= + "select Customer.address[]::zip, sum(\"Order\".quantity) from Customer, \"Order\", Pet" + + " where \"Order\".status='approved'" + + " group by Customer.address[]::zip"; + @Test + public void testTotalPetsToDeliverByAddress() { + TdRules rules = getRules(queryTotalPetsToDeliverByAddress); + assertModel("rules-total-pets-by-address.xml", new TdRulesXmlSerializer().serialize(rules)); + } +} diff --git a/st-tdg-test/src/test/java/test4giis/tdrules/petstore/TestPetstoreQagrowLive.java b/st-tdg-test/src/test/java/test4giis/tdrules/petstore/TestPetstoreQagrowLive.java new file mode 100644 index 0000000..e1de083 --- /dev/null +++ b/st-tdg-test/src/test/java/test4giis/tdrules/petstore/TestPetstoreQagrowLive.java @@ -0,0 +1,15 @@ +package test4giis.tdrules.petstore; + +/** + * Mismos test que TestPetstoreQagrowLocal, pero accediendo al servidor de swagger-petstore, + * heredan de la base salvo la forma de crear el generador y comparar los datos + * (asegurar que el container ha sido arrancado con docker-run) + */ +public class TestPetstoreQagrowLive extends TestPetstoreQagrowLocal { + + @Override + protected boolean isLiveBackend() { + return true; + } + +} diff --git a/st-tdg-test/src/test/java/test4giis/tdrules/petstore/TestPetstoreQagrowLiveBackId.java b/st-tdg-test/src/test/java/test4giis/tdrules/petstore/TestPetstoreQagrowLiveBackId.java new file mode 100644 index 0000000..b64b0e1 --- /dev/null +++ b/st-tdg-test/src/test/java/test4giis/tdrules/petstore/TestPetstoreQagrowLiveBackId.java @@ -0,0 +1,26 @@ +package test4giis.tdrules.petstore; + +import org.junit.Test; + +import giis.tdrules.store.loader.DataLoader; + +/** + * Tests adicionales con QAGrow para comprobar la generacion de claves en el backend + * extendiendo algunas funcionalidades del petstore para que generen valores de estas + */ +public class TestPetstoreQagrowLiveBackId extends TestPetstoreDatagenLiveBackId { + + /** + * Prueba de con dos entidades con referencias generando claves en el backend + */ + @Test + public void testPetByCategoryAndStatus() { + //Deben ser los mismos que en el correspondiente live, pero cambia el valor + //que tienen las claves primarias (Category son multiplos de 10 y Pet multiplos de 1000) + + DataLoader dg = getDataLoader(); + generateAndLoad(dg, TestPetstoreDatagenLocal.queryPetByCategoryAndStatus); + assertData("qagrow-livebackid-pet-by-category-status.txt", dg); + } + +} diff --git a/st-tdg-test/src/test/java/test4giis/tdrules/petstore/TestPetstoreQagrowLocal.java b/st-tdg-test/src/test/java/test4giis/tdrules/petstore/TestPetstoreQagrowLocal.java new file mode 100644 index 0000000..261062c --- /dev/null +++ b/st-tdg-test/src/test/java/test4giis/tdrules/petstore/TestPetstoreQagrowLocal.java @@ -0,0 +1,121 @@ +package test4giis.tdrules.petstore; + +import org.junit.Test; + +import giis.tdrules.store.loader.DataLoader; +import giis.tdrules.store.loader.IAttrGen; + +/** + * Mismos test que TestPetstoreDatagenLocal, + * generando los datos de prueba en un archivo utilizando QAGrow. + * Utiliza un un esquema y un DataAdapter local, que no requiere una conexion activa a un servidor. + * + * Está implementado de forma similar a TestPetstoreDatagenLocal, + * usando las mismas queries que define como constantes + * y los mismos convenios (los ficheros para comparacion de salidas empiezan por qagrow-local-) + * + */ +public class TestPetstoreQagrowLocal extends BasePetstore { + + @Test + public void testSmoke() { + DataLoader dg = getDataLoader(); + generateAndLoad(dg, TestPetstoreDatagenLocal.querySmoke); + assertData("qagrow-local-smoke.txt", dg); + } + + @Test + public void testPet0ByCategoryAndStatus() { + DataLoader dg = getDataLoader(); + generateAndLoad(dg, TestPetstoreDatagenLocal.queryPet0ByCategoryAndStatus); + assertData("qagrow-local-pet0-by-category-status.txt", dg); + } + + @Test + public void testPet1ByCategoryAndStatus() { + DataLoader dg = getDataLoader(); + generateAndLoad(dg, TestPetstoreDatagenLocal.queryPet1ByCategoryAndStatus); + assertData("qagrow-local-pet1-by-category-status.txt", dg); + } + + @Test + public void testPlacedPet0OrdersByCategoryAndOrderStatus() { + DataLoader dg = getDataLoader(); + generateAndLoad(dg, TestPetstoreDatagenLocal.queryPlacedPet0OrdersByCategoryAndOrderStatus); + assertData("qagrow-local-placed-pet0-orders-by-category-order-status.txt", dg); + } + + @Test + public void testPlacedPet0OrdersWithAlias() { + DataLoader dg = getDataLoader(); + generateAndLoad(dg, TestPetstoreDatagenLocal.queryPlacedPet0OrdersWithAlias); + assertData("qagrow-local-placed-pet0-orders-by-category-order-status.txt", dg); + + } + + ////////////////////////////// Generacion datos de arrays ////////////////////////////// + + @Test + public void testPetByCategoryAndStatus() { + DataLoader dg = getDataLoader(); + generateAndLoad(dg, TestPetstoreDatagenLocal.queryPetByCategoryAndStatus); + assertData("qagrow-local-pet-by-category-status.txt", dg); + } + + @Test + public void testPetByCategoryAndStatusWithDictionary() { + //Repetir la misma anterior pero especificando el AttrGen con diccionario: + //usar getDictionaryAttrGen() e inyectarlo en el DataGenerator + IAttrGen dict=getDictionaryAttrGen(); + DataLoader dg = getDataLoader().setAttrGen(dict); + generateAndLoad(dg, TestPetstoreDatagenLocal.queryPetByCategoryAndStatus, dict); + assertData("qagrow-local-pet-by-category-status-dict.txt", dg); + + } + + @Test + public void testPetByUrlAndTag() { + DataLoader dg = getDataLoader(); + generateAndLoad(dg, TestPetstoreDatagenLocal.queryPetByUrlAndTag); + assertData("qagrow-local-pet-by-url-tag.txt", dg); + } + + @Test + public void testPlacedPetOrdersByCategoryAndOrderStatus() { + DataLoader dg = getDataLoader(); + generateAndLoad(dg, TestPetstoreDatagenLocal.queryPlacedPetOrdersByCategoryAndOrderStatus); + assertData("qagrow-local-placed-pet-orders-by-category-order-status.txt", dg); + } + + @Test + public void testPlacedPetOrdersByAddressAndOrderStatus() { + DataLoader dg = getDataLoader(); + generateAndLoad(dg, TestPetstoreDatagenLocal.queryPlacedPetOrdersByAddressAndOrderStatus); + assertData("qagrow-local-placed-pet-orders-by-address-order-status.txt", dg); + } + + @Test + public void testPlacedPetOrdersByAddressAndOrderStatusWithDictionary() { + IAttrGen dict=getDictionaryAttrGen(); + DataLoader dg = getDataLoader().setAttrGen(dict); + generateAndLoad(dg, TestPetstoreDatagenLocal.queryPlacedPetOrdersByAddressAndOrderStatus, dict); + assertData("qagrow-local-placed-pet-orders-by-address-order-status-dict.txt", dg); + } + + ////////////////////////////// Generacion de datos con group by ////////////////////////////// + + @Test + public void testTotalOrdersToDeliverByAddress() { + DataLoader dg = getDataLoader(); + generateAndLoad(dg, TestPetstoreDatagenLocal.queryTotalOrdersToDeliverByAddress); + assertData("qagrow-local-total-pet-orders-by-address.txt", dg); + } + + @Test + public void testTotalPetsToDeliverByAddress() { + DataLoader dg = getDataLoader(); + generateAndLoad(dg, TestPetstoreDatagenLocal.queryTotalPetsToDeliverByAddress); + assertData("qagrow-local-total-pets-by-address.txt", dg); + } + +} diff --git a/st-tdg-test/src/test/java/test4giis/tdrules/petstore/TestPetstoreSchemaLocal.java b/st-tdg-test/src/test/java/test4giis/tdrules/petstore/TestPetstoreSchemaLocal.java new file mode 100644 index 0000000..671243b --- /dev/null +++ b/st-tdg-test/src/test/java/test4giis/tdrules/petstore/TestPetstoreSchemaLocal.java @@ -0,0 +1,59 @@ +package test4giis.tdrules.petstore; + +import static org.junit.Assert.assertEquals; + +import java.util.List; + +import org.junit.Test; + +import com.fasterxml.jackson.core.JsonProcessingException; + +import giis.tdrules.client.oa.MermaidWriter; +import giis.tdrules.model.io.TdSchemaXmlSerializer; +import giis.tdrules.model.transform.SchemaSorter; +import giis.tdrules.openapi.model.TdSchema; + +/** + * Genera el esquema a partir de la especificacion (leida de archivo) y lo comprueba en formato json y xml + */ +public class TestPetstoreSchemaLocal extends BasePetstore { + + @Test + public void testSchemaLocalJson() throws JsonProcessingException { + TdSchema schema = getSchema(); + assertModel("schema-petstore.json", serialize(schema)); + } + + @Test + public void testSchemaLocalXml () { + TdSchema schema = getSchema(); + assertModel("schema-petstore.xml", new TdSchemaXmlSerializer().serialize(schema)); + } + + @Test + public void testSchemaLocalMermaid () { + String mermaid = new MermaidWriter(getSchema()).getMermaid(); + assertModel("schema-petstore.md", mermaid); + } + + //Regression test for QAGrow#28 + @Test + public void testSchemaGetTableList () { + TdSchema schema = getSchema(); + assertEquals("[Order, Customer_address_xa, Customer, Address, Category, User," + + " Tag, Pet_category_xt, Pet_photoUrls_xa, Pet_tags_xa, Pet," + + " Customer0, Order0, Pet0_category_xt, Pet0, Pet1_category_xt, Pet1, ApiResponse]", + schema.getEntityNames().toString()); + } + + //Regression test for QAGrow#29 + @Test + public void testSchemaOrderTables () { + TdSchema schema = getSchema(); + List tables = schema.getEntityNames(); + assertEquals("[Category, Pet_category_xt, Pet, Customer, Order, Customer_address_xa, Address, User," + + " Tag, Pet_photoUrls_xa, Pet_tags_xa, Customer0, Pet0_category_xt, Pet0, Order0, Pet1_category_xt, Pet1, ApiResponse]", + new SchemaSorter(schema).sort(tables).toString()); + } + +} diff --git a/st-tdg-test/src/test/resources/gestaoHospital/datagen-local-hospital-product-by-productype-quantity.txt b/st-tdg-test/src/test/resources/gestaoHospital/datagen-local-hospital-product-by-productype-quantity.txt new file mode 100644 index 0000000..7bbadbf --- /dev/null +++ b/st-tdg-test/src/test/resources/gestaoHospital/datagen-local-hospital-product-by-productype-quantity.txt @@ -0,0 +1,5 @@ +"HospitalDTO":{"id":"1","address":"2","availableBeds":3,"beds":4,"latitude":"5","longitude":"6","name":"7"} +"ProductDTO":{"id":"1","hospitalDTOId":"1","description":"103","name":"104","productName":"105","productType":"COMMON","quantity":1} +"ProductDTO":{"id":"2","hospitalDTOId":"1","description":"203","name":"204","productName":"205","productType":"BLOOD","quantity":1} +"ProductDTO":{"id":"3","hospitalDTOId":"1","description":"303","name":"304","productName":"305","productType":"COMMON","quantity":0} +"HospitalDTO":{"id":"2","address":"402","availableBeds":403,"beds":404,"latitude":"405","longitude":"406","name":"407"} \ No newline at end of file diff --git a/st-tdg-test/src/test/resources/gestaoHospital/datagen-local-product-by-productype-quantity.txt b/st-tdg-test/src/test/resources/gestaoHospital/datagen-local-product-by-productype-quantity.txt new file mode 100644 index 0000000..f02ce44 --- /dev/null +++ b/st-tdg-test/src/test/resources/gestaoHospital/datagen-local-product-by-productype-quantity.txt @@ -0,0 +1,3 @@ +"ProductDTO":{"id":"1","hospitalDTOId":"2","description":"3","name":"4","productName":"5","productType":"COMMON","quantity":1} +"ProductDTO":{"id":"101","hospitalDTOId":"102","description":"103","name":"104","productName":"105","productType":"BLOOD","quantity":1} +"ProductDTO":{"id":"201","hospitalDTOId":"202","description":"203","name":"204","productName":"205","productType":"COMMON","quantity":0} \ No newline at end of file diff --git a/st-tdg-test/src/test/resources/gestaoHospital/datagen-local-smoke.txt b/st-tdg-test/src/test/resources/gestaoHospital/datagen-local-smoke.txt new file mode 100644 index 0000000..1b0f5d5 --- /dev/null +++ b/st-tdg-test/src/test/resources/gestaoHospital/datagen-local-smoke.txt @@ -0,0 +1,2 @@ +"HospitalDTO":{"id":"1","address":"2","availableBeds":10,"beds":4,"latitude":"5","longitude":"6","name":"7"} +"HospitalDTO":{"id":"101","address":"102","availableBeds":1,"beds":104,"latitude":"105","longitude":"106","name":"107"} \ No newline at end of file diff --git a/st-tdg-test/src/test/resources/gestaoHospital/qagrow-live-aval-products-by-hospital.txt b/st-tdg-test/src/test/resources/gestaoHospital/qagrow-live-aval-products-by-hospital.txt new file mode 100644 index 0000000..a1254c7 --- /dev/null +++ b/st-tdg-test/src/test/resources/gestaoHospital/qagrow-live-aval-products-by-hospital.txt @@ -0,0 +1,6 @@ +"hospital":{"id":"0","name":"Mayo Clinic","address":"2nd Avenue Southwest, Rochester, Olmsted County, Minnesota, 55902, USA","beds":4,"availableBeds":3,"location":{"id":"0","name":"Mayo Clinic","referenceId":"2nd Avenue Southwest, Rochester, Olmsted County, Minnesota, 55902, USA","locationCategory":"HOSPITAL","position":{"x":-92.4659151,"y":44.0181799,"coordinates":[-92.4659151,44.0181799],"type":"Point"},"location":{"x":-92.4659151,"y":44.0181799,"coordinates":[-92.4659151,44.0181799],"type":"Point"}},"patients":[],"products":[{"name":"Rice","description":"Food","quantity":1,"productType":"BLOOD","hospitalId":"0","id":"0"},{"name":"Blood A+","description":"Blood Bank","quantity":0,"productType":"COMMON","hospitalId":"0","id":"1"}]} +"hospital":{"id":"1","name":"John Hopkins Hospital","address":"5th Avenue South, Downtown, Saint Petersburg, Pinellas County, Florida, 33701, USA","beds":104,"availableBeds":103,"location":{"id":"1","name":"John Hopkins Hospital","referenceId":"5th Avenue South, Downtown, Saint Petersburg, Pinellas County, Florida, 33701, USA","locationCategory":"HOSPITAL","position":{"x":-82.6385709,"y":27.7657,"coordinates":[-82.6385709,27.7657],"type":"Point"},"location":{"x":-82.6385709,"y":27.7657,"coordinates":[-82.6385709,27.7657],"type":"Point"}},"patients":[],"products":[]} +"product":{"name":"Rice","description":"Food","quantity":1,"productType":"BLOOD","hospitalId":"0","id":"0"} +"product":{"name":"Blood A+","description":"Blood Bank","quantity":0,"productType":"COMMON","hospitalId":"0","id":"1"} +"location":{"id":"0","name":"Mayo Clinic","referenceId":"2nd Avenue Southwest, Rochester, Olmsted County, Minnesota, 55902, USA","locationCategory":"HOSPITAL","position":{"x":-92.4659151,"y":44.0181799,"coordinates":[-92.4659151,44.0181799],"type":"Point"},"location":{"x":-92.4659151,"y":44.0181799,"coordinates":[-92.4659151,44.0181799],"type":"Point"}} +"location":{"id":"1","name":"John Hopkins Hospital","referenceId":"5th Avenue South, Downtown, Saint Petersburg, Pinellas County, Florida, 33701, USA","locationCategory":"HOSPITAL","position":{"x":-82.6385709,"y":27.7657,"coordinates":[-82.6385709,27.7657],"type":"Point"},"location":{"x":-82.6385709,"y":27.7657,"coordinates":[-82.6385709,27.7657],"type":"Point"}} diff --git a/st-tdg-test/src/test/resources/gestaoHospital/qagrow-live-smoke-dict.txt b/st-tdg-test/src/test/resources/gestaoHospital/qagrow-live-smoke-dict.txt new file mode 100644 index 0000000..ede5037 --- /dev/null +++ b/st-tdg-test/src/test/resources/gestaoHospital/qagrow-live-smoke-dict.txt @@ -0,0 +1,6 @@ +"hospital":{"id":"0","name":"Mayo Clinic","address":"2nd Avenue Southwest, Rochester, Olmsted County, Minnesota, 55902, USA","beds":4,"availableBeds":11,"location":{"id":"0","name":"Mayo Clinic","referenceId":"2nd Avenue Southwest, Rochester, Olmsted County, Minnesota, 55902, USA","locationCategory":"HOSPITAL","position":{"x":-92.4659151,"y":44.0181799,"coordinates":[-92.4659151,44.0181799],"type":"Point"},"location":{"x":-92.4659151,"y":44.0181799,"coordinates":[-92.4659151,44.0181799],"type":"Point"}},"patients":[],"products":[]} +"hospital":{"id":"1","name":"John Hopkins Hospital","address":"5th Avenue South, Downtown, Saint Petersburg, Pinellas County, Florida, 33701, USA","beds":104,"availableBeds":10,"location":{"id":"1","name":"John Hopkins Hospital","referenceId":"5th Avenue South, Downtown, Saint Petersburg, Pinellas County, Florida, 33701, USA","locationCategory":"HOSPITAL","position":{"x":-82.6385709,"y":27.7657,"coordinates":[-82.6385709,27.7657],"type":"Point"},"location":{"x":-82.6385709,"y":27.7657,"coordinates":[-82.6385709,27.7657],"type":"Point"}},"patients":[],"products":[]} +"hospital":{"id":"2","name":"Singapore General Hospital","address":"Everton Road, Bukit Merah, Singapur, Central Region, 088860, Singapore","beds":204,"availableBeds":9,"location":{"id":"2","name":"Singapore General Hospital","referenceId":"Everton Road, Bukit Merah, Singapur, Central Region, 088860, Singapore","locationCategory":"HOSPITAL","position":{"x":103.837679,"y":1.276579,"coordinates":[103.837679,1.276579],"type":"Point"},"location":{"x":103.837679,"y":1.276579,"coordinates":[103.837679,1.276579],"type":"Point"}},"patients":[],"products":[]} +"location":{"id":"0","name":"Mayo Clinic","referenceId":"2nd Avenue Southwest, Rochester, Olmsted County, Minnesota, 55902, USA","locationCategory":"HOSPITAL","position":{"x":-92.4659151,"y":44.0181799,"coordinates":[-92.4659151,44.0181799],"type":"Point"},"location":{"x":-92.4659151,"y":44.0181799,"coordinates":[-92.4659151,44.0181799],"type":"Point"}} +"location":{"id":"1","name":"John Hopkins Hospital","referenceId":"5th Avenue South, Downtown, Saint Petersburg, Pinellas County, Florida, 33701, USA","locationCategory":"HOSPITAL","position":{"x":-82.6385709,"y":27.7657,"coordinates":[-82.6385709,27.7657],"type":"Point"},"location":{"x":-82.6385709,"y":27.7657,"coordinates":[-82.6385709,27.7657],"type":"Point"}} +"location":{"id":"2","name":"Singapore General Hospital","referenceId":"Everton Road, Bukit Merah, Singapur, Central Region, 088860, Singapore","locationCategory":"HOSPITAL","position":{"x":103.837679,"y":1.276579,"coordinates":[103.837679,1.276579],"type":"Point"},"location":{"x":103.837679,"y":1.276579,"coordinates":[103.837679,1.276579],"type":"Point"}} diff --git a/st-tdg-test/src/test/resources/gestaoHospital/qagrow-live-smoke.txt b/st-tdg-test/src/test/resources/gestaoHospital/qagrow-live-smoke.txt new file mode 100644 index 0000000..0cce1a1 --- /dev/null +++ b/st-tdg-test/src/test/resources/gestaoHospital/qagrow-live-smoke.txt @@ -0,0 +1,6 @@ +"hospital":{"id":"0","name":"7","address":"2","beds":4,"availableBeds":11,"location":{"id":"0","name":"7","referenceId":"2","locationCategory":"HOSPITAL","position":{"x":-46.6388042029871,"y":-23.5920091,"coordinates":[-46.6388042029871,-23.5920091],"type":"Point"},"location":{"x":-46.6388042029871,"y":-23.5920091,"coordinates":[-46.6388042029871,-23.5920091],"type":"Point"}},"patients":[],"products":[]} +"hospital":{"id":"1","name":"107","address":"102","beds":104,"availableBeds":10,"location":{"id":"1","name":"107","referenceId":"102","locationCategory":"HOSPITAL","position":{"x":-46.6388042029871,"y":-23.5920091,"coordinates":[-46.6388042029871,-23.5920091],"type":"Point"},"location":{"x":-46.6388042029871,"y":-23.5920091,"coordinates":[-46.6388042029871,-23.5920091],"type":"Point"}},"patients":[],"products":[]} +"hospital":{"id":"2","name":"207","address":"202","beds":204,"availableBeds":9,"location":{"id":"2","name":"207","referenceId":"202","locationCategory":"HOSPITAL","position":{"x":-46.6388042029871,"y":-23.5920091,"coordinates":[-46.6388042029871,-23.5920091],"type":"Point"},"location":{"x":-46.6388042029871,"y":-23.5920091,"coordinates":[-46.6388042029871,-23.5920091],"type":"Point"}},"patients":[],"products":[]} +"location":{"id":"0","name":"7","referenceId":"2","locationCategory":"HOSPITAL","position":{"x":-46.6388042029871,"y":-23.5920091,"coordinates":[-46.6388042029871,-23.5920091],"type":"Point"},"location":{"x":-46.6388042029871,"y":-23.5920091,"coordinates":[-46.6388042029871,-23.5920091],"type":"Point"}} +"location":{"id":"1","name":"107","referenceId":"102","locationCategory":"HOSPITAL","position":{"x":-46.6388042029871,"y":-23.5920091,"coordinates":[-46.6388042029871,-23.5920091],"type":"Point"},"location":{"x":-46.6388042029871,"y":-23.5920091,"coordinates":[-46.6388042029871,-23.5920091],"type":"Point"}} +"location":{"id":"2","name":"207","referenceId":"202","locationCategory":"HOSPITAL","position":{"x":-46.6388042029871,"y":-23.5920091,"coordinates":[-46.6388042029871,-23.5920091],"type":"Point"},"location":{"x":-46.6388042029871,"y":-23.5920091,"coordinates":[-46.6388042029871,-23.5920091],"type":"Point"}} diff --git a/st-tdg-test/src/test/resources/gestaoHospital/qagrow-live-unav-products-by-hospital.txt b/st-tdg-test/src/test/resources/gestaoHospital/qagrow-live-unav-products-by-hospital.txt new file mode 100644 index 0000000..0573c32 --- /dev/null +++ b/st-tdg-test/src/test/resources/gestaoHospital/qagrow-live-unav-products-by-hospital.txt @@ -0,0 +1,9 @@ +"hospital":{"id":"0","name":"Mayo Clinic","address":"2nd Avenue Southwest, Rochester, Olmsted County, Minnesota, 55902, USA","beds":4,"availableBeds":1,"location":{"id":"0","name":"Mayo Clinic","referenceId":"2nd Avenue Southwest, Rochester, Olmsted County, Minnesota, 55902, USA","locationCategory":"HOSPITAL","position":{"x":-92.4659151,"y":44.0181799,"coordinates":[-92.4659151,44.0181799],"type":"Point"},"location":{"x":-92.4659151,"y":44.0181799,"coordinates":[-92.4659151,44.0181799],"type":"Point"}},"patients":[],"products":[{"name":"Rice","description":"Food","quantity":0,"productType":"COMMON","hospitalId":"0","id":"0"},{"name":"Blood A-","description":"Blood Bank","quantity":1,"productType":"COMMON","hospitalId":"0","id":"2"}]} +"hospital":{"id":"1","name":"John Hopkins Hospital","address":"5th Avenue South, Downtown, Saint Petersburg, Pinellas County, Florida, 33701, USA","beds":104,"availableBeds":0,"location":{"id":"1","name":"John Hopkins Hospital","referenceId":"5th Avenue South, Downtown, Saint Petersburg, Pinellas County, Florida, 33701, USA","locationCategory":"HOSPITAL","position":{"x":-82.6385709,"y":27.7657,"coordinates":[-82.6385709,27.7657],"type":"Point"},"location":{"x":-82.6385709,"y":27.7657,"coordinates":[-82.6385709,27.7657],"type":"Point"}},"patients":[],"products":[{"name":"Blood A+","description":"Blood Bank","quantity":0,"productType":"BLOOD","hospitalId":"1","id":"1"}]} +"hospital":{"id":"2","name":"Singapore General Hospital","address":"Everton Road, Bukit Merah, Singapur, Central Region, 088860, Singapore","beds":204,"availableBeds":1,"location":{"id":"2","name":"Singapore General Hospital","referenceId":"Everton Road, Bukit Merah, Singapur, Central Region, 088860, Singapore","locationCategory":"HOSPITAL","position":{"x":103.837679,"y":1.276579,"coordinates":[103.837679,1.276579],"type":"Point"},"location":{"x":103.837679,"y":1.276579,"coordinates":[103.837679,1.276579],"type":"Point"}},"patients":[],"products":[]} +"product":{"name":"Rice","description":"Food","quantity":0,"productType":"COMMON","hospitalId":"0","id":"0"} +"product":{"name":"Blood A+","description":"Blood Bank","quantity":0,"productType":"BLOOD","hospitalId":"1","id":"1"} +"product":{"name":"Blood A-","description":"Blood Bank","quantity":1,"productType":"COMMON","hospitalId":"0","id":"2"} +"location":{"id":"0","name":"Mayo Clinic","referenceId":"2nd Avenue Southwest, Rochester, Olmsted County, Minnesota, 55902, USA","locationCategory":"HOSPITAL","position":{"x":-92.4659151,"y":44.0181799,"coordinates":[-92.4659151,44.0181799],"type":"Point"},"location":{"x":-92.4659151,"y":44.0181799,"coordinates":[-92.4659151,44.0181799],"type":"Point"}} +"location":{"id":"1","name":"John Hopkins Hospital","referenceId":"5th Avenue South, Downtown, Saint Petersburg, Pinellas County, Florida, 33701, USA","locationCategory":"HOSPITAL","position":{"x":-82.6385709,"y":27.7657,"coordinates":[-82.6385709,27.7657],"type":"Point"},"location":{"x":-82.6385709,"y":27.7657,"coordinates":[-82.6385709,27.7657],"type":"Point"}} +"location":{"id":"2","name":"Singapore General Hospital","referenceId":"Everton Road, Bukit Merah, Singapur, Central Region, 088860, Singapore","locationCategory":"HOSPITAL","position":{"x":103.837679,"y":1.276579,"coordinates":[103.837679,1.276579],"type":"Point"},"location":{"x":103.837679,"y":1.276579,"coordinates":[103.837679,1.276579],"type":"Point"}} diff --git a/st-tdg-test/src/test/resources/gestaoHospital/qagrow-local-hospital-product-by-productype-quantity.txt b/st-tdg-test/src/test/resources/gestaoHospital/qagrow-local-hospital-product-by-productype-quantity.txt new file mode 100644 index 0000000..7cc3b58 --- /dev/null +++ b/st-tdg-test/src/test/resources/gestaoHospital/qagrow-local-hospital-product-by-productype-quantity.txt @@ -0,0 +1,5 @@ +"HospitalDTO":{"id":"0","address":"2","availableBeds":3,"beds":4,"latitude":"5","longitude":"6","name":"7"} +"HospitalDTO":{"id":"1","address":"102","availableBeds":103,"beds":104,"latitude":"105","longitude":"106","name":"107"} +"ProductDTO":{"id":"0","hospitalDTOId":"0","description":"203","name":"204","productName":"205","productType":"COMMON","quantity":1} +"ProductDTO":{"id":"1","hospitalDTOId":"0","description":"303","name":"304","productName":"305","productType":"BLOOD","quantity":2} +"ProductDTO":{"id":"2","hospitalDTOId":"0","description":"403","name":"404","productName":"405","productType":"COMMON","quantity":0} \ No newline at end of file diff --git a/st-tdg-test/src/test/resources/gestaoHospital/qagrow-local-product-by-productype-quantity.txt b/st-tdg-test/src/test/resources/gestaoHospital/qagrow-local-product-by-productype-quantity.txt new file mode 100644 index 0000000..783f417 --- /dev/null +++ b/st-tdg-test/src/test/resources/gestaoHospital/qagrow-local-product-by-productype-quantity.txt @@ -0,0 +1,4 @@ +"HospitalDTO":{"id":"0","address":"2","availableBeds":3,"beds":4,"latitude":"5","longitude":"6","name":"7"} +"ProductDTO":{"id":"0","hospitalDTOId":"0","description":"103","name":"104","productName":"105","productType":"COMMON","quantity":1} +"ProductDTO":{"id":"1","hospitalDTOId":"0","description":"203","name":"204","productName":"205","productType":"BLOOD","quantity":1} +"ProductDTO":{"id":"2","hospitalDTOId":"0","description":"303","name":"304","productName":"305","productType":"COMMON","quantity":0} \ No newline at end of file diff --git a/st-tdg-test/src/test/resources/gestaoHospital/qagrow-local-smoke.txt b/st-tdg-test/src/test/resources/gestaoHospital/qagrow-local-smoke.txt new file mode 100644 index 0000000..c0d461b --- /dev/null +++ b/st-tdg-test/src/test/resources/gestaoHospital/qagrow-local-smoke.txt @@ -0,0 +1,3 @@ +"HospitalDTO":{"id":"0","address":"2","availableBeds":11,"beds":4,"latitude":"5","longitude":"6","name":"7"} +"HospitalDTO":{"id":"1","address":"102","availableBeds":10,"beds":104,"latitude":"105","longitude":"106","name":"107"} +"HospitalDTO":{"id":"2","address":"202","availableBeds":9,"beds":204,"latitude":"205","longitude":"206","name":"207"} \ No newline at end of file diff --git a/st-tdg-test/src/test/resources/gestaoHospital/rules-hospital-product-by-producttype-and-quantity.xml b/st-tdg-test/src/test/resources/gestaoHospital/rules-hospital-product-by-producttype-and-quantity.xml new file mode 100644 index 0000000..76d2ddc --- /dev/null +++ b/st-tdg-test/src/test/resources/gestaoHospital/rules-hospital-product-by-producttype-and-quantity.xml @@ -0,0 +1,53 @@ + + +0.0.0 +tds ProductDTO,HospitalDTO where ProductDTO.productType='COMMON' and ProductDTO.quantity>0 +SELECT * + FROM ProductDTO + INNER JOIN HospitalDTO ON ProductDTO.hospitalDTOId = HospitalDTO.id + WHERE ProductDTO.productType = 'COMMON' AND ProductDTO.quantity > 0 + + 1STTT1.w.1.[ProductDTO.productType = 'COMMON'] + SELECT * + FROM ProductDTO + INNER JOIN HospitalDTO ON ProductDTO.hospitalDTOId = HospitalDTO.id + WHERE (ProductDTO.productType = 'COMMON') AND (ProductDTO.quantity > 0) + --A set of joined tables such that: +--The WHERE condition fulfills: + --(T) ProductDTO.productType = 'COMMON' is TRUE + --(T) ProductDTO.quantity > 0 is TRUE + + 2STFT1.w.1.[ProductDTO.productType = 'COMMON'] + SELECT * + FROM ProductDTO + INNER JOIN HospitalDTO ON ProductDTO.hospitalDTOId = HospitalDTO.id + WHERE NOT(ProductDTO.productType = 'COMMON') AND (ProductDTO.quantity > 0) + --A set of joined tables such that: +--The WHERE condition fulfills: + --(F) ProductDTO.productType = 'COMMON' is FALSE + --(T) ProductDTO.quantity > 0 is TRUE + + 3STFT1.w.2.[ProductDTO.quantity > 0] + SELECT * + FROM ProductDTO + INNER JOIN HospitalDTO ON ProductDTO.hospitalDTOId = HospitalDTO.id + WHERE NOT(ProductDTO.quantity > 0) AND (ProductDTO.productType = 'COMMON') + --A set of joined tables such that: +--The WHERE condition fulfills: + --(F) ProductDTO.quantity > 0 is FALSE + --(T) ProductDTO.productType = 'COMMON' is TRUE + + 4JRO1.j.1.[INNER JOIN] + SELECT * + FROM ProductDTO + RIGHT JOIN HospitalDTO ON ProductDTO.hospitalDTOId = HospitalDTO.id + WHERE (ProductDTO.hospitalDTOId IS NULL) AND (HospitalDTO.id IS NOT NULL) + +--The JOIN tables fulfill: +--COMMENT: Reduced conditions: [ProductDTO.productType = 'COMMON'] ; [ProductDTO.quantity > 0] ; + --summary: [ProductDTO] right [HospitalDTO] ON ProductDTO.hospitalDTOId = HospitalDTO.id + --There exist some row in table HospitalDTO + --which does not join to any table in ProductDTO + + + \ No newline at end of file diff --git a/st-tdg-test/src/test/resources/gestaoHospital/rules-product-by-producttype-and-quantity.xml b/st-tdg-test/src/test/resources/gestaoHospital/rules-product-by-producttype-and-quantity.xml new file mode 100644 index 0000000..616415f --- /dev/null +++ b/st-tdg-test/src/test/resources/gestaoHospital/rules-product-by-producttype-and-quantity.xml @@ -0,0 +1,37 @@ + + +0.0.0 +tds ProductDTO where ProductDTO.productType='COMMON' and ProductDTO.quantity>0 +SELECT * + FROM ProductDTO + WHERE ProductDTO.productType = 'COMMON' AND ProductDTO.quantity > 0 + + 1STTT1.w.1.[ProductDTO.productType = 'COMMON'] + SELECT * + FROM ProductDTO + WHERE (ProductDTO.productType = 'COMMON') AND (ProductDTO.quantity > 0) + --Some row in the table such that: +--The WHERE condition fulfills: + --(T) ProductDTO.productType = 'COMMON' is TRUE + --(T) ProductDTO.quantity > 0 is TRUE + + 2STFT1.w.1.[ProductDTO.productType = 'COMMON'] + SELECT * + FROM ProductDTO + WHERE NOT(ProductDTO.productType = 'COMMON') AND (ProductDTO.quantity > 0) + --Some row in the table such that: +--The WHERE condition fulfills: + --(F) ProductDTO.productType = 'COMMON' is FALSE + --(T) ProductDTO.quantity > 0 is TRUE + + 3STFT1.w.2.[ProductDTO.quantity > 0] + SELECT * + FROM ProductDTO + WHERE NOT(ProductDTO.quantity > 0) AND (ProductDTO.productType = 'COMMON') + --Some row in the table such that: +--The WHERE condition fulfills: + --(F) ProductDTO.quantity > 0 is FALSE + --(T) ProductDTO.productType = 'COMMON' is TRUE + + + \ No newline at end of file diff --git a/st-tdg-test/src/test/resources/gestaoHospital/rules-smoke.xml b/st-tdg-test/src/test/resources/gestaoHospital/rules-smoke.xml new file mode 100644 index 0000000..50780c5 --- /dev/null +++ b/st-tdg-test/src/test/resources/gestaoHospital/rules-smoke.xml @@ -0,0 +1,26 @@ + + +0.0.0 +select * from HospitalDTO where availableBeds=10 +SELECT * + FROM HospitalDTO + WHERE availableBeds = 10 + + 1STFF1.w.1.[WHERE availableBeds = 10] + SELECT * + FROM HospitalDTO + WHERE NOT(availableBeds = 10) + --Some row in the table such that: +--The WHERE condition fulfills: + --(F) availableBeds = 10 is FALSE + + 2STTF1.w.1.[WHERE availableBeds = 10] + SELECT * + FROM HospitalDTO + WHERE (availableBeds = 10) + --Some row in the table such that: +--The WHERE condition fulfills: + --(T) availableBeds = 10 is TRUE + + + \ No newline at end of file diff --git a/st-tdg-test/src/test/resources/gestaoHospital/schema-hospital.json b/st-tdg-test/src/test/resources/gestaoHospital/schema-hospital.json new file mode 100644 index 0000000..51cc0e5 --- /dev/null +++ b/st-tdg-test/src/test/resources/gestaoHospital/schema-hospital.json @@ -0,0 +1,468 @@ +{ + "storetype" : "openapi", + "entities" : [ { + "name" : "HospitalDTO", + "entitytype" : "table", + "attributes" : [ { + "name" : "id", + "datatype" : "string", + "uid" : "true", + "notnull" : "true" + }, { + "name" : "address", + "datatype" : "string", + "notnull" : "true" + }, { + "name" : "availableBeds", + "datatype" : "int32", + "notnull" : "true" + }, { + "name" : "beds", + "datatype" : "int32", + "notnull" : "true" + }, { + "name" : "latitude", + "datatype" : "string", + "notnull" : "true" + }, { + "name" : "longitude", + "datatype" : "string", + "notnull" : "true" + }, { + "name" : "name", + "datatype" : "string", + "notnull" : "true" + } ], + "ddls" : [ { + "command" : "post", + "query" : "/v1/hospitais/" + }, { + "command" : "put", + "query" : "/v1/hospitais/{hospital_id}" + } ] + }, { + "name" : "GeoJsonPoint_coordinates_xa", + "entitytype" : "array", + "attributes" : [ { + "name" : "pk_xa", + "datatype" : "integer", + "uid" : "true", + "notnull" : "true" + }, { + "name" : "fk_xa", + "datatype" : "string", + "notnull" : "true", + "rid" : "GeoJsonPoint.id" + }, { + "name" : "coordinates", + "datatype" : "double", + "notnull" : "true" + } ] + }, { + "name" : "Location_location_xt_coordinates_xa", + "entitytype" : "array", + "attributes" : [ { + "name" : "pk_xa", + "datatype" : "integer", + "uid" : "true", + "notnull" : "true" + }, { + "name" : "fk_xa", + "datatype" : "string", + "notnull" : "true", + "rid" : "Location_location_xt.id" + }, { + "name" : "coordinates", + "datatype" : "double", + "notnull" : "true" + } ] + }, { + "name" : "Location_location_xt", + "entitytype" : "type", + "subtype" : "GeoJsonPoint", + "attributes" : [ { + "name" : "id", + "datatype" : "string", + "notnull" : "true", + "rid" : "GeoJsonPoint.id" + }, { + "name" : "coordinates", + "datatype" : "Location_location_xt_coordinates_xa", + "compositetype" : "array", + "subtype" : "number", + "notnull" : "true" + }, { + "name" : "type", + "datatype" : "string", + "notnull" : "true" + }, { + "name" : "x", + "datatype" : "double", + "notnull" : "true" + }, { + "name" : "y", + "datatype" : "double", + "notnull" : "true" + } ] + }, { + "name" : "Location_position_xt_coordinates_xa", + "entitytype" : "array", + "attributes" : [ { + "name" : "pk_xa", + "datatype" : "integer", + "uid" : "true", + "notnull" : "true" + }, { + "name" : "fk_xa", + "datatype" : "string", + "notnull" : "true", + "rid" : "Location_position_xt.id" + }, { + "name" : "coordinates", + "datatype" : "double", + "notnull" : "true" + } ] + }, { + "name" : "Location_position_xt", + "entitytype" : "type", + "subtype" : "GeoJsonPoint", + "attributes" : [ { + "name" : "id", + "datatype" : "string", + "notnull" : "true", + "rid" : "GeoJsonPoint.id" + }, { + "name" : "coordinates", + "datatype" : "Location_position_xt_coordinates_xa", + "compositetype" : "array", + "subtype" : "number", + "notnull" : "true" + }, { + "name" : "type", + "datatype" : "string", + "notnull" : "true" + }, { + "name" : "x", + "datatype" : "double", + "notnull" : "true" + }, { + "name" : "y", + "datatype" : "double", + "notnull" : "true" + } ] + }, { + "name" : "Patient_location_xt_location_xt_coordinates_xa", + "entitytype" : "array", + "attributes" : [ { + "name" : "pk_xa", + "datatype" : "integer", + "uid" : "true", + "notnull" : "true" + }, { + "name" : "fk_xa", + "datatype" : "string", + "notnull" : "true", + "rid" : "Patient_location_xt_location_xt.id" + }, { + "name" : "coordinates", + "datatype" : "double", + "notnull" : "true" + } ] + }, { + "name" : "Patient_location_xt_location_xt", + "entitytype" : "type", + "subtype" : "GeoJsonPoint", + "attributes" : [ { + "name" : "id", + "datatype" : "string", + "notnull" : "true", + "rid" : "GeoJsonPoint.id" + }, { + "name" : "coordinates", + "datatype" : "Patient_location_xt_location_xt_coordinates_xa", + "compositetype" : "array", + "subtype" : "number", + "notnull" : "true" + }, { + "name" : "type", + "datatype" : "string", + "notnull" : "true" + }, { + "name" : "x", + "datatype" : "double", + "notnull" : "true" + }, { + "name" : "y", + "datatype" : "double", + "notnull" : "true" + } ] + }, { + "name" : "Patient_location_xt_position_xt_coordinates_xa", + "entitytype" : "array", + "attributes" : [ { + "name" : "pk_xa", + "datatype" : "integer", + "uid" : "true", + "notnull" : "true" + }, { + "name" : "fk_xa", + "datatype" : "string", + "notnull" : "true", + "rid" : "Patient_location_xt_position_xt.id" + }, { + "name" : "coordinates", + "datatype" : "double", + "notnull" : "true" + } ] + }, { + "name" : "Patient_location_xt_position_xt", + "entitytype" : "type", + "subtype" : "GeoJsonPoint", + "attributes" : [ { + "name" : "id", + "datatype" : "string", + "notnull" : "true", + "rid" : "GeoJsonPoint.id" + }, { + "name" : "coordinates", + "datatype" : "Patient_location_xt_position_xt_coordinates_xa", + "compositetype" : "array", + "subtype" : "number", + "notnull" : "true" + }, { + "name" : "type", + "datatype" : "string", + "notnull" : "true" + }, { + "name" : "x", + "datatype" : "double", + "notnull" : "true" + }, { + "name" : "y", + "datatype" : "double", + "notnull" : "true" + } ] + }, { + "name" : "Patient_location_xt", + "entitytype" : "type", + "subtype" : "Location", + "attributes" : [ { + "name" : "id", + "datatype" : "string", + "notnull" : "true", + "rid" : "Location.id" + }, { + "name" : "location", + "datatype" : "Patient_location_xt_location_xt", + "compositetype" : "type", + "notnull" : "true" + }, { + "name" : "locationCategory", + "datatype" : "string", + "notnull" : "true", + "checkin" : "HOSPITAL,PATIENT" + }, { + "name" : "name", + "datatype" : "string", + "notnull" : "true" + }, { + "name" : "position", + "datatype" : "Patient_location_xt_position_xt", + "compositetype" : "type", + "notnull" : "true" + }, { + "name" : "referenceId_X", + "datatype" : "string", + "notnull" : "true" + } ] + }, { + "name" : "Patient", + "entitytype" : "table", + "attributes" : [ { + "name" : "id", + "datatype" : "string", + "uid" : "true", + "notnull" : "true" + }, { + "name" : "hospitalDTOId", + "datatype" : "string", + "notnull" : "true", + "rid" : "HospitalDTO.id", + "ridname" : "fk_Patient_hospitalDTOId" + }, { + "name" : "active", + "datatype" : "boolean", + "notnull" : "true" + }, { + "name" : "birthDate", + "datatype" : "date-time", + "notnull" : "true" + }, { + "name" : "cpf", + "datatype" : "string", + "notnull" : "true" + }, { + "name" : "entryDate", + "datatype" : "date-time", + "notnull" : "true" + }, { + "name" : "exitDate", + "datatype" : "date-time", + "notnull" : "true" + }, { + "name" : "gender", + "datatype" : "string", + "notnull" : "true" + }, { + "name" : "location", + "datatype" : "Patient_location_xt", + "compositetype" : "type", + "notnull" : "true" + }, { + "name" : "name", + "datatype" : "string", + "notnull" : "true" + } ], + "ddls" : [ { + "command" : "post", + "query" : "/v1/hospitais/{hospital_id}/pacientes/checkin" + }, { + "command" : "put", + "query" : "/v1/hospitais/{hospital_id}/pacientes/{patientId}" + } ] + }, { + "name" : "GeoJsonPoint", + "entitytype" : "table", + "attributes" : [ { + "name" : "id", + "datatype" : "string", + "uid" : "true", + "notnull" : "true" + }, { + "name" : "coordinates", + "datatype" : "GeoJsonPoint_coordinates_xa", + "compositetype" : "array", + "subtype" : "number", + "notnull" : "true" + }, { + "name" : "type", + "datatype" : "string", + "notnull" : "true" + }, { + "name" : "x", + "datatype" : "double", + "notnull" : "true" + }, { + "name" : "y", + "datatype" : "double", + "notnull" : "true" + } ] + }, { + "name" : "LocationDTO", + "entitytype" : "table", + "attributes" : [ { + "name" : "id", + "datatype" : "string", + "uid" : "true", + "notnull" : "true" + }, { + "name" : "category", + "datatype" : "string", + "notnull" : "true" + }, { + "name" : "latitude", + "datatype" : "string", + "notnull" : "true" + }, { + "name" : "longitude", + "datatype" : "string", + "notnull" : "true" + }, { + "name" : "name", + "datatype" : "string", + "notnull" : "true" + }, { + "name" : "referenceId_X", + "datatype" : "string", + "notnull" : "true" + } ] + }, { + "name" : "ProductDTO", + "entitytype" : "table", + "attributes" : [ { + "name" : "id", + "datatype" : "string", + "uid" : "true", + "notnull" : "true" + }, { + "name" : "hospitalDTOId", + "datatype" : "string", + "notnull" : "true", + "rid" : "HospitalDTO.id", + "ridname" : "fk_ProductDTO_hospitalDTOId" + }, { + "name" : "description", + "datatype" : "string", + "notnull" : "true" + }, { + "name" : "name", + "datatype" : "string", + "notnull" : "true" + }, { + "name" : "productName", + "datatype" : "string", + "notnull" : "true" + }, { + "name" : "productType", + "datatype" : "string", + "notnull" : "true", + "checkin" : "COMMON,BLOOD" + }, { + "name" : "quantity", + "datatype" : "int32", + "notnull" : "true" + } ], + "ddls" : [ { + "command" : "post", + "query" : "/v1/hospitais/estoque" + }, { + "command" : "post", + "query" : "/v1/hospitais/{hospital_id}/estoque" + }, { + "command" : "put", + "query" : "/v1/hospitais/{hospital_id}/estoque/{produto_id}" + } ] + }, { + "name" : "Location", + "entitytype" : "table", + "attributes" : [ { + "name" : "id", + "datatype" : "string", + "uid" : "true", + "notnull" : "true" + }, { + "name" : "location", + "datatype" : "Location_location_xt", + "compositetype" : "type", + "notnull" : "true" + }, { + "name" : "locationCategory", + "datatype" : "string", + "notnull" : "true", + "checkin" : "HOSPITAL,PATIENT" + }, { + "name" : "name", + "datatype" : "string", + "notnull" : "true" + }, { + "name" : "position", + "datatype" : "Location_position_xt", + "compositetype" : "type", + "notnull" : "true" + }, { + "name" : "referenceId_X", + "datatype" : "string", + "notnull" : "true" + } ] + } ] +} \ No newline at end of file diff --git a/st-tdg-test/src/test/resources/gestaoHospital/schema-hospital.md b/st-tdg-test/src/test/resources/gestaoHospital/schema-hospital.md new file mode 100644 index 0000000..25a89d2 --- /dev/null +++ b/st-tdg-test/src/test/resources/gestaoHospital/schema-hospital.md @@ -0,0 +1,31 @@ +classDiagram + GeoJsonPoint *--"*" GeoJsonPoint_coordinates_xa + Location_location_xt *--"*" Location_location_xt_coordinates_xa + GeoJsonPoint <--"*" Location_location_xt + Location_position_xt *--"*" Location_position_xt_coordinates_xa + GeoJsonPoint <--"*" Location_position_xt + Patient_location_xt_location_xt *--"*" Patient_location_xt_location_xt_coordinates_xa + GeoJsonPoint <--"*" Patient_location_xt_location_xt + Patient_location_xt_position_xt *--"*" Patient_location_xt_position_xt_coordinates_xa + GeoJsonPoint <--"*" Patient_location_xt_position_xt + Location <--"*" Patient_location_xt + Patient_location_xt *--"1" Patient_location_xt_location_xt + Patient_location_xt *--"1" Patient_location_xt_position_xt + HospitalDTO <--"*" Patient + Patient *--"1" Patient_location_xt + HospitalDTO <--"*" ProductDTO + Location *--"1" Location_location_xt + Location *--"1" Location_position_xt + Location_location_xt ..|> GeoJsonPoint + Location_position_xt ..|> GeoJsonPoint + Patient_location_xt_location_xt ..|> GeoJsonPoint + Patient_location_xt_position_xt ..|> GeoJsonPoint + Patient_location_xt ..|> Location + class LocationDTO + HospitalDTO: +post(/v1/hospitais/) + HospitalDTO: +put(/v1/hospitais/{hospital_id}) + Patient: +post(/v1/hospitais/{hospital_id}/pacientes/checkin) + Patient: +put(/v1/hospitais/{hospital_id}/pacientes/{patientId}) + ProductDTO: +post(/v1/hospitais/estoque) + ProductDTO: +post(/v1/hospitais/{hospital_id}/estoque) + ProductDTO: +put(/v1/hospitais/{hospital_id}/estoque/{produto_id}) \ No newline at end of file diff --git a/st-tdg-test/src/test/resources/gestaoHospital/schema-hospital.xml b/st-tdg-test/src/test/resources/gestaoHospital/schema-hospital.xml new file mode 100644 index 0000000..08a14ee --- /dev/null +++ b/st-tdg-test/src/test/resources/gestaoHospital/schema-hospital.xml @@ -0,0 +1,123 @@ + + + + + + + + + +/v1/hospitais/ +/v1/hospitais/{hospital_id} +
+ + + + +
+ + + + +
+ + + + + + +
+ + + + +
+ + + + + + +
+ + + + +
+ + + + + + +
+ + + + +
+ + + + + + +
+ + + + + + + +
+ + + + + + + + + + + +/v1/hospitais/{hospital_id}/pacientes/checkin +/v1/hospitais/{hospital_id}/pacientes/{patientId} +
+ + + + + + +
+ + + + + + + +
+ + + + + + + + +/v1/hospitais/estoque +/v1/hospitais/{hospital_id}/estoque +/v1/hospitais/{hospital_id}/estoque/{produto_id} +
+ + + + + + + +
+
\ No newline at end of file diff --git a/st-tdg-test/src/test/resources/logback.xml b/st-tdg-test/src/test/resources/logback.xml new file mode 100644 index 0000000..904df55 --- /dev/null +++ b/st-tdg-test/src/test/resources/logback.xml @@ -0,0 +1,28 @@ + + + + %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n + + + + + target/swagger-petstore-test.log + + %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/st-tdg-test/src/test/resources/market/func-CartsByUser.txt b/st-tdg-test/src/test/resources/market/func-CartsByUser.txt new file mode 100644 index 0000000..06bd3a6 --- /dev/null +++ b/st-tdg-test/src/test/resources/market/func-CartsByUser.txt @@ -0,0 +1,6 @@ +"cart":{"id":1,"userAccount":{"id":1,"email":"us01@email.com","name":"Lucia","active":true,"roles":[]},"cartItems":[],"deliveryIncluded":true,"itemsCost":0.0,"itemsCount":0,"empty":true} +"cart":{"id":2,"userAccount":{"id":2,"email":"lucia@email.com","name":"Sofia","active":true,"roles":[]},"cartItems":[],"deliveryIncluded":true,"itemsCost":0.0,"itemsCount":0,"empty":true} +"contacts":{"id":1,"userAccount":{"id":1,"email":"us01@email.com","name":"Lucia","active":true,"roles":[]},"phone":"+12123456789","address":"2","cityAndRegion":null} +"contacts":{"id":2,"userAccount":{"id":2,"email":"lucia@email.com","name":"Sofia","active":true,"roles":[]},"phone":"+12112345678","address":"102","cityAndRegion":null} +"userAccount":{"id":1,"email":"us01@email.com","name":"Lucia","active":true,"roles":[]} +"userAccount":{"id":2,"email":"lucia@email.com","name":"Sofia","active":true,"roles":[]} diff --git a/st-tdg-test/src/test/resources/market/func-CartsByUserProductQuantity.txt b/st-tdg-test/src/test/resources/market/func-CartsByUserProductQuantity.txt new file mode 100644 index 0000000..fef5855 --- /dev/null +++ b/st-tdg-test/src/test/resources/market/func-CartsByUserProductQuantity.txt @@ -0,0 +1,11 @@ +"cart":{"id":1,"userAccount":{"id":1,"email":"pepe@email.com","name":"Lucia","active":true,"roles":[]},"cartItems":[{"product":{"id":1,"distillery":{"id":1,"title":"Ardbeg","region":{"id":1,"name":"Campbeltown","subtitle":"405","description":"403","color":"402"},"description":"503"},"name":"607","price":60.8,"description":"606","volume":609,"alcohol":60.4,"age":603,"available":true},"quantity":5},{"product":{"id":2,"distillery":{"id":1,"title":"Ardbeg","region":{"id":1,"name":"Campbeltown","subtitle":"405","description":"403","color":"402"},"description":"503"},"name":"707","price":70.8,"description":"706","volume":709,"alcohol":70.4,"age":703,"available":true},"quantity":5}],"deliveryIncluded":true,"itemsCost":658.0,"itemsCount":2,"empty":false} +"cart":{"id":2,"userAccount":{"id":2,"email":"us01@email.com","name":"Sofia","active":true,"roles":[]},"cartItems":[{"product":{"id":1,"distillery":{"id":1,"title":"Ardbeg","region":{"id":1,"name":"Campbeltown","subtitle":"405","description":"403","color":"402"},"description":"503"},"name":"607","price":60.8,"description":"606","volume":609,"alcohol":60.4,"age":603,"available":true},"quantity":5}],"deliveryIncluded":true,"itemsCost":304.0,"itemsCount":1,"empty":false} +"contacts":{"id":1,"userAccount":{"id":1,"email":"pepe@email.com","name":"Lucia","active":true,"roles":[]},"phone":"+12123456789","address":"2","cityAndRegion":null} +"contacts":{"id":2,"userAccount":{"id":2,"email":"us01@email.com","name":"Sofia","active":true,"roles":[]},"phone":"+12112345678","address":"102","cityAndRegion":null} +"distillery":{"id":1,"title":"Ardbeg","region":{"id":1,"name":"Campbeltown","subtitle":"405","description":"403","color":"402"},"description":"503"} +"product":{"id":1,"distillery":{"id":1,"title":"Ardbeg","region":{"id":1,"name":"Campbeltown","subtitle":"405","description":"403","color":"402"},"description":"503"},"name":"607","price":60.8,"description":"606","volume":609,"alcohol":60.4,"age":603,"available":true} +"product":{"id":2,"distillery":{"id":1,"title":"Ardbeg","region":{"id":1,"name":"Campbeltown","subtitle":"405","description":"403","color":"402"},"description":"503"},"name":"707","price":70.8,"description":"706","volume":709,"alcohol":70.4,"age":703,"available":true} +"product":{"id":3,"distillery":{"id":1,"title":"Ardbeg","region":{"id":1,"name":"Campbeltown","subtitle":"405","description":"403","color":"402"},"description":"503"},"name":"807","price":80.8,"description":"806","volume":809,"alcohol":80.4,"age":803,"available":true} +"region":{"id":1,"name":"Campbeltown","subtitle":"405","description":"403","color":"402"} +"userAccount":{"id":1,"email":"pepe@email.com","name":"Lucia","active":true,"roles":[]} +"userAccount":{"id":2,"email":"us01@email.com","name":"Sofia","active":true,"roles":[]} diff --git a/st-tdg-test/src/test/resources/market/func-DistilleryByRegion.txt b/st-tdg-test/src/test/resources/market/func-DistilleryByRegion.txt new file mode 100644 index 0000000..9a126e3 --- /dev/null +++ b/st-tdg-test/src/test/resources/market/func-DistilleryByRegion.txt @@ -0,0 +1,4 @@ +"distillery":{"id":1,"title":"Ardbeg","region":{"id":1,"name":"Campbeltown","subtitle":"5","description":"3","color":"2"},"description":"203"} +"distillery":{"id":2,"title":"Balvenie","region":{"id":2,"name":"Islay","subtitle":"105","description":"103","color":"102"},"description":"303"} +"region":{"id":1,"name":"Campbeltown","subtitle":"5","description":"3","color":"2"} +"region":{"id":2,"name":"Islay","subtitle":"105","description":"103","color":"102"} diff --git a/st-tdg-test/src/test/resources/market/func-DistilleryByTitle.txt b/st-tdg-test/src/test/resources/market/func-DistilleryByTitle.txt new file mode 100644 index 0000000..53420c7 --- /dev/null +++ b/st-tdg-test/src/test/resources/market/func-DistilleryByTitle.txt @@ -0,0 +1,3 @@ +"distillery":{"id":1,"title":"Balvenie","region":{"id":1,"name":"Campbeltown","subtitle":"105","description":"103","color":"102"},"description":"203"} +"distillery":{"id":2,"title":"Ardbeg","region":{"id":1,"name":"Campbeltown","subtitle":"105","description":"103","color":"102"},"description":"303"} +"region":{"id":1,"name":"Campbeltown","subtitle":"105","description":"103","color":"102"} diff --git a/st-tdg-test/src/test/resources/market/func-OrderByUser.txt b/st-tdg-test/src/test/resources/market/func-OrderByUser.txt new file mode 100644 index 0000000..7345611 --- /dev/null +++ b/st-tdg-test/src/test/resources/market/func-OrderByUser.txt @@ -0,0 +1,16 @@ +"cart":{"id":1,"userAccount":{"id":1,"email":"us01@email.com","name":"Lucia","active":true,"roles":[]},"cartItems":[],"deliveryIncluded":true,"itemsCost":0.0,"itemsCount":0,"empty":true} +"cart":{"id":2,"userAccount":{"id":2,"email":"pepe@email.com","name":"Sofia","active":true,"roles":[]},"cartItems":[],"deliveryIncluded":true,"itemsCost":0.0,"itemsCount":0,"empty":true} +"contacts":{"id":1,"userAccount":{"id":1,"email":"us01@email.com","name":"Lucia","active":true,"roles":[]},"phone":"+12123456789","address":"2","cityAndRegion":null} +"contacts":{"id":2,"userAccount":{"id":2,"email":"pepe@email.com","name":"Sofia","active":true,"roles":[]},"phone":"+12112345678","address":"102","cityAndRegion":null} +"distillery":{"id":1,"title":"Ardbeg","region":{"id":1,"name":"Campbeltown","subtitle":"405","description":"403","color":"402"},"description":"503"} +"order":{"id":1,"userAccount":{"id":1,"email":"us01@email.com","name":"Lucia","active":true,"roles":[]},"orderedProducts":[{"pk":{"product":1,"customerOrder":1},"product":{"id":1,"distillery":{"id":1,"title":"Ardbeg","region":{"id":1,"name":"Campbeltown","subtitle":"405","description":"403","color":"402"},"description":"503"},"name":"607","price":60.8,"description":"606","volume":609,"alcohol":60.4,"age":603,"available":true},"quantity":5}],"bill":{"id":1,"totalCost":704.0,"payed":true,"ccNumber":"4030000010001234"},"productsCost":304.0,"deliveryCost":400,"deliveryIncluded":true,"executed":false} +"order":{"id":2,"userAccount":{"id":2,"email":"pepe@email.com","name":"Sofia","active":true,"roles":[]},"orderedProducts":[{"pk":{"product":2,"customerOrder":2},"product":{"id":2,"distillery":{"id":1,"title":"Ardbeg","region":{"id":1,"name":"Campbeltown","subtitle":"405","description":"403","color":"402"},"description":"503"},"name":"707","price":70.8,"description":"706","volume":709,"alcohol":70.4,"age":703,"available":true},"quantity":5},{"pk":{"product":1,"customerOrder":2},"product":{"id":1,"distillery":{"id":1,"title":"Ardbeg","region":{"id":1,"name":"Campbeltown","subtitle":"405","description":"403","color":"402"},"description":"503"},"name":"607","price":60.8,"description":"606","volume":609,"alcohol":60.4,"age":603,"available":true},"quantity":5}],"bill":{"id":2,"totalCost":1058.0,"payed":true,"ccNumber":"4030000010001234"},"productsCost":658.0,"deliveryCost":400,"deliveryIncluded":true,"executed":false} +"orderedProduct":{"pk":{"product":1,"customerOrder":1},"product":{"id":1,"distillery":{"id":1,"title":"Ardbeg","region":{"id":1,"name":"Campbeltown","subtitle":"405","description":"403","color":"402"},"description":"503"},"name":"607","price":60.8,"description":"606","volume":609,"alcohol":60.4,"age":603,"available":true},"quantity":5} +"orderedProduct":{"pk":{"product":2,"customerOrder":2},"product":{"id":2,"distillery":{"id":1,"title":"Ardbeg","region":{"id":1,"name":"Campbeltown","subtitle":"405","description":"403","color":"402"},"description":"503"},"name":"707","price":70.8,"description":"706","volume":709,"alcohol":70.4,"age":703,"available":true},"quantity":5} +"orderedProduct":{"pk":{"product":1,"customerOrder":2},"product":{"id":1,"distillery":{"id":1,"title":"Ardbeg","region":{"id":1,"name":"Campbeltown","subtitle":"405","description":"403","color":"402"},"description":"503"},"name":"607","price":60.8,"description":"606","volume":609,"alcohol":60.4,"age":603,"available":true},"quantity":5} +"product":{"id":1,"distillery":{"id":1,"title":"Ardbeg","region":{"id":1,"name":"Campbeltown","subtitle":"405","description":"403","color":"402"},"description":"503"},"name":"607","price":60.8,"description":"606","volume":609,"alcohol":60.4,"age":603,"available":true} +"product":{"id":2,"distillery":{"id":1,"title":"Ardbeg","region":{"id":1,"name":"Campbeltown","subtitle":"405","description":"403","color":"402"},"description":"503"},"name":"707","price":70.8,"description":"706","volume":709,"alcohol":70.4,"age":703,"available":true} +"product":{"id":3,"distillery":{"id":1,"title":"Ardbeg","region":{"id":1,"name":"Campbeltown","subtitle":"405","description":"403","color":"402"},"description":"503"},"name":"807","price":80.8,"description":"806","volume":809,"alcohol":80.4,"age":803,"available":true} +"region":{"id":1,"name":"Campbeltown","subtitle":"405","description":"403","color":"402"} +"userAccount":{"id":1,"email":"us01@email.com","name":"Lucia","active":true,"roles":[]} +"userAccount":{"id":2,"email":"pepe@email.com","name":"Sofia","active":true,"roles":[]} diff --git a/st-tdg-test/src/test/resources/market/func-ProductsByDistillery.txt b/st-tdg-test/src/test/resources/market/func-ProductsByDistillery.txt new file mode 100644 index 0000000..ffbff9e --- /dev/null +++ b/st-tdg-test/src/test/resources/market/func-ProductsByDistillery.txt @@ -0,0 +1,5 @@ +"distillery":{"id":1,"title":"Balvenie","region":{"id":1,"name":"Campbeltown","subtitle":"105","description":"103","color":"102"},"description":"203"} +"distillery":{"id":2,"title":"Ardbeg","region":{"id":1,"name":"Campbeltown","subtitle":"105","description":"103","color":"102"},"description":"303"} +"product":{"id":1,"distillery":{"id":1,"title":"Balvenie","region":{"id":1,"name":"Campbeltown","subtitle":"105","description":"103","color":"102"},"description":"203"},"name":"407","price":40.8,"description":"406","volume":409,"alcohol":40.4,"age":403,"available":true} +"product":{"id":2,"distillery":{"id":2,"title":"Ardbeg","region":{"id":1,"name":"Campbeltown","subtitle":"105","description":"103","color":"102"},"description":"303"},"name":"507","price":50.8,"description":"506","volume":509,"alcohol":50.4,"age":503,"available":false} +"region":{"id":1,"name":"Campbeltown","subtitle":"105","description":"103","color":"102"} diff --git a/st-tdg-test/src/test/resources/market/func-ProductsByDistilleryAvailable.txt b/st-tdg-test/src/test/resources/market/func-ProductsByDistilleryAvailable.txt new file mode 100644 index 0000000..94c2ce8 --- /dev/null +++ b/st-tdg-test/src/test/resources/market/func-ProductsByDistilleryAvailable.txt @@ -0,0 +1,6 @@ +"distillery":{"id":1,"title":"Ardbeg","region":{"id":1,"name":"Campbeltown","subtitle":"105","description":"103","color":"102"},"description":"203"} +"distillery":{"id":2,"title":"Balvenie","region":{"id":1,"name":"Campbeltown","subtitle":"105","description":"103","color":"102"},"description":"303"} +"product":{"id":1,"distillery":{"id":1,"title":"Ardbeg","region":{"id":1,"name":"Campbeltown","subtitle":"105","description":"103","color":"102"},"description":"203"},"name":"407","price":40.8,"description":"406","volume":409,"alcohol":40.4,"age":403,"available":true} +"product":{"id":2,"distillery":{"id":2,"title":"Balvenie","region":{"id":1,"name":"Campbeltown","subtitle":"105","description":"103","color":"102"},"description":"303"},"name":"507","price":50.8,"description":"506","volume":509,"alcohol":50.4,"age":503,"available":true} +"product":{"id":3,"distillery":{"id":1,"title":"Ardbeg","region":{"id":1,"name":"Campbeltown","subtitle":"105","description":"103","color":"102"},"description":"203"},"name":"607","price":60.8,"description":"606","volume":609,"alcohol":60.4,"age":603,"available":false} +"region":{"id":1,"name":"Campbeltown","subtitle":"105","description":"103","color":"102"} diff --git a/st-tdg-test/src/test/resources/market/func-ProductsByDistilleryNotAvailable.txt b/st-tdg-test/src/test/resources/market/func-ProductsByDistilleryNotAvailable.txt new file mode 100644 index 0000000..7a3de41 --- /dev/null +++ b/st-tdg-test/src/test/resources/market/func-ProductsByDistilleryNotAvailable.txt @@ -0,0 +1,6 @@ +"distillery":{"id":1,"title":"Ardbeg","region":{"id":1,"name":"Campbeltown","subtitle":"105","description":"103","color":"102"},"description":"203"} +"distillery":{"id":2,"title":"Balvenie","region":{"id":1,"name":"Campbeltown","subtitle":"105","description":"103","color":"102"},"description":"303"} +"product":{"id":1,"distillery":{"id":1,"title":"Ardbeg","region":{"id":1,"name":"Campbeltown","subtitle":"105","description":"103","color":"102"},"description":"203"},"name":"407","price":40.8,"description":"406","volume":409,"alcohol":40.4,"age":403,"available":false} +"product":{"id":2,"distillery":{"id":2,"title":"Balvenie","region":{"id":1,"name":"Campbeltown","subtitle":"105","description":"103","color":"102"},"description":"303"},"name":"507","price":50.8,"description":"506","volume":509,"alcohol":50.4,"age":503,"available":false} +"product":{"id":3,"distillery":{"id":1,"title":"Ardbeg","region":{"id":1,"name":"Campbeltown","subtitle":"105","description":"103","color":"102"},"description":"203"},"name":"607","price":60.8,"description":"606","volume":609,"alcohol":60.4,"age":603,"available":true} +"region":{"id":1,"name":"Campbeltown","subtitle":"105","description":"103","color":"102"} diff --git a/st-tdg-test/src/test/resources/market/func-ProductsByDistilleryPrice.txt b/st-tdg-test/src/test/resources/market/func-ProductsByDistilleryPrice.txt new file mode 100644 index 0000000..0af9508 --- /dev/null +++ b/st-tdg-test/src/test/resources/market/func-ProductsByDistilleryPrice.txt @@ -0,0 +1,10 @@ +"distillery":{"id":1,"title":"Ardbeg","region":{"id":1,"name":"Campbeltown","subtitle":"105","description":"103","color":"102"},"description":"203"} +"distillery":{"id":2,"title":"Balvenie","region":{"id":1,"name":"Campbeltown","subtitle":"105","description":"103","color":"102"},"description":"303"} +"product":{"id":1,"distillery":{"id":1,"title":"Ardbeg","region":{"id":1,"name":"Campbeltown","subtitle":"105","description":"103","color":"102"},"description":"203"},"name":"407","price":6.0,"description":"406","volume":409,"alcohol":40.4,"age":403,"available":true} +"product":{"id":2,"distillery":{"id":2,"title":"Balvenie","region":{"id":1,"name":"Campbeltown","subtitle":"105","description":"103","color":"102"},"description":"303"},"name":"507","price":6.0,"description":"506","volume":509,"alcohol":50.4,"age":503,"available":false} +"product":{"id":3,"distillery":{"id":1,"title":"Ardbeg","region":{"id":1,"name":"Campbeltown","subtitle":"105","description":"103","color":"102"},"description":"203"},"name":"607","price":101.0,"description":"606","volume":609,"alcohol":60.4,"age":603,"available":true} +"product":{"id":4,"distillery":{"id":1,"title":"Ardbeg","region":{"id":1,"name":"Campbeltown","subtitle":"105","description":"103","color":"102"},"description":"203"},"name":"707","price":100.0,"description":"706","volume":709,"alcohol":70.4,"age":703,"available":false} +"product":{"id":5,"distillery":{"id":1,"title":"Ardbeg","region":{"id":1,"name":"Campbeltown","subtitle":"105","description":"103","color":"102"},"description":"203"},"name":"807","price":99.0,"description":"806","volume":809,"alcohol":80.4,"age":803,"available":true} +"product":{"id":6,"distillery":{"id":1,"title":"Ardbeg","region":{"id":1,"name":"Campbeltown","subtitle":"105","description":"103","color":"102"},"description":"203"},"name":"907","price":5.0,"description":"906","volume":909,"alcohol":90.4,"age":903,"available":false} +"product":{"id":7,"distillery":{"id":1,"title":"Ardbeg","region":{"id":1,"name":"Campbeltown","subtitle":"105","description":"103","color":"102"},"description":"203"},"name":"1007","price":4.0,"description":"1006","volume":1009,"alcohol":5.3,"age":1003,"available":true} +"region":{"id":1,"name":"Campbeltown","subtitle":"105","description":"103","color":"102"} diff --git a/st-tdg-test/src/test/resources/market/func-ProductsByDistilleryPriceAge.txt b/st-tdg-test/src/test/resources/market/func-ProductsByDistilleryPriceAge.txt new file mode 100644 index 0000000..ffd9807 --- /dev/null +++ b/st-tdg-test/src/test/resources/market/func-ProductsByDistilleryPriceAge.txt @@ -0,0 +1,12 @@ +"distillery":{"id":1,"title":"Ardbeg","region":{"id":1,"name":"Campbeltown","subtitle":"105","description":"103","color":"102"},"description":"203"} +"distillery":{"id":2,"title":"Balvenie","region":{"id":1,"name":"Campbeltown","subtitle":"105","description":"103","color":"102"},"description":"303"} +"product":{"id":1,"distillery":{"id":1,"title":"Ardbeg","region":{"id":1,"name":"Campbeltown","subtitle":"105","description":"103","color":"102"},"description":"203"},"name":"407","price":6.0,"description":"406","volume":409,"alcohol":40.4,"age":12,"available":true} +"product":{"id":2,"distillery":{"id":2,"title":"Balvenie","region":{"id":1,"name":"Campbeltown","subtitle":"105","description":"103","color":"102"},"description":"303"},"name":"507","price":6.0,"description":"506","volume":509,"alcohol":50.4,"age":12,"available":false} +"product":{"id":3,"distillery":{"id":1,"title":"Ardbeg","region":{"id":1,"name":"Campbeltown","subtitle":"105","description":"103","color":"102"},"description":"203"},"name":"607","price":101.0,"description":"606","volume":609,"alcohol":60.4,"age":12,"available":true} +"product":{"id":4,"distillery":{"id":1,"title":"Ardbeg","region":{"id":1,"name":"Campbeltown","subtitle":"105","description":"103","color":"102"},"description":"203"},"name":"707","price":100.0,"description":"706","volume":709,"alcohol":70.4,"age":12,"available":false} +"product":{"id":5,"distillery":{"id":1,"title":"Ardbeg","region":{"id":1,"name":"Campbeltown","subtitle":"105","description":"103","color":"102"},"description":"203"},"name":"807","price":99.0,"description":"806","volume":809,"alcohol":80.4,"age":12,"available":true} +"product":{"id":6,"distillery":{"id":1,"title":"Ardbeg","region":{"id":1,"name":"Campbeltown","subtitle":"105","description":"103","color":"102"},"description":"203"},"name":"907","price":5.0,"description":"906","volume":909,"alcohol":90.4,"age":12,"available":false} +"product":{"id":7,"distillery":{"id":1,"title":"Ardbeg","region":{"id":1,"name":"Campbeltown","subtitle":"105","description":"103","color":"102"},"description":"203"},"name":"1007","price":4.0,"description":"1006","volume":1009,"alcohol":5.3,"age":12,"available":true} +"product":{"id":8,"distillery":{"id":1,"title":"Ardbeg","region":{"id":1,"name":"Campbeltown","subtitle":"105","description":"103","color":"102"},"description":"203"},"name":"1107","price":99.0,"description":"1106","volume":1109,"alcohol":15.3,"age":13,"available":false} +"product":{"id":9,"distillery":{"id":1,"title":"Ardbeg","region":{"id":1,"name":"Campbeltown","subtitle":"105","description":"103","color":"102"},"description":"203"},"name":"1207","price":99.0,"description":"1206","volume":1209,"alcohol":25.3,"age":11,"available":true} +"region":{"id":1,"name":"Campbeltown","subtitle":"105","description":"103","color":"102"} diff --git a/st-tdg-test/src/test/resources/market/func-UsersByName.txt b/st-tdg-test/src/test/resources/market/func-UsersByName.txt new file mode 100644 index 0000000..ab5b448 --- /dev/null +++ b/st-tdg-test/src/test/resources/market/func-UsersByName.txt @@ -0,0 +1,6 @@ +"cart":{"id":1,"userAccount":{"id":1,"email":"us01@email.com","name":"Lucia","active":true,"roles":[]},"cartItems":[],"deliveryIncluded":true,"itemsCost":0.0,"itemsCount":0,"empty":true} +"cart":{"id":2,"userAccount":{"id":2,"email":"us101@email.com","name":"Pepe","active":true,"roles":[]},"cartItems":[],"deliveryIncluded":true,"itemsCost":0.0,"itemsCount":0,"empty":true} +"contacts":{"id":1,"userAccount":{"id":1,"email":"us01@email.com","name":"Lucia","active":true,"roles":[]},"phone":"+12123456789","address":"102","cityAndRegion":null} +"contacts":{"id":2,"userAccount":{"id":2,"email":"us101@email.com","name":"Pepe","active":true,"roles":[]},"phone":"+12112345678","address":"202","cityAndRegion":null} +"userAccount":{"id":1,"email":"us01@email.com","name":"Lucia","active":true,"roles":[]} +"userAccount":{"id":2,"email":"us101@email.com","name":"Pepe","active":true,"roles":[]} diff --git a/st-tdg-test/src/test/resources/market/qagrow-livebackid-DistilleryByRegion.txt b/st-tdg-test/src/test/resources/market/qagrow-livebackid-DistilleryByRegion.txt new file mode 100644 index 0000000..90ec85b --- /dev/null +++ b/st-tdg-test/src/test/resources/market/qagrow-livebackid-DistilleryByRegion.txt @@ -0,0 +1,4 @@ +"distillery":{"id":1,"title":"Ardbeg","region":{"id":1,"name":"Campbeltown","subtitle":"5","description":"3","color":"2"},"description":"203"} +"distillery":{"id":2,"title":"Balvenie","region":{"id":2,"name":"Highland","subtitle":"105","description":"103","color":"102"},"description":"303"} +"region":{"id":1,"name":"Campbeltown","subtitle":"5","description":"3","color":"2"} +"region":{"id":2,"name":"Highland","subtitle":"105","description":"103","color":"102"} diff --git a/st-tdg-test/src/test/resources/market/qagrow-livebackid-DistilleryByTitle.txt b/st-tdg-test/src/test/resources/market/qagrow-livebackid-DistilleryByTitle.txt new file mode 100644 index 0000000..9d632c2 --- /dev/null +++ b/st-tdg-test/src/test/resources/market/qagrow-livebackid-DistilleryByTitle.txt @@ -0,0 +1,3 @@ +"distillery":{"id":1,"title":"Ardbeg","region":{"id":1,"name":"Campbeltown","subtitle":"5","description":"3","color":"2"},"description":"103"} +"distillery":{"id":2,"title":"Balvenie","region":{"id":1,"name":"Campbeltown","subtitle":"5","description":"3","color":"2"},"description":"203"} +"region":{"id":1,"name":"Campbeltown","subtitle":"5","description":"3","color":"2"} diff --git a/st-tdg-test/src/test/resources/market/qagrow-livebackid-RegionByName.txt b/st-tdg-test/src/test/resources/market/qagrow-livebackid-RegionByName.txt new file mode 100644 index 0000000..b8c4775 --- /dev/null +++ b/st-tdg-test/src/test/resources/market/qagrow-livebackid-RegionByName.txt @@ -0,0 +1,2 @@ +"region":{"id":1,"name":"Highland","subtitle":"105","description":"103","color":"102"} +"region":{"id":2,"name":"Campbeltown","subtitle":"205","description":"203","color":"202"} diff --git a/st-tdg-test/src/test/resources/market/qagrow-local-product.txt b/st-tdg-test/src/test/resources/market/qagrow-local-product.txt new file mode 100644 index 0000000..a4d5bb1 --- /dev/null +++ b/st-tdg-test/src/test/resources/market/qagrow-local-product.txt @@ -0,0 +1,5 @@ +"RegionDTORes":{"id":1,"color":"2","description":"3","name":"0","subtitle":"5"} +"DistilleryDTORes":{"id":1,"region":"0","description":"103","title":"0"} +"ProductDTORes":{"productId":1,"distillery":"0","age":11,"alcohol":20.4,"available":true,"description":"206","name":"207","price":20.8,"volume":209} +"ProductDTORes":{"productId":2,"distillery":"0","age":10,"alcohol":30.4,"available":false,"description":"306","name":"307","price":30.8,"volume":309} +"ProductDTORes":{"productId":3,"distillery":"0","age":9,"alcohol":40.4,"available":true,"description":"406","name":"407","price":40.8,"volume":409} \ No newline at end of file diff --git a/st-tdg-test/src/test/resources/market/qagrow-local-userbyemail.txt b/st-tdg-test/src/test/resources/market/qagrow-local-userbyemail.txt new file mode 100644 index 0000000..e03a7b0 --- /dev/null +++ b/st-tdg-test/src/test/resources/market/qagrow-local-userbyemail.txt @@ -0,0 +1,2 @@ +"UserDTORes":{"email":"1","address":"2","name":"3","password":"4","phone":"5"} +"UserDTORes":{"email":"pepe@email.com","address":"102","name":"103","password":"104","phone":"105"} \ No newline at end of file diff --git a/st-tdg-test/src/test/resources/market/qagrow-local-userbyname.txt b/st-tdg-test/src/test/resources/market/qagrow-local-userbyname.txt new file mode 100644 index 0000000..96ace00 --- /dev/null +++ b/st-tdg-test/src/test/resources/market/qagrow-local-userbyname.txt @@ -0,0 +1,2 @@ +"UserDTORes":{"email":"0","address":"2","name":"1","password":"4","phone":"5"} +"UserDTORes":{"email":"1","address":"102","name":"Pepe","password":"104","phone":"105"} \ No newline at end of file diff --git a/st-tdg-test/src/test/resources/market/schema-marketWithoutArrays.json b/st-tdg-test/src/test/resources/market/schema-marketWithoutArrays.json new file mode 100644 index 0000000..c54c397 --- /dev/null +++ b/st-tdg-test/src/test/resources/market/schema-marketWithoutArrays.json @@ -0,0 +1,480 @@ +{ + "storetype" : "openapi", + "entities" : [ { + "name" : "CartDTO", + "entitytype" : "table", + "attributes" : [ { + "name" : "user", + "datatype" : "string", + "uid" : "true", + "notnull" : "true", + "rid" : "UserDTOReq.email", + "ridname" : "fk_CartDTO_user" + }, { + "name" : "deliveryCost", + "datatype" : "int32", + "notnull" : "true" + }, { + "name" : "deliveryIncluded", + "datatype" : "boolean", + "notnull" : "true" + }, { + "name" : "empty", + "datatype" : "boolean", + "notnull" : "true" + }, { + "name" : "productsCost", + "datatype" : "double", + "notnull" : "true" + }, { + "name" : "totalCost", + "datatype" : "double", + "notnull" : "true" + }, { + "name" : "totalItems", + "datatype" : "int32", + "notnull" : "true" + } ] + }, { + "name" : "CartItemDTOReq", + "entitytype" : "table", + "attributes" : [ { + "name" : "user", + "datatype" : "string", + "uid" : "true", + "notnull" : "true", + "rid" : "CartDTO.user", + "ridname" : "fk_CartItemDTOReq_user" + }, { + "name" : "productId", + "datatype" : "int64", + "uid" : "true", + "notnull" : "true", + "rid" : "ProductDTOReq.productId", + "ridname" : "fk_CartItemDTOReq_productId" + }, { + "name" : "quantity", + "datatype" : "int32", + "notnull" : "true" + } ], + "ddls" : [ { + "command" : "put", + "query" : "/customer/cart" + } ] + }, { + "name" : "CartItemDTORes", + "entitytype" : "table", + "attributes" : [ { + "name" : "productId", + "datatype" : "int64", + "uid" : "true", + "notnull" : "true", + "rid" : "ProductDTORes.productId", + "ridname" : "fk_CartItemDTORes_productId" + }, { + "name" : "user", + "datatype" : "string", + "uid" : "true", + "notnull" : "true", + "rid" : "CartDTO.user", + "ridname" : "fk_CartItemDTORes_user" + }, { + "name" : "quantity", + "datatype" : "int32", + "notnull" : "true" + } ] + }, { + "name" : "ContactsDTOReq", + "entitytype" : "table", + "attributes" : [ { + "name" : "user", + "datatype" : "string", + "notnull" : "true", + "rid" : "UserDTOReq.email", + "ridname" : "fk_ContactsDTOReq_user" + }, { + "name" : "address", + "datatype" : "string", + "size" : "100", + "notnull" : "true" + }, { + "name" : "phone", + "datatype" : "string", + "size" : "20", + "notnull" : "true" + } ], + "ddls" : [ { + "command" : "put", + "query" : "/customer/contacts" + } ] + }, { + "name" : "ContactsDTORes", + "entitytype" : "table", + "attributes" : [ { + "name" : "user", + "datatype" : "string", + "notnull" : "true", + "rid" : "UserDTORes.email", + "ridname" : "fk_ContactsDTORes_user" + }, { + "name" : "address", + "datatype" : "string", + "size" : "100", + "notnull" : "true" + }, { + "name" : "phone", + "datatype" : "string", + "size" : "20", + "notnull" : "true" + } ] + }, { + "name" : "CreditCardDTO", + "entitytype" : "table", + "attributes" : [ { + "name" : "ccNumber", + "datatype" : "string", + "notnull" : "true" + } ], + "ddls" : [ { + "command" : "post", + "query" : "/customer/cart/pay" + } ] + }, { + "name" : "DistilleryDTOReq", + "entitytype" : "table", + "attributes" : [ { + "name" : "id", + "datatype" : "int64", + "uid" : "true", + "notnull" : "true" + }, { + "name" : "region", + "datatype" : "string", + "notnull" : "true", + "rid" : "RegionDTOReq.name", + "ridname" : "fk_DistilleryDTOReq_region" + }, { + "name" : "description", + "datatype" : "string", + "notnull" : "true" + }, { + "name" : "title", + "datatype" : "string", + "notnull" : "true" + } ], + "ddls" : [ { + "command" : "post", + "query" : "/distillerydto" + } ] + }, { + "name" : "DistilleryDTORes", + "entitytype" : "table", + "attributes" : [ { + "name" : "id", + "datatype" : "int64", + "uid" : "true", + "notnull" : "true" + }, { + "name" : "region", + "datatype" : "string", + "notnull" : "true", + "rid" : "RegionDTORes.name", + "ridname" : "fk_DistilleryDTORes_region" + }, { + "name" : "description", + "datatype" : "string", + "notnull" : "true" + }, { + "name" : "title", + "datatype" : "string", + "notnull" : "true" + } ] + }, { + "name" : "OrderDTO", + "entitytype" : "table", + "attributes" : [ { + "name" : "id", + "datatype" : "int64", + "uid" : "true", + "notnull" : "true" + }, { + "name" : "ccNumber", + "datatype" : "string", + "notnull" : "true", + "rid" : "CreditCardDTO.ccNumber", + "ridname" : "fk_OrderDTO_ccNumber" + }, { + "name" : "userAccount", + "datatype" : "string", + "notnull" : "true", + "rid" : "UserDTOReq.email", + "ridname" : "fk_OrderDTO_userAccount" + }, { + "name" : "billNumber", + "datatype" : "int32", + "notnull" : "true" + }, { + "name" : "dateCreated", + "datatype" : "date-time", + "notnull" : "true" + }, { + "name" : "deliveryCost", + "datatype" : "int32", + "notnull" : "true" + }, { + "name" : "deliveryIncluded", + "datatype" : "boolean", + "notnull" : "true" + }, { + "name" : "executed", + "datatype" : "boolean", + "notnull" : "true" + }, { + "name" : "payed", + "datatype" : "boolean", + "notnull" : "true" + }, { + "name" : "productsCost", + "datatype" : "double", + "notnull" : "true" + }, { + "name" : "totalCost", + "datatype" : "double", + "notnull" : "true" + } ] + }, { + "name" : "ProductDTOReq", + "entitytype" : "table", + "attributes" : [ { + "name" : "productId", + "datatype" : "int64", + "uid" : "true", + "notnull" : "true" + }, { + "name" : "distillery", + "datatype" : "string", + "notnull" : "true", + "rid" : "DistilleryDTOReq.title", + "ridname" : "fk_ProductDTOReq_distillery" + }, { + "name" : "age", + "datatype" : "int32", + "notnull" : "true" + }, { + "name" : "alcohol", + "datatype" : "float", + "notnull" : "true" + }, { + "name" : "available", + "datatype" : "boolean", + "notnull" : "true" + }, { + "name" : "description", + "datatype" : "string", + "notnull" : "true" + }, { + "name" : "name", + "datatype" : "string", + "notnull" : "true" + }, { + "name" : "price", + "datatype" : "double", + "notnull" : "true" + }, { + "name" : "volume", + "datatype" : "int32", + "notnull" : "true" + } ], + "checks" : [ { + "attribute" : "age", + "name" : "chkmax_ProductDTOReq_age", + "constraint" : "age<=2E+3" + }, { + "attribute" : "alcohol", + "name" : "chkmin_ProductDTOReq_alcohol", + "constraint" : "alcohol>=1" + }, { + "attribute" : "alcohol", + "name" : "chkmax_ProductDTOReq_alcohol", + "constraint" : "alcohol<=96" + } ], + "ddls" : [ { + "command" : "post", + "query" : "/products/productdto" + } ] + }, { + "name" : "ProductDTORes", + "entitytype" : "table", + "attributes" : [ { + "name" : "productId", + "datatype" : "int64", + "uid" : "true", + "notnull" : "true" + }, { + "name" : "distillery", + "datatype" : "string", + "notnull" : "true", + "rid" : "DistilleryDTORes.title", + "ridname" : "fk_ProductDTORes_distillery" + }, { + "name" : "age", + "datatype" : "int32", + "notnull" : "true" + }, { + "name" : "alcohol", + "datatype" : "float", + "notnull" : "true" + }, { + "name" : "available", + "datatype" : "boolean", + "notnull" : "true" + }, { + "name" : "description", + "datatype" : "string", + "notnull" : "true" + }, { + "name" : "name", + "datatype" : "string", + "notnull" : "true" + }, { + "name" : "price", + "datatype" : "double", + "notnull" : "true" + }, { + "name" : "volume", + "datatype" : "int32", + "notnull" : "true" + } ], + "checks" : [ { + "attribute" : "age", + "name" : "chkmax_ProductDTORes_age", + "constraint" : "age<=2E+3" + }, { + "attribute" : "alcohol", + "name" : "chkmin_ProductDTORes_alcohol", + "constraint" : "alcohol>=1" + }, { + "attribute" : "alcohol", + "name" : "chkmax_ProductDTORes_alcohol", + "constraint" : "alcohol<=96" + } ] + }, { + "name" : "RegionDTOReq", + "entitytype" : "table", + "attributes" : [ { + "name" : "id", + "datatype" : "int64", + "uid" : "true", + "notnull" : "true" + }, { + "name" : "color", + "datatype" : "string", + "notnull" : "true" + }, { + "name" : "description", + "datatype" : "string", + "notnull" : "true" + }, { + "name" : "name", + "datatype" : "string", + "notnull" : "true" + }, { + "name" : "subtitle", + "datatype" : "string", + "notnull" : "true" + } ], + "ddls" : [ { + "command" : "post", + "query" : "/regiondto" + } ] + }, { + "name" : "RegionDTORes", + "entitytype" : "table", + "attributes" : [ { + "name" : "id", + "datatype" : "int64", + "uid" : "true", + "notnull" : "true" + }, { + "name" : "color", + "datatype" : "string", + "notnull" : "true" + }, { + "name" : "description", + "datatype" : "string", + "notnull" : "true" + }, { + "name" : "name", + "datatype" : "string", + "notnull" : "true" + }, { + "name" : "subtitle", + "datatype" : "string", + "notnull" : "true" + } ] + }, { + "name" : "UserDTOReq", + "entitytype" : "table", + "attributes" : [ { + "name" : "email", + "datatype" : "string", + "size" : "50", + "uid" : "true", + "notnull" : "true" + }, { + "name" : "address", + "datatype" : "string", + "size" : "100", + "notnull" : "true" + }, { + "name" : "name", + "datatype" : "string", + "size" : "50", + "notnull" : "true" + }, { + "name" : "password", + "datatype" : "string", + "size" : "50", + "notnull" : "true" + }, { + "name" : "phone", + "datatype" : "string", + "size" : "20", + "notnull" : "true" + } ], + "ddls" : [ { + "command" : "post", + "query" : "/register" + } ] + }, { + "name" : "UserDTORes", + "entitytype" : "table", + "attributes" : [ { + "name" : "email", + "datatype" : "string", + "size" : "50", + "uid" : "true", + "notnull" : "true" + }, { + "name" : "address", + "datatype" : "string", + "size" : "100", + "notnull" : "true" + }, { + "name" : "name", + "datatype" : "string", + "size" : "50", + "notnull" : "true" + }, { + "name" : "password", + "datatype" : "string", + "size" : "50", + "notnull" : "true" + }, { + "name" : "phone", + "datatype" : "string", + "size" : "20", + "notnull" : "true" + } ] + } ] +} \ No newline at end of file diff --git a/st-tdg-test/src/test/resources/market/schema-marketWithoutArrays.md b/st-tdg-test/src/test/resources/market/schema-marketWithoutArrays.md new file mode 100644 index 0000000..805ee2b --- /dev/null +++ b/st-tdg-test/src/test/resources/market/schema-marketWithoutArrays.md @@ -0,0 +1,21 @@ +classDiagram + UserDTOReq <--"*" CartDTO + CartDTO <--"*" CartItemDTOReq + ProductDTOReq <--"*" CartItemDTOReq + ProductDTORes <--"*" CartItemDTORes + CartDTO <--"*" CartItemDTORes + UserDTOReq <--"*" ContactsDTOReq + UserDTORes <--"*" ContactsDTORes + RegionDTOReq <--"*" DistilleryDTOReq + RegionDTORes <--"*" DistilleryDTORes + CreditCardDTO <--"*" OrderDTO + UserDTOReq <--"*" OrderDTO + DistilleryDTOReq <--"*" ProductDTOReq + DistilleryDTORes <--"*" ProductDTORes + CartItemDTOReq: +put(/customer/cart) + ContactsDTOReq: +put(/customer/contacts) + CreditCardDTO: +post(/customer/cart/pay) + DistilleryDTOReq: +post(/distillerydto) + ProductDTOReq: +post(/products/productdto) + RegionDTOReq: +post(/regiondto) + UserDTOReq: +post(/register) \ No newline at end of file diff --git a/st-tdg-test/src/test/resources/market/schema-marketWithoutArrays.xml b/st-tdg-test/src/test/resources/market/schema-marketWithoutArrays.xml new file mode 100644 index 0000000..eed8d35 --- /dev/null +++ b/st-tdg-test/src/test/resources/market/schema-marketWithoutArrays.xml @@ -0,0 +1,122 @@ + + + + + + + + + +
+ + + + +/customer/cart +
+ + + + +
+ + + + +/customer/contacts +
+ + + + +
+ + +/customer/cart/pay +
+ + + + + +/distillerydto +
+ + + + + +
+ + + + + + + + + + + + +
+ + + + + + + + + + +age<=2E+3 +alcohol>=1 +alcohol<=96 +/products/productdto +
+ + + + + + + + + + +age<=2E+3 +alcohol>=1 +alcohol<=96 +
+ + + + + + +/regiondto +
+ + + + + + +
+ + + + + + +/register +
+ + + + + + +
+
\ No newline at end of file diff --git a/st-tdg-test/src/test/resources/petstore/datagen-live-pet-by-category-status-dict.txt b/st-tdg-test/src/test/resources/petstore/datagen-live-pet-by-category-status-dict.txt new file mode 100644 index 0000000..73b83b9 --- /dev/null +++ b/st-tdg-test/src/test/resources/petstore/datagen-live-pet-by-category-status-dict.txt @@ -0,0 +1,8 @@ +"category":{"id":1,"name":"Dogs"} +"category":{"id":2,"name":"Tiger"} +"category":{"id":3,"name":"Lion"} +"pet":{"id":1,"category":{"id":1,"name":"Dogs"},"name":"Max","photoUrls":["http://localhost/photos/000103.jpg"],"tags":[{"id":3,"name":"Puppy"}],"status":"available"} +"pet":{"id":2,"category":{"id":1,"name":"Dogs"},"name":"Luna","photoUrls":["http://localhost/photos/000503.jpg"],"tags":[{"id":403,"name":"Young"}],"status":"sold"} +"pet":{"id":3,"category":{"id":2,"name":"Tiger"},"name":"Charlie","photoUrls":["http://localhost/photos/000803.jpg"],"tags":[{"id":703,"name":"Old"}],"status":"available"} +"pet":{"id":4,"category":{"id":1,"name":"Dogs"},"name":"Bella","photoUrls":["http://localhost/photos/001203.jpg"],"tags":[],"status":"available"} +"pet":{"id":5,"category":{"id":1,"name":"Dogs"},"name":"Cooper","photoUrls":[],"tags":[{"id":1403,"name":"Puppy-1"}],"status":"available"} diff --git a/st-tdg-test/src/test/resources/petstore/datagen-live-pet-by-category-status.txt b/st-tdg-test/src/test/resources/petstore/datagen-live-pet-by-category-status.txt new file mode 100644 index 0000000..67fb61c --- /dev/null +++ b/st-tdg-test/src/test/resources/petstore/datagen-live-pet-by-category-status.txt @@ -0,0 +1,8 @@ +"category":{"id":1,"name":"Dogs"} +"category":{"id":2,"name":"1"} +"category":{"id":3,"name":"1102"} +"pet":{"id":1,"category":{"id":1,"name":"Dogs"},"name":"302","photoUrls":["103"],"tags":[{"id":3,"name":"4"}],"status":"available"} +"pet":{"id":2,"category":{"id":1,"name":"Dogs"},"name":"602","photoUrls":["503"],"tags":[{"id":403,"name":"404"}],"status":"sold"} +"pet":{"id":3,"category":{"id":2,"name":"1"},"name":"1002","photoUrls":["803"],"tags":[{"id":703,"name":"704"}],"status":"available"} +"pet":{"id":4,"category":{"id":1,"name":"Dogs"},"name":"1302","photoUrls":["1203"],"tags":[],"status":"available"} +"pet":{"id":5,"category":{"id":1,"name":"Dogs"},"name":"1502","photoUrls":[],"tags":[{"id":1403,"name":"1404"}],"status":"available"} diff --git a/st-tdg-test/src/test/resources/petstore/datagen-live-pet0-by-category-status.txt b/st-tdg-test/src/test/resources/petstore/datagen-live-pet0-by-category-status.txt new file mode 100644 index 0000000..1f67c50 --- /dev/null +++ b/st-tdg-test/src/test/resources/petstore/datagen-live-pet0-by-category-status.txt @@ -0,0 +1,3 @@ +"pet":{"id":1,"category":{"id":3,"name":"Dogs"},"name":"2","photoUrls":[],"tags":[],"status":"available"} +"pet":{"id":101,"category":{"id":103,"name":"1"},"name":"102","photoUrls":[],"tags":[],"status":"available"} +"pet":{"id":201,"category":{"id":203,"name":"Dogs"},"name":"202","photoUrls":[],"tags":[],"status":"sold"} diff --git a/st-tdg-test/src/test/resources/petstore/datagen-live-pet1-by-category-status.txt b/st-tdg-test/src/test/resources/petstore/datagen-live-pet1-by-category-status.txt new file mode 100644 index 0000000..0bebb1d --- /dev/null +++ b/st-tdg-test/src/test/resources/petstore/datagen-live-pet1-by-category-status.txt @@ -0,0 +1,6 @@ +"category":{"id":1,"name":"Dogs"} +"category":{"id":2,"name":"1"} +"category":{"id":3,"name":"502"} +"pet":{"id":1,"category":{"id":1,"name":"Dogs"},"name":"102","photoUrls":[],"tags":[],"status":"available"} +"pet":{"id":2,"category":{"id":1,"name":"Dogs"},"name":"202","photoUrls":[],"tags":[],"status":"sold"} +"pet":{"id":3,"category":{"id":2,"name":"1"},"name":"402","photoUrls":[],"tags":[],"status":"available"} diff --git a/st-tdg-test/src/test/resources/petstore/datagen-live-placed-pet-orders-by-address-order-status-dict.txt b/st-tdg-test/src/test/resources/petstore/datagen-live-placed-pet-orders-by-address-order-status-dict.txt new file mode 100644 index 0000000..1355df4 --- /dev/null +++ b/st-tdg-test/src/test/resources/petstore/datagen-live-placed-pet-orders-by-address-order-status-dict.txt @@ -0,0 +1,4 @@ +"category":{"id":1,"name":"Dogs"} +"pet":{"id":1,"category":{"id":1,"name":"Dogs"},"name":"Max","photoUrls":["http://localhost/photos/000103.jpg"],"tags":[{"id":3,"name":"Puppy"}],"status":"available"} +"order":{"id":1,"petId":1,"customerId":1,"quantity":604,"shipDate":"2007-01-11T00:00:00.000+00:00","status":"delivered","complete":true} +"customer":{"id":1,"username":"James Smith","address":[{"street":"Main St","city":"Springfield","state":"California","zip":"000406"}]} diff --git a/st-tdg-test/src/test/resources/petstore/datagen-live-placed-pet-orders-by-address-order-status.txt b/st-tdg-test/src/test/resources/petstore/datagen-live-placed-pet-orders-by-address-order-status.txt new file mode 100644 index 0000000..f7ad42e --- /dev/null +++ b/st-tdg-test/src/test/resources/petstore/datagen-live-placed-pet-orders-by-address-order-status.txt @@ -0,0 +1,4 @@ +"category":{"id":1,"name":"Dogs"} +"pet":{"id":1,"category":{"id":1,"name":"Dogs"},"name":"302","photoUrls":["103"],"tags":[{"id":3,"name":"4"}],"status":"available"} +"order":{"id":1,"petId":1,"customerId":1,"quantity":604,"shipDate":"2007-01-11T00:00:00.000+00:00","status":"delivered","complete":true} +"customer":{"id":1,"username":"502","address":[{"street":"403","city":"404","state":"405","zip":"406"}]} diff --git a/st-tdg-test/src/test/resources/petstore/datagen-live-placed-pet0-orders-by-category-order-status.txt b/st-tdg-test/src/test/resources/petstore/datagen-live-placed-pet0-orders-by-category-order-status.txt new file mode 100644 index 0000000..c0c8491 --- /dev/null +++ b/st-tdg-test/src/test/resources/petstore/datagen-live-placed-pet0-orders-by-category-order-status.txt @@ -0,0 +1,8 @@ +"pet":{"id":1,"category":{"id":103,"name":"Dogs"},"name":"102","photoUrls":[],"tags":[],"status":"available"} +"pet":{"id":2,"category":{"id":303,"name":"1"},"name":"302","photoUrls":[],"tags":[],"status":"sold"} +"pet":{"id":3,"category":{"id":703,"name":"Dogs"},"name":"702","photoUrls":[],"tags":[],"status":"available"} +"order":{"id":1,"petId":1,"customerId":1,"quantity":204,"shipDate":"2007-01-07T00:00:00.000+00:00","status":"placed","complete":true} +"order":{"id":2,"petId":2,"customerId":1,"quantity":404,"shipDate":"2007-01-09T00:00:00.000+00:00","status":"placed","complete":true} +"order":{"id":3,"petId":1,"customerId":1,"quantity":504,"shipDate":"2007-01-10T00:00:00.000+00:00","status":"delivered","complete":false} +"customer":{"id":1,"username":"2","address":[]} +"customer":{"id":2,"username":"602","address":[]} diff --git a/st-tdg-test/src/test/resources/petstore/datagen-live-smoke.txt b/st-tdg-test/src/test/resources/petstore/datagen-live-smoke.txt new file mode 100644 index 0000000..a939092 --- /dev/null +++ b/st-tdg-test/src/test/resources/petstore/datagen-live-smoke.txt @@ -0,0 +1,2 @@ +"category":{"id":1,"name":"Dogs"} +"category":{"id":101,"name":"102"} diff --git a/st-tdg-test/src/test/resources/petstore/datagen-livebackid-pet-by-category-status.txt b/st-tdg-test/src/test/resources/petstore/datagen-livebackid-pet-by-category-status.txt new file mode 100644 index 0000000..7d56ba2 --- /dev/null +++ b/st-tdg-test/src/test/resources/petstore/datagen-livebackid-pet-by-category-status.txt @@ -0,0 +1,8 @@ +"category":{"id":10,"name":"Dogs"} +"category":{"id":20,"name":"1"} +"category":{"id":30,"name":"1102"} +"pet":{"id":1000,"category":{"id":10,"name":"Dogs"},"name":"302","photoUrls":["203"],"tags":[{"id":103,"name":"104"}],"status":"available"} +"pet":{"id":2000,"category":{"id":10,"name":"Dogs"},"name":"602","photoUrls":["503"],"tags":[{"id":403,"name":"404"}],"status":"sold"} +"pet":{"id":3000,"category":{"id":20,"name":"1"},"name":"1002","photoUrls":["903"],"tags":[{"id":803,"name":"804"}],"status":"available"} +"pet":{"id":4000,"category":{"id":10,"name":"Dogs"},"name":"1302","photoUrls":["1203"],"tags":[],"status":"available"} +"pet":{"id":5000,"category":{"id":10,"name":"Dogs"},"name":"1502","photoUrls":[],"tags":[{"id":1403,"name":"1404"}],"status":"available"} diff --git a/st-tdg-test/src/test/resources/petstore/datagen-local-pet-by-category-status-dict.txt b/st-tdg-test/src/test/resources/petstore/datagen-local-pet-by-category-status-dict.txt new file mode 100644 index 0000000..f799789 --- /dev/null +++ b/st-tdg-test/src/test/resources/petstore/datagen-local-pet-by-category-status-dict.txt @@ -0,0 +1,8 @@ +"Category":{"id":1,"name":"Dogs"} +"Pet":{"id":1,"name":"Max","category":{"id":1,"name":"Dogs"},"photoUrls":["http://localhost/photos/000103.jpg"],"tags":[{"id":3,"name":"Puppy"}],"status":"available"} +"Pet":{"id":2,"name":"Luna","category":{"id":1,"name":"Dogs"},"photoUrls":["http://localhost/photos/000503.jpg"],"tags":[{"id":403,"name":"Young"}],"status":"sold"} +"Category":{"id":2,"name":"Tiger"} +"Pet":{"id":3,"name":"Charlie","category":{"id":2,"name":"Tiger"},"photoUrls":["http://localhost/photos/000803.jpg"],"tags":[{"id":703,"name":"Old"}],"status":"available"} +"Category":{"id":3,"name":"Lion"} +"Pet":{"id":4,"name":"Bella","category":{"id":1,"name":"Dogs"},"photoUrls":["http://localhost/photos/001203.jpg"],"tags":[],"status":"available"} +"Pet":{"id":5,"name":"Cooper","category":{"id":1,"name":"Dogs"},"photoUrls":[],"tags":[{"id":1403,"name":"Puppy-1"}],"status":"available"} \ No newline at end of file diff --git a/st-tdg-test/src/test/resources/petstore/datagen-local-pet-by-category-status.txt b/st-tdg-test/src/test/resources/petstore/datagen-local-pet-by-category-status.txt new file mode 100644 index 0000000..dc8a445 --- /dev/null +++ b/st-tdg-test/src/test/resources/petstore/datagen-local-pet-by-category-status.txt @@ -0,0 +1,8 @@ +"Category":{"id":1,"name":"Dogs"} +"Pet":{"id":1,"name":"302","category":{"id":1,"name":"Dogs"},"photoUrls":["103"],"tags":[{"id":3,"name":"4"}],"status":"available"} +"Pet":{"id":2,"name":"602","category":{"id":1,"name":"Dogs"},"photoUrls":["503"],"tags":[{"id":403,"name":"404"}],"status":"sold"} +"Category":{"id":2,"name":"1"} +"Pet":{"id":3,"name":"1002","category":{"id":2,"name":"1"},"photoUrls":["803"],"tags":[{"id":703,"name":"704"}],"status":"available"} +"Category":{"id":3,"name":"1102"} +"Pet":{"id":4,"name":"1302","category":{"id":1,"name":"Dogs"},"photoUrls":["1203"],"tags":[],"status":"available"} +"Pet":{"id":5,"name":"1502","category":{"id":1,"name":"Dogs"},"photoUrls":[],"tags":[{"id":1403,"name":"1404"}],"status":"available"} \ No newline at end of file diff --git a/st-tdg-test/src/test/resources/petstore/datagen-local-pet0-by-category-status.txt b/st-tdg-test/src/test/resources/petstore/datagen-local-pet0-by-category-status.txt new file mode 100644 index 0000000..53b577d --- /dev/null +++ b/st-tdg-test/src/test/resources/petstore/datagen-local-pet0-by-category-status.txt @@ -0,0 +1,3 @@ +"Pet0":{"id":1,"name":"2","category":{"id":3,"name":"Dogs"},"status":"available"} +"Pet0":{"id":101,"name":"102","category":{"id":103,"name":"1"},"status":"available"} +"Pet0":{"id":201,"name":"202","category":{"id":203,"name":"Dogs"},"status":"sold"} \ No newline at end of file diff --git a/st-tdg-test/src/test/resources/petstore/datagen-local-pet1-by-category-status.txt b/st-tdg-test/src/test/resources/petstore/datagen-local-pet1-by-category-status.txt new file mode 100644 index 0000000..89ce3b7 --- /dev/null +++ b/st-tdg-test/src/test/resources/petstore/datagen-local-pet1-by-category-status.txt @@ -0,0 +1,6 @@ +"Category":{"id":1,"name":"Dogs"} +"Pet1":{"id":1,"name":"102","category":{"id":1,"name":"Dogs"},"status":"available"} +"Pet1":{"id":2,"name":"202","category":{"id":1,"name":"Dogs"},"status":"sold"} +"Category":{"id":2,"name":"1"} +"Pet1":{"id":3,"name":"402","category":{"id":2,"name":"1"},"status":"available"} +"Category":{"id":3,"name":"502"} \ No newline at end of file diff --git a/st-tdg-test/src/test/resources/petstore/datagen-local-placed-pet-orders-by-address-order-status-dict.txt b/st-tdg-test/src/test/resources/petstore/datagen-local-placed-pet-orders-by-address-order-status-dict.txt new file mode 100644 index 0000000..9f67c8c --- /dev/null +++ b/st-tdg-test/src/test/resources/petstore/datagen-local-placed-pet-orders-by-address-order-status-dict.txt @@ -0,0 +1,4 @@ +"Category":{"id":1,"name":"Dogs"} +"Pet":{"id":1,"name":"Max","category":{"id":1,"name":"Dogs"},"photoUrls":["http://localhost/photos/000103.jpg"],"tags":[{"id":3,"name":"Puppy"}],"status":"available"} +"Customer":{"id":1,"username":"James Smith","address":[{"street":"Main St","city":"Springfield","state":"California","zip":"000406"}]} +"Order":{"id":1,"petId":1,"customerId":1,"quantity":604,"shipDate":"2007-01-11","status":"delivered","complete":true} \ No newline at end of file diff --git a/st-tdg-test/src/test/resources/petstore/datagen-local-placed-pet-orders-by-address-order-status.txt b/st-tdg-test/src/test/resources/petstore/datagen-local-placed-pet-orders-by-address-order-status.txt new file mode 100644 index 0000000..21d9de5 --- /dev/null +++ b/st-tdg-test/src/test/resources/petstore/datagen-local-placed-pet-orders-by-address-order-status.txt @@ -0,0 +1,4 @@ +"Category":{"id":1,"name":"Dogs"} +"Pet":{"id":1,"name":"302","category":{"id":1,"name":"Dogs"},"photoUrls":["103"],"tags":[{"id":3,"name":"4"}],"status":"available"} +"Customer":{"id":1,"username":"502","address":[{"street":"403","city":"404","state":"405","zip":"406"}]} +"Order":{"id":1,"petId":1,"customerId":1,"quantity":604,"shipDate":"2007-01-11","status":"delivered","complete":true} \ No newline at end of file diff --git a/st-tdg-test/src/test/resources/petstore/datagen-local-placed-pet0-orders-by-category-order-status.txt b/st-tdg-test/src/test/resources/petstore/datagen-local-placed-pet0-orders-by-category-order-status.txt new file mode 100644 index 0000000..dffb61d --- /dev/null +++ b/st-tdg-test/src/test/resources/petstore/datagen-local-placed-pet0-orders-by-category-order-status.txt @@ -0,0 +1,8 @@ +"Customer0":{"id":1,"username":"2"} +"Pet0":{"id":1,"name":"102","category":{"id":103,"name":"Dogs"},"status":"available"} +"Order0":{"id":1,"petId":1,"customerId":1,"quantity":204,"shipDate":"2007-01-07","status":"placed","complete":true} +"Pet0":{"id":2,"name":"302","category":{"id":303,"name":"1"},"status":"sold"} +"Order0":{"id":2,"petId":2,"customerId":1,"quantity":404,"shipDate":"2007-01-09","status":"placed","complete":true} +"Order0":{"id":3,"petId":1,"customerId":1,"quantity":504,"shipDate":"2007-01-10","status":"delivered","complete":false} +"Customer0":{"id":2,"username":"602"} +"Pet0":{"id":3,"name":"702","category":{"id":703,"name":"Dogs"},"status":"available"} \ No newline at end of file diff --git a/st-tdg-test/src/test/resources/petstore/datagen-local-smoke.txt b/st-tdg-test/src/test/resources/petstore/datagen-local-smoke.txt new file mode 100644 index 0000000..c388edd --- /dev/null +++ b/st-tdg-test/src/test/resources/petstore/datagen-local-smoke.txt @@ -0,0 +1,2 @@ +"Category":{"id":1,"name":"Dogs"} +"Category":{"id":101,"name":"102"} \ No newline at end of file diff --git a/st-tdg-test/src/test/resources/petstore/qagrow-live-pet-by-category-status-dict.txt b/st-tdg-test/src/test/resources/petstore/qagrow-live-pet-by-category-status-dict.txt new file mode 100644 index 0000000..0190677 --- /dev/null +++ b/st-tdg-test/src/test/resources/petstore/qagrow-live-pet-by-category-status-dict.txt @@ -0,0 +1,8 @@ +"category":{"id":1,"name":"Dogs"} +"category":{"id":2,"name":"Tiger"} +"category":{"id":3,"name":"Lion"} +"pet":{"id":1,"category":{"id":1,"name":"Dogs"},"name":"Max","photoUrls":["http://localhost/photos/000303.jpg"],"tags":[{"id":703,"name":"Puppy"}],"status":"available"} +"pet":{"id":2,"category":{"id":2,"name":"Tiger"},"name":"Luna","photoUrls":["http://localhost/photos/000403.jpg"],"tags":[{"id":803,"name":"Young"}],"status":"available"} +"pet":{"id":3,"category":{"id":1,"name":"Dogs"},"name":"Charlie","photoUrls":["http://localhost/photos/000503.jpg"],"tags":[{"id":903,"name":"Old"}],"status":"pending"} +"pet":{"id":4,"category":{"id":1,"name":"Dogs"},"name":"Bella","photoUrls":["http://localhost/photos/000603.jpg"],"tags":[],"status":"available"} +"pet":{"id":5,"category":{"id":1,"name":"Dogs"},"name":"Cooper","photoUrls":[],"tags":[{"id":1003,"name":"Puppy-1"}],"status":"available"} diff --git a/st-tdg-test/src/test/resources/petstore/qagrow-live-pet-by-category-status.txt b/st-tdg-test/src/test/resources/petstore/qagrow-live-pet-by-category-status.txt new file mode 100644 index 0000000..3e9c45c --- /dev/null +++ b/st-tdg-test/src/test/resources/petstore/qagrow-live-pet-by-category-status.txt @@ -0,0 +1,8 @@ +"category":{"id":1,"name":"Dogs"} +"category":{"id":2,"name":"1"} +"category":{"id":3,"name":"202"} +"pet":{"id":1,"category":{"id":1,"name":"Dogs"},"name":"1102","photoUrls":["303"],"tags":[{"id":703,"name":"704"}],"status":"available"} +"pet":{"id":2,"category":{"id":2,"name":"1"},"name":"1202","photoUrls":["403"],"tags":[{"id":803,"name":"804"}],"status":"available"} +"pet":{"id":3,"category":{"id":1,"name":"Dogs"},"name":"1302","photoUrls":["503"],"tags":[{"id":903,"name":"904"}],"status":"pending"} +"pet":{"id":4,"category":{"id":1,"name":"Dogs"},"name":"1402","photoUrls":["603"],"tags":[],"status":"available"} +"pet":{"id":5,"category":{"id":1,"name":"Dogs"},"name":"1502","photoUrls":[],"tags":[{"id":1003,"name":"1004"}],"status":"available"} diff --git a/st-tdg-test/src/test/resources/petstore/qagrow-live-pet-by-url-tag.txt b/st-tdg-test/src/test/resources/petstore/qagrow-live-pet-by-url-tag.txt new file mode 100644 index 0000000..f56eeca --- /dev/null +++ b/st-tdg-test/src/test/resources/petstore/qagrow-live-pet-by-url-tag.txt @@ -0,0 +1,5 @@ +"category":{"id":1,"name":"2"} +"category":{"id":2,"name":"102"} +"pet":{"id":1,"category":{"id":1,"name":"2"},"name":"802","photoUrls":["URL","1"],"tags":[{"id":503,"name":"kitty"},{"id":603,"name":"1"}],"status":"available"} +"pet":{"id":2,"category":{"id":1,"name":"2"},"name":"902","photoUrls":["URL"],"tags":[],"status":"pending"} +"pet":{"id":3,"category":{"id":1,"name":"2"},"name":"1002","photoUrls":[],"tags":[{"id":703,"name":"kitty"}],"status":"sold"} diff --git a/st-tdg-test/src/test/resources/petstore/qagrow-live-pet0-by-category-status.txt b/st-tdg-test/src/test/resources/petstore/qagrow-live-pet0-by-category-status.txt new file mode 100644 index 0000000..0e98ec8 --- /dev/null +++ b/st-tdg-test/src/test/resources/petstore/qagrow-live-pet0-by-category-status.txt @@ -0,0 +1,3 @@ +"pet":{"id":1,"category":{"id":3,"name":"Dogs"},"name":"2","photoUrls":[],"tags":[],"status":"available"} +"pet":{"id":2,"category":{"id":103,"name":"1"},"name":"102","photoUrls":[],"tags":[],"status":"available"} +"pet":{"id":3,"category":{"id":203,"name":"Dogs"},"name":"202","photoUrls":[],"tags":[],"status":"pending"} diff --git a/st-tdg-test/src/test/resources/petstore/qagrow-live-pet1-by-category-status.txt b/st-tdg-test/src/test/resources/petstore/qagrow-live-pet1-by-category-status.txt new file mode 100644 index 0000000..1836afb --- /dev/null +++ b/st-tdg-test/src/test/resources/petstore/qagrow-live-pet1-by-category-status.txt @@ -0,0 +1,6 @@ +"category":{"id":1,"name":"Dogs"} +"category":{"id":2,"name":"1"} +"category":{"id":3,"name":"202"} +"pet":{"id":1,"category":{"id":1,"name":"Dogs"},"name":"302","photoUrls":[],"tags":[],"status":"available"} +"pet":{"id":2,"category":{"id":2,"name":"1"},"name":"402","photoUrls":[],"tags":[],"status":"available"} +"pet":{"id":3,"category":{"id":1,"name":"Dogs"},"name":"502","photoUrls":[],"tags":[],"status":"pending"} diff --git a/st-tdg-test/src/test/resources/petstore/qagrow-live-placed-pet-orders-by-address-order-status-dict.txt b/st-tdg-test/src/test/resources/petstore/qagrow-live-placed-pet-orders-by-address-order-status-dict.txt new file mode 100644 index 0000000..b25ff84 --- /dev/null +++ b/st-tdg-test/src/test/resources/petstore/qagrow-live-placed-pet-orders-by-address-order-status-dict.txt @@ -0,0 +1,14 @@ +"category":{"id":1,"name":"Tiger"} +"category":{"id":2,"name":"Lion"} +"pet":{"id":1,"category":{"id":1,"name":"Tiger"},"name":"Max","photoUrls":["http://localhost/photos/000203.jpg"],"tags":[{"id":503,"name":"Puppy"}],"status":"available"} +"pet":{"id":2,"category":{"id":1,"name":"Tiger"},"name":"Luna","photoUrls":["http://localhost/photos/000303.jpg"],"tags":[{"id":603,"name":"Young"}],"status":"pending"} +"pet":{"id":3,"category":{"id":1,"name":"Tiger"},"name":"Charlie","photoUrls":["http://localhost/photos/000403.jpg"],"tags":[],"status":"sold"} +"pet":{"id":4,"category":{"id":1,"name":"Tiger"},"name":"Bella","photoUrls":[],"tags":[{"id":703,"name":"Old"}],"status":"available"} +"order":{"id":1,"petId":1,"customerId":1,"quantity":1804,"shipDate":"2007-01-23T00:00:00.000+00:00","status":"placed","complete":true} +"order":{"id":2,"petId":1,"customerId":1,"quantity":1904,"shipDate":"2007-01-24T00:00:00.000+00:00","status":"approved","complete":false} +"order":{"id":3,"petId":1,"customerId":2,"quantity":2004,"shipDate":"2007-01-25T00:00:00.000+00:00","status":"placed","complete":true} +"order":{"id":4,"petId":3,"customerId":1,"quantity":2104,"shipDate":"2007-01-26T00:00:00.000+00:00","status":"placed","complete":false} +"order":{"id":5,"petId":4,"customerId":1,"quantity":2204,"shipDate":"2007-01-27T00:00:00.000+00:00","status":"placed","complete":true} +"customer":{"id":1,"username":"James Smith","address":[{"street":"Main St","city":"Springfield","state":"California","zip":"99999"},{"street":"Broadway","city":"Franklin","state":"Texas","zip":"000001"}]} +"customer":{"id":2,"username":"Mary Johnson","address":[]} +"customer":{"id":3,"username":"Robert Williams","address":[{"street":"Park Ave","city":"Greenville","state":"Florida","zip":"99999"}]} diff --git a/st-tdg-test/src/test/resources/petstore/qagrow-live-placed-pet-orders-by-address-order-status.txt b/st-tdg-test/src/test/resources/petstore/qagrow-live-placed-pet-orders-by-address-order-status.txt new file mode 100644 index 0000000..910a321 --- /dev/null +++ b/st-tdg-test/src/test/resources/petstore/qagrow-live-placed-pet-orders-by-address-order-status.txt @@ -0,0 +1,14 @@ +"category":{"id":1,"name":"2"} +"category":{"id":2,"name":"102"} +"pet":{"id":1,"category":{"id":1,"name":"2"},"name":"802","photoUrls":["203"],"tags":[{"id":503,"name":"504"}],"status":"available"} +"pet":{"id":2,"category":{"id":1,"name":"2"},"name":"902","photoUrls":["303"],"tags":[{"id":603,"name":"604"}],"status":"pending"} +"pet":{"id":3,"category":{"id":1,"name":"2"},"name":"1002","photoUrls":["403"],"tags":[],"status":"sold"} +"pet":{"id":4,"category":{"id":1,"name":"2"},"name":"1102","photoUrls":[],"tags":[{"id":703,"name":"704"}],"status":"available"} +"order":{"id":1,"petId":1,"customerId":1,"quantity":1804,"shipDate":"2007-01-23T00:00:00.000+00:00","status":"placed","complete":true} +"order":{"id":2,"petId":1,"customerId":1,"quantity":1904,"shipDate":"2007-01-24T00:00:00.000+00:00","status":"approved","complete":false} +"order":{"id":3,"petId":1,"customerId":2,"quantity":2004,"shipDate":"2007-01-25T00:00:00.000+00:00","status":"placed","complete":true} +"order":{"id":4,"petId":3,"customerId":1,"quantity":2104,"shipDate":"2007-01-26T00:00:00.000+00:00","status":"placed","complete":false} +"order":{"id":5,"petId":4,"customerId":1,"quantity":2204,"shipDate":"2007-01-27T00:00:00.000+00:00","status":"placed","complete":true} +"customer":{"id":1,"username":"1502","address":[{"street":"1203","city":"1204","state":"1205","zip":"99999"},{"street":"1303","city":"1304","state":"1305","zip":"1"}]} +"customer":{"id":2,"username":"1602","address":[]} +"customer":{"id":3,"username":"1702","address":[{"street":"1403","city":"1404","state":"1405","zip":"99999"}]} diff --git a/st-tdg-test/src/test/resources/petstore/qagrow-live-placed-pet-orders-by-category-order-status.txt b/st-tdg-test/src/test/resources/petstore/qagrow-live-placed-pet-orders-by-category-order-status.txt new file mode 100644 index 0000000..6840c0f --- /dev/null +++ b/st-tdg-test/src/test/resources/petstore/qagrow-live-placed-pet-orders-by-category-order-status.txt @@ -0,0 +1,17 @@ +"category":{"id":1,"name":"Dogs"} +"category":{"id":2,"name":"1"} +"category":{"id":3,"name":"202"} +"pet":{"id":1,"category":{"id":1,"name":"Dogs"},"name":"1102","photoUrls":["303"],"tags":[{"id":703,"name":"704"}],"status":"available"} +"pet":{"id":2,"category":{"id":2,"name":"1"},"name":"1202","photoUrls":["403"],"tags":[{"id":803,"name":"804"}],"status":"pending"} +"pet":{"id":3,"category":{"id":1,"name":"Dogs"},"name":"1302","photoUrls":["503"],"tags":[{"id":903,"name":"904"}],"status":"sold"} +"pet":{"id":4,"category":{"id":1,"name":"Dogs"},"name":"1402","photoUrls":["603"],"tags":[],"status":"available"} +"pet":{"id":5,"category":{"id":1,"name":"Dogs"},"name":"1502","photoUrls":[],"tags":[{"id":1003,"name":"1004"}],"status":"pending"} +"order":{"id":1,"petId":1,"customerId":1,"quantity":2104,"shipDate":"2007-01-26T00:00:00.000+00:00","status":"placed","complete":false} +"order":{"id":2,"petId":2,"customerId":1,"quantity":2204,"shipDate":"2007-01-27T00:00:00.000+00:00","status":"placed","complete":true} +"order":{"id":3,"petId":1,"customerId":1,"quantity":2304,"shipDate":"2007-01-28T00:00:00.000+00:00","status":"approved","complete":false} +"order":{"id":4,"petId":1,"customerId":2,"quantity":2404,"shipDate":"2007-01-29T00:00:00.000+00:00","status":"placed","complete":true} +"order":{"id":5,"petId":4,"customerId":1,"quantity":2504,"shipDate":"2007-01-30T00:00:00.000+00:00","status":"placed","complete":false} +"order":{"id":6,"petId":5,"customerId":1,"quantity":2604,"shipDate":"2007-02-01T00:00:00.000+00:00","status":"placed","complete":true} +"customer":{"id":1,"username":"1802","address":[{"street":"1603","city":"1604","state":"1605","zip":"1606"}]} +"customer":{"id":2,"username":"1902","address":[]} +"customer":{"id":3,"username":"2002","address":[{"street":"1703","city":"1704","state":"1705","zip":"1706"}]} diff --git a/st-tdg-test/src/test/resources/petstore/qagrow-live-placed-pet0-orders-by-category-order-status.txt b/st-tdg-test/src/test/resources/petstore/qagrow-live-placed-pet0-orders-by-category-order-status.txt new file mode 100644 index 0000000..913202e --- /dev/null +++ b/st-tdg-test/src/test/resources/petstore/qagrow-live-placed-pet0-orders-by-category-order-status.txt @@ -0,0 +1,8 @@ +"pet":{"id":1,"category":{"id":203,"name":"Dogs"},"name":"202","photoUrls":[],"tags":[],"status":"pending"} +"pet":{"id":2,"category":{"id":303,"name":"1"},"name":"302","photoUrls":[],"tags":[],"status":"sold"} +"pet":{"id":3,"category":{"id":403,"name":"Dogs"},"name":"402","photoUrls":[],"tags":[],"status":"available"} +"order":{"id":1,"petId":1,"customerId":1,"quantity":504,"shipDate":"2007-01-10T00:00:00.000+00:00","status":"placed","complete":false} +"order":{"id":2,"petId":2,"customerId":1,"quantity":604,"shipDate":"2007-01-11T00:00:00.000+00:00","status":"placed","complete":true} +"order":{"id":3,"petId":1,"customerId":1,"quantity":704,"shipDate":"2007-01-12T00:00:00.000+00:00","status":"approved","complete":false} +"customer":{"id":1,"username":"2","address":[]} +"customer":{"id":2,"username":"102","address":[]} diff --git a/st-tdg-test/src/test/resources/petstore/qagrow-live-smoke.txt b/st-tdg-test/src/test/resources/petstore/qagrow-live-smoke.txt new file mode 100644 index 0000000..def7a0e --- /dev/null +++ b/st-tdg-test/src/test/resources/petstore/qagrow-live-smoke.txt @@ -0,0 +1,2 @@ +"category":{"id":1,"name":"1"} +"category":{"id":2,"name":"Dogs"} diff --git a/st-tdg-test/src/test/resources/petstore/qagrow-live-total-pet-orders-by-address.txt b/st-tdg-test/src/test/resources/petstore/qagrow-live-total-pet-orders-by-address.txt new file mode 100644 index 0000000..4a5afc0 --- /dev/null +++ b/st-tdg-test/src/test/resources/petstore/qagrow-live-total-pet-orders-by-address.txt @@ -0,0 +1,17 @@ +"category":{"id":1,"name":"2"} +"category":{"id":2,"name":"102"} +"category":{"id":3,"name":"202"} +"pet":{"id":1,"category":{"id":1,"name":"2"},"name":"1102","photoUrls":["303"],"tags":[{"id":703,"name":"704"}],"status":"available"} +"pet":{"id":2,"category":{"id":2,"name":"102"},"name":"1202","photoUrls":["403"],"tags":[{"id":803,"name":"804"}],"status":"pending"} +"pet":{"id":3,"category":{"id":2,"name":"102"},"name":"1302","photoUrls":["503"],"tags":[{"id":903,"name":"904"}],"status":"sold"} +"pet":{"id":4,"category":{"id":1,"name":"2"},"name":"1402","photoUrls":["603"],"tags":[],"status":"available"} +"pet":{"id":5,"category":{"id":1,"name":"2"},"name":"1502","photoUrls":[],"tags":[{"id":1003,"name":"1004"}],"status":"pending"} +"order":{"id":1,"petId":1,"customerId":1,"quantity":2104,"shipDate":"2007-01-26T00:00:00.000+00:00","status":"placed","complete":false} +"order":{"id":2,"petId":1,"customerId":1,"quantity":2204,"shipDate":"2007-01-27T00:00:00.000+00:00","status":"approved","complete":true} +"order":{"id":3,"petId":2,"customerId":1,"quantity":2304,"shipDate":"2007-01-28T00:00:00.000+00:00","status":"approved","complete":false} +"order":{"id":4,"petId":1,"customerId":2,"quantity":2404,"shipDate":"2007-01-29T00:00:00.000+00:00","status":"approved","complete":true} +"order":{"id":5,"petId":4,"customerId":1,"quantity":2504,"shipDate":"2007-01-30T00:00:00.000+00:00","status":"approved","complete":false} +"order":{"id":6,"petId":5,"customerId":1,"quantity":2604,"shipDate":"2007-02-01T00:00:00.000+00:00","status":"approved","complete":true} +"customer":{"id":1,"username":"1802","address":[{"street":"1603","city":"1604","state":"1605","zip":"0"}]} +"customer":{"id":2,"username":"1902","address":[]} +"customer":{"id":3,"username":"2002","address":[{"street":"1703","city":"1704","state":"1705","zip":"1"}]} diff --git a/st-tdg-test/src/test/resources/petstore/qagrow-live-total-pets-by-address.txt b/st-tdg-test/src/test/resources/petstore/qagrow-live-total-pets-by-address.txt new file mode 100644 index 0000000..04750d9 --- /dev/null +++ b/st-tdg-test/src/test/resources/petstore/qagrow-live-total-pets-by-address.txt @@ -0,0 +1,18 @@ +"category":{"id":1,"name":"2"} +"category":{"id":2,"name":"102"} +"category":{"id":3,"name":"202"} +"pet":{"id":1,"category":{"id":1,"name":"2"},"name":"1102","photoUrls":["303"],"tags":[{"id":703,"name":"704"}],"status":"available"} +"pet":{"id":2,"category":{"id":2,"name":"102"},"name":"1202","photoUrls":["403"],"tags":[{"id":803,"name":"804"}],"status":"pending"} +"pet":{"id":3,"category":{"id":2,"name":"102"},"name":"1302","photoUrls":["603"],"tags":[],"status":"sold"} +"pet":{"id":4,"category":{"id":1,"name":"2"},"name":"1402","photoUrls":["503"],"tags":[{"id":903,"name":"904"}],"status":"available"} +"pet":{"id":5,"category":{"id":1,"name":"2"},"name":"1502","photoUrls":[],"tags":[{"id":1003,"name":"1004"}],"status":"pending"} +"order":{"id":1,"petId":1,"customerId":1,"quantity":2104,"shipDate":"2007-01-26T00:00:00.000+00:00","status":"placed","complete":false} +"order":{"id":2,"petId":1,"customerId":1,"quantity":0,"shipDate":"2007-01-27T00:00:00.000+00:00","status":"approved","complete":true} +"order":{"id":3,"petId":2,"customerId":1,"quantity":0,"shipDate":"2007-01-28T00:00:00.000+00:00","status":"approved","complete":false} +"order":{"id":4,"petId":1,"customerId":1,"quantity":0,"shipDate":"2007-01-29T00:00:00.000+00:00","status":"approved","complete":true} +"order":{"id":5,"petId":1,"customerId":2,"quantity":2504,"shipDate":"2007-01-30T00:00:00.000+00:00","status":"approved","complete":false} +"order":{"id":6,"petId":3,"customerId":1,"quantity":2604,"shipDate":"2007-02-01T00:00:00.000+00:00","status":"approved","complete":true} +"order":{"id":7,"petId":5,"customerId":1,"quantity":2704,"shipDate":"2007-02-02T00:00:00.000+00:00","status":"approved","complete":false} +"customer":{"id":1,"username":"1802","address":[{"street":"1603","city":"1604","state":"1605","zip":"0"}]} +"customer":{"id":2,"username":"1902","address":[]} +"customer":{"id":3,"username":"2002","address":[{"street":"1703","city":"1704","state":"1705","zip":"1"}]} diff --git a/st-tdg-test/src/test/resources/petstore/qagrow-livebackid-pet-by-category-status.txt b/st-tdg-test/src/test/resources/petstore/qagrow-livebackid-pet-by-category-status.txt new file mode 100644 index 0000000..31fbccf --- /dev/null +++ b/st-tdg-test/src/test/resources/petstore/qagrow-livebackid-pet-by-category-status.txt @@ -0,0 +1,8 @@ +"category":{"id":10,"name":"Dogs"} +"category":{"id":20,"name":"1"} +"category":{"id":30,"name":"202"} +"pet":{"id":1000,"category":{"id":10,"name":"Dogs"},"name":"1102","photoUrls":["303"],"tags":[{"id":703,"name":"704"}],"status":"available"} +"pet":{"id":2000,"category":{"id":20,"name":"1"},"name":"1202","photoUrls":["403"],"tags":[{"id":803,"name":"804"}],"status":"available"} +"pet":{"id":3000,"category":{"id":10,"name":"Dogs"},"name":"1302","photoUrls":["503"],"tags":[{"id":903,"name":"904"}],"status":"pending"} +"pet":{"id":4000,"category":{"id":10,"name":"Dogs"},"name":"1402","photoUrls":["603"],"tags":[],"status":"available"} +"pet":{"id":5000,"category":{"id":10,"name":"Dogs"},"name":"1502","photoUrls":[],"tags":[{"id":1003,"name":"1004"}],"status":"available"} diff --git a/st-tdg-test/src/test/resources/petstore/qagrow-local-pet-by-category-status-dict.txt b/st-tdg-test/src/test/resources/petstore/qagrow-local-pet-by-category-status-dict.txt new file mode 100644 index 0000000..17dbd15 --- /dev/null +++ b/st-tdg-test/src/test/resources/petstore/qagrow-local-pet-by-category-status-dict.txt @@ -0,0 +1,8 @@ +"Category":{"id":1,"name":"Dogs"} +"Category":{"id":2,"name":"Tiger"} +"Category":{"id":3,"name":"Lion"} +"Pet":{"id":1,"name":"Max","category":{"id":1,"name":"Dogs"},"photoUrls":["http://localhost/photos/000303.jpg"],"tags":[{"id":703,"name":"Puppy"}],"status":"available"} +"Pet":{"id":2,"name":"Luna","category":{"id":2,"name":"Tiger"},"photoUrls":["http://localhost/photos/000403.jpg"],"tags":[{"id":803,"name":"Young"}],"status":"available"} +"Pet":{"id":3,"name":"Charlie","category":{"id":1,"name":"Dogs"},"photoUrls":["http://localhost/photos/000503.jpg"],"tags":[{"id":903,"name":"Old"}],"status":"pending"} +"Pet":{"id":4,"name":"Bella","category":{"id":1,"name":"Dogs"},"photoUrls":["http://localhost/photos/000603.jpg"],"tags":[],"status":"available"} +"Pet":{"id":5,"name":"Cooper","category":{"id":1,"name":"Dogs"},"photoUrls":[],"tags":[{"id":1003,"name":"Puppy-1"}],"status":"available"} \ No newline at end of file diff --git a/st-tdg-test/src/test/resources/petstore/qagrow-local-pet-by-category-status.txt b/st-tdg-test/src/test/resources/petstore/qagrow-local-pet-by-category-status.txt new file mode 100644 index 0000000..081dbf1 --- /dev/null +++ b/st-tdg-test/src/test/resources/petstore/qagrow-local-pet-by-category-status.txt @@ -0,0 +1,8 @@ +"Category":{"id":1,"name":"Dogs"} +"Category":{"id":2,"name":"1"} +"Category":{"id":3,"name":"202"} +"Pet":{"id":1,"name":"1102","category":{"id":1,"name":"Dogs"},"photoUrls":["303"],"tags":[{"id":703,"name":"704"}],"status":"available"} +"Pet":{"id":2,"name":"1202","category":{"id":2,"name":"1"},"photoUrls":["403"],"tags":[{"id":803,"name":"804"}],"status":"available"} +"Pet":{"id":3,"name":"1302","category":{"id":1,"name":"Dogs"},"photoUrls":["503"],"tags":[{"id":903,"name":"904"}],"status":"pending"} +"Pet":{"id":4,"name":"1402","category":{"id":1,"name":"Dogs"},"photoUrls":["603"],"tags":[],"status":"available"} +"Pet":{"id":5,"name":"1502","category":{"id":1,"name":"Dogs"},"photoUrls":[],"tags":[{"id":1003,"name":"1004"}],"status":"available"} \ No newline at end of file diff --git a/st-tdg-test/src/test/resources/petstore/qagrow-local-pet-by-url-tag.txt b/st-tdg-test/src/test/resources/petstore/qagrow-local-pet-by-url-tag.txt new file mode 100644 index 0000000..0352bfb --- /dev/null +++ b/st-tdg-test/src/test/resources/petstore/qagrow-local-pet-by-url-tag.txt @@ -0,0 +1,5 @@ +"Category":{"id":1,"name":"2"} +"Category":{"id":2,"name":"102"} +"Pet":{"id":1,"name":"802","category":{"id":1,"name":"2"},"photoUrls":["URL","1"],"tags":[{"id":503,"name":"kitty"},{"id":603,"name":"1"}],"status":"available"} +"Pet":{"id":2,"name":"902","category":{"id":1,"name":"2"},"photoUrls":["URL"],"tags":[],"status":"pending"} +"Pet":{"id":3,"name":"1002","category":{"id":1,"name":"2"},"photoUrls":[],"tags":[{"id":703,"name":"kitty"}],"status":"sold"} \ No newline at end of file diff --git a/st-tdg-test/src/test/resources/petstore/qagrow-local-pet0-by-category-status.txt b/st-tdg-test/src/test/resources/petstore/qagrow-local-pet0-by-category-status.txt new file mode 100644 index 0000000..55e3036 --- /dev/null +++ b/st-tdg-test/src/test/resources/petstore/qagrow-local-pet0-by-category-status.txt @@ -0,0 +1,3 @@ +"Pet0":{"id":1,"name":"2","category":{"id":3,"name":"Dogs"},"status":"available"} +"Pet0":{"id":2,"name":"102","category":{"id":103,"name":"1"},"status":"available"} +"Pet0":{"id":3,"name":"202","category":{"id":203,"name":"Dogs"},"status":"pending"} \ No newline at end of file diff --git a/st-tdg-test/src/test/resources/petstore/qagrow-local-pet1-by-category-status.txt b/st-tdg-test/src/test/resources/petstore/qagrow-local-pet1-by-category-status.txt new file mode 100644 index 0000000..bca0115 --- /dev/null +++ b/st-tdg-test/src/test/resources/petstore/qagrow-local-pet1-by-category-status.txt @@ -0,0 +1,6 @@ +"Category":{"id":1,"name":"Dogs"} +"Category":{"id":2,"name":"1"} +"Category":{"id":3,"name":"202"} +"Pet1":{"id":1,"name":"302","category":{"id":1,"name":"Dogs"},"status":"available"} +"Pet1":{"id":2,"name":"402","category":{"id":2,"name":"1"},"status":"available"} +"Pet1":{"id":3,"name":"502","category":{"id":1,"name":"Dogs"},"status":"pending"} \ No newline at end of file diff --git a/st-tdg-test/src/test/resources/petstore/qagrow-local-placed-pet-orders-by-address-order-status-dict.txt b/st-tdg-test/src/test/resources/petstore/qagrow-local-placed-pet-orders-by-address-order-status-dict.txt new file mode 100644 index 0000000..eb0e152 --- /dev/null +++ b/st-tdg-test/src/test/resources/petstore/qagrow-local-placed-pet-orders-by-address-order-status-dict.txt @@ -0,0 +1,14 @@ +"Category":{"id":1,"name":"Tiger"} +"Category":{"id":2,"name":"Lion"} +"Pet":{"id":1,"name":"Max","category":{"id":1,"name":"Tiger"},"photoUrls":["http://localhost/photos/000203.jpg"],"tags":[{"id":503,"name":"Puppy"}],"status":"available"} +"Pet":{"id":2,"name":"Luna","category":{"id":1,"name":"Tiger"},"photoUrls":["http://localhost/photos/000303.jpg"],"tags":[{"id":603,"name":"Young"}],"status":"pending"} +"Pet":{"id":3,"name":"Charlie","category":{"id":1,"name":"Tiger"},"photoUrls":["http://localhost/photos/000403.jpg"],"tags":[],"status":"sold"} +"Pet":{"id":4,"name":"Bella","category":{"id":1,"name":"Tiger"},"photoUrls":[],"tags":[{"id":703,"name":"Old"}],"status":"available"} +"Customer":{"id":1,"username":"James Smith","address":[{"street":"Main St","city":"Springfield","state":"California","zip":"99999"},{"street":"Broadway","city":"Franklin","state":"Texas","zip":"000001"}]} +"Customer":{"id":2,"username":"Mary Johnson","address":[]} +"Customer":{"id":3,"username":"Robert Williams","address":[{"street":"Park Ave","city":"Greenville","state":"Florida","zip":"99999"}]} +"Order":{"id":1,"petId":1,"customerId":1,"quantity":1804,"shipDate":"2007-01-23","status":"placed","complete":true} +"Order":{"id":2,"petId":1,"customerId":1,"quantity":1904,"shipDate":"2007-01-24","status":"approved","complete":false} +"Order":{"id":3,"petId":1,"customerId":2,"quantity":2004,"shipDate":"2007-01-25","status":"placed","complete":true} +"Order":{"id":4,"petId":3,"customerId":1,"quantity":2104,"shipDate":"2007-01-26","status":"placed","complete":false} +"Order":{"id":5,"petId":4,"customerId":1,"quantity":2204,"shipDate":"2007-01-27","status":"placed","complete":true} \ No newline at end of file diff --git a/st-tdg-test/src/test/resources/petstore/qagrow-local-placed-pet-orders-by-address-order-status.txt b/st-tdg-test/src/test/resources/petstore/qagrow-local-placed-pet-orders-by-address-order-status.txt new file mode 100644 index 0000000..126ccb7 --- /dev/null +++ b/st-tdg-test/src/test/resources/petstore/qagrow-local-placed-pet-orders-by-address-order-status.txt @@ -0,0 +1,14 @@ +"Category":{"id":1,"name":"2"} +"Category":{"id":2,"name":"102"} +"Pet":{"id":1,"name":"802","category":{"id":1,"name":"2"},"photoUrls":["203"],"tags":[{"id":503,"name":"504"}],"status":"available"} +"Pet":{"id":2,"name":"902","category":{"id":1,"name":"2"},"photoUrls":["303"],"tags":[{"id":603,"name":"604"}],"status":"pending"} +"Pet":{"id":3,"name":"1002","category":{"id":1,"name":"2"},"photoUrls":["403"],"tags":[],"status":"sold"} +"Pet":{"id":4,"name":"1102","category":{"id":1,"name":"2"},"photoUrls":[],"tags":[{"id":703,"name":"704"}],"status":"available"} +"Customer":{"id":1,"username":"1502","address":[{"street":"1203","city":"1204","state":"1205","zip":"99999"},{"street":"1303","city":"1304","state":"1305","zip":"1"}]} +"Customer":{"id":2,"username":"1602","address":[]} +"Customer":{"id":3,"username":"1702","address":[{"street":"1403","city":"1404","state":"1405","zip":"99999"}]} +"Order":{"id":1,"petId":1,"customerId":1,"quantity":1804,"shipDate":"2007-01-23","status":"placed","complete":true} +"Order":{"id":2,"petId":1,"customerId":1,"quantity":1904,"shipDate":"2007-01-24","status":"approved","complete":false} +"Order":{"id":3,"petId":1,"customerId":2,"quantity":2004,"shipDate":"2007-01-25","status":"placed","complete":true} +"Order":{"id":4,"petId":3,"customerId":1,"quantity":2104,"shipDate":"2007-01-26","status":"placed","complete":false} +"Order":{"id":5,"petId":4,"customerId":1,"quantity":2204,"shipDate":"2007-01-27","status":"placed","complete":true} \ No newline at end of file diff --git a/st-tdg-test/src/test/resources/petstore/qagrow-local-placed-pet-orders-by-category-order-status.txt b/st-tdg-test/src/test/resources/petstore/qagrow-local-placed-pet-orders-by-category-order-status.txt new file mode 100644 index 0000000..6d641d5 --- /dev/null +++ b/st-tdg-test/src/test/resources/petstore/qagrow-local-placed-pet-orders-by-category-order-status.txt @@ -0,0 +1,17 @@ +"Category":{"id":1,"name":"Dogs"} +"Category":{"id":2,"name":"1"} +"Category":{"id":3,"name":"202"} +"Pet":{"id":1,"name":"1102","category":{"id":1,"name":"Dogs"},"photoUrls":["303"],"tags":[{"id":703,"name":"704"}],"status":"available"} +"Pet":{"id":2,"name":"1202","category":{"id":2,"name":"1"},"photoUrls":["403"],"tags":[{"id":803,"name":"804"}],"status":"pending"} +"Pet":{"id":3,"name":"1302","category":{"id":1,"name":"Dogs"},"photoUrls":["503"],"tags":[{"id":903,"name":"904"}],"status":"sold"} +"Pet":{"id":4,"name":"1402","category":{"id":1,"name":"Dogs"},"photoUrls":["603"],"tags":[],"status":"available"} +"Pet":{"id":5,"name":"1502","category":{"id":1,"name":"Dogs"},"photoUrls":[],"tags":[{"id":1003,"name":"1004"}],"status":"pending"} +"Customer":{"id":1,"username":"1802","address":[{"street":"1603","city":"1604","state":"1605","zip":"1606"}]} +"Customer":{"id":2,"username":"1902","address":[]} +"Customer":{"id":3,"username":"2002","address":[{"street":"1703","city":"1704","state":"1705","zip":"1706"}]} +"Order":{"id":1,"petId":1,"customerId":1,"quantity":2104,"shipDate":"2007-01-26","status":"placed","complete":false} +"Order":{"id":2,"petId":2,"customerId":1,"quantity":2204,"shipDate":"2007-01-27","status":"placed","complete":true} +"Order":{"id":3,"petId":1,"customerId":1,"quantity":2304,"shipDate":"2007-01-28","status":"approved","complete":false} +"Order":{"id":4,"petId":1,"customerId":2,"quantity":2404,"shipDate":"2007-01-29","status":"placed","complete":true} +"Order":{"id":5,"petId":4,"customerId":1,"quantity":2504,"shipDate":"2007-01-30","status":"placed","complete":false} +"Order":{"id":6,"petId":5,"customerId":1,"quantity":2604,"shipDate":"2007-02-01","status":"placed","complete":true} \ No newline at end of file diff --git a/st-tdg-test/src/test/resources/petstore/qagrow-local-placed-pet0-orders-by-category-order-status.txt b/st-tdg-test/src/test/resources/petstore/qagrow-local-placed-pet0-orders-by-category-order-status.txt new file mode 100644 index 0000000..dbf49f0 --- /dev/null +++ b/st-tdg-test/src/test/resources/petstore/qagrow-local-placed-pet0-orders-by-category-order-status.txt @@ -0,0 +1,8 @@ +"Customer0":{"id":1,"username":"2"} +"Customer0":{"id":2,"username":"102"} +"Pet0":{"id":1,"name":"202","category":{"id":203,"name":"Dogs"},"status":"pending"} +"Pet0":{"id":2,"name":"302","category":{"id":303,"name":"1"},"status":"sold"} +"Pet0":{"id":3,"name":"402","category":{"id":403,"name":"Dogs"},"status":"available"} +"Order0":{"id":1,"petId":1,"customerId":1,"quantity":504,"shipDate":"2007-01-10","status":"placed","complete":false} +"Order0":{"id":2,"petId":2,"customerId":1,"quantity":604,"shipDate":"2007-01-11","status":"placed","complete":true} +"Order0":{"id":3,"petId":1,"customerId":1,"quantity":704,"shipDate":"2007-01-12","status":"approved","complete":false} \ No newline at end of file diff --git a/st-tdg-test/src/test/resources/petstore/qagrow-local-smoke.txt b/st-tdg-test/src/test/resources/petstore/qagrow-local-smoke.txt new file mode 100644 index 0000000..a204828 --- /dev/null +++ b/st-tdg-test/src/test/resources/petstore/qagrow-local-smoke.txt @@ -0,0 +1,2 @@ +"Category":{"id":1,"name":"1"} +"Category":{"id":2,"name":"Dogs"} \ No newline at end of file diff --git a/st-tdg-test/src/test/resources/petstore/qagrow-local-total-pet-orders-by-address.txt b/st-tdg-test/src/test/resources/petstore/qagrow-local-total-pet-orders-by-address.txt new file mode 100644 index 0000000..d016dd4 --- /dev/null +++ b/st-tdg-test/src/test/resources/petstore/qagrow-local-total-pet-orders-by-address.txt @@ -0,0 +1,17 @@ +"Category":{"id":1,"name":"2"} +"Category":{"id":2,"name":"102"} +"Category":{"id":3,"name":"202"} +"Pet":{"id":1,"name":"1102","category":{"id":1,"name":"2"},"photoUrls":["303"],"tags":[{"id":703,"name":"704"}],"status":"available"} +"Pet":{"id":2,"name":"1202","category":{"id":2,"name":"102"},"photoUrls":["403"],"tags":[{"id":803,"name":"804"}],"status":"pending"} +"Pet":{"id":3,"name":"1302","category":{"id":2,"name":"102"},"photoUrls":["503"],"tags":[{"id":903,"name":"904"}],"status":"sold"} +"Pet":{"id":4,"name":"1402","category":{"id":1,"name":"2"},"photoUrls":["603"],"tags":[],"status":"available"} +"Pet":{"id":5,"name":"1502","category":{"id":1,"name":"2"},"photoUrls":[],"tags":[{"id":1003,"name":"1004"}],"status":"pending"} +"Customer":{"id":1,"username":"1802","address":[{"street":"1603","city":"1604","state":"1605","zip":"0"}]} +"Customer":{"id":2,"username":"1902","address":[]} +"Customer":{"id":3,"username":"2002","address":[{"street":"1703","city":"1704","state":"1705","zip":"1"}]} +"Order":{"id":1,"petId":1,"customerId":1,"quantity":2104,"shipDate":"2007-01-26","status":"placed","complete":false} +"Order":{"id":2,"petId":1,"customerId":1,"quantity":2204,"shipDate":"2007-01-27","status":"approved","complete":true} +"Order":{"id":3,"petId":2,"customerId":1,"quantity":2304,"shipDate":"2007-01-28","status":"approved","complete":false} +"Order":{"id":4,"petId":1,"customerId":2,"quantity":2404,"shipDate":"2007-01-29","status":"approved","complete":true} +"Order":{"id":5,"petId":4,"customerId":1,"quantity":2504,"shipDate":"2007-01-30","status":"approved","complete":false} +"Order":{"id":6,"petId":5,"customerId":1,"quantity":2604,"shipDate":"2007-02-01","status":"approved","complete":true} \ No newline at end of file diff --git a/st-tdg-test/src/test/resources/petstore/qagrow-local-total-pets-by-address.txt b/st-tdg-test/src/test/resources/petstore/qagrow-local-total-pets-by-address.txt new file mode 100644 index 0000000..823fd25 --- /dev/null +++ b/st-tdg-test/src/test/resources/petstore/qagrow-local-total-pets-by-address.txt @@ -0,0 +1,18 @@ +"Category":{"id":1,"name":"2"} +"Category":{"id":2,"name":"102"} +"Category":{"id":3,"name":"202"} +"Pet":{"id":1,"name":"1102","category":{"id":1,"name":"2"},"photoUrls":["303"],"tags":[{"id":703,"name":"704"}],"status":"available"} +"Pet":{"id":2,"name":"1202","category":{"id":2,"name":"102"},"photoUrls":["403"],"tags":[{"id":803,"name":"804"}],"status":"pending"} +"Pet":{"id":3,"name":"1302","category":{"id":2,"name":"102"},"photoUrls":["603"],"tags":[],"status":"sold"} +"Pet":{"id":4,"name":"1402","category":{"id":1,"name":"2"},"photoUrls":["503"],"tags":[{"id":903,"name":"904"}],"status":"available"} +"Pet":{"id":5,"name":"1502","category":{"id":1,"name":"2"},"photoUrls":[],"tags":[{"id":1003,"name":"1004"}],"status":"pending"} +"Customer":{"id":1,"username":"1802","address":[{"street":"1603","city":"1604","state":"1605","zip":"0"}]} +"Customer":{"id":2,"username":"1902","address":[]} +"Customer":{"id":3,"username":"2002","address":[{"street":"1703","city":"1704","state":"1705","zip":"1"}]} +"Order":{"id":1,"petId":1,"customerId":1,"quantity":2104,"shipDate":"2007-01-26","status":"placed","complete":false} +"Order":{"id":2,"petId":1,"customerId":1,"quantity":0,"shipDate":"2007-01-27","status":"approved","complete":true} +"Order":{"id":3,"petId":2,"customerId":1,"quantity":0,"shipDate":"2007-01-28","status":"approved","complete":false} +"Order":{"id":4,"petId":1,"customerId":1,"quantity":0,"shipDate":"2007-01-29","status":"approved","complete":true} +"Order":{"id":5,"petId":1,"customerId":2,"quantity":2504,"shipDate":"2007-01-30","status":"approved","complete":false} +"Order":{"id":6,"petId":3,"customerId":1,"quantity":2604,"shipDate":"2007-02-01","status":"approved","complete":true} +"Order":{"id":7,"petId":5,"customerId":1,"quantity":2704,"shipDate":"2007-02-02","status":"approved","complete":false} \ No newline at end of file diff --git a/st-tdg-test/src/test/resources/petstore/rules-pet-by-category-status.xml b/st-tdg-test/src/test/resources/petstore/rules-pet-by-category-status.xml new file mode 100644 index 0000000..fc523c9 --- /dev/null +++ b/st-tdg-test/src/test/resources/petstore/rules-pet-by-category-status.xml @@ -0,0 +1,93 @@ + + +0.0.0 +tds Pet where Pet.category::name='Dogs' and Pet.status='available' +SELECT * + FROM Pet + LEFT JOIN Pet_tags_xa ON Pet.id = Pet_tags_xa.fk_xa + LEFT JOIN Pet_photoUrls_xa ON Pet.id = Pet_photoUrls_xa.fk_xa + LEFT JOIN Category Pet_category_xref ON Pet.category::id = Pet_category_xref.id + WHERE Pet.category::name = 'Dogs' AND Pet.status = 'available' + + 1STTT1.w.1.[Pet.category::name = 'Dogs'] + SELECT * + FROM Pet + INNER JOIN Pet_tags_xa ON Pet.id = Pet_tags_xa.fk_xa + INNER JOIN Pet_photoUrls_xa ON Pet.id = Pet_photoUrls_xa.fk_xa + INNER JOIN Category Pet_category_xref ON Pet.category::id = Pet_category_xref.id + WHERE (Pet.category::name = 'Dogs') AND (Pet.status = 'available') + --A set of joined tables such that: +--The WHERE condition fulfills: + --(T) Pet.category::name = 'Dogs' is TRUE + --(T) Pet.status = 'available' is TRUE + + 2STFT1.w.1.[Pet.category::name = 'Dogs'] + SELECT * + FROM Pet + INNER JOIN Pet_tags_xa ON Pet.id = Pet_tags_xa.fk_xa + INNER JOIN Pet_photoUrls_xa ON Pet.id = Pet_photoUrls_xa.fk_xa + INNER JOIN Category Pet_category_xref ON Pet.category::id = Pet_category_xref.id + WHERE NOT(Pet.category::name = 'Dogs') AND (Pet.status = 'available') + --A set of joined tables such that: +--The WHERE condition fulfills: + --(F) Pet.category::name = 'Dogs' is FALSE + --(T) Pet.status = 'available' is TRUE + + 3STFT1.w.2.[Pet.status = 'available'] + SELECT * + FROM Pet + INNER JOIN Pet_tags_xa ON Pet.id = Pet_tags_xa.fk_xa + INNER JOIN Pet_photoUrls_xa ON Pet.id = Pet_photoUrls_xa.fk_xa + INNER JOIN Category Pet_category_xref ON Pet.category::id = Pet_category_xref.id + WHERE NOT(Pet.status = 'available') AND (Pet.category::name = 'Dogs') + --A set of joined tables such that: +--The WHERE condition fulfills: + --(F) Pet.status = 'available' is FALSE + --(T) Pet.category::name = 'Dogs' is TRUE + + 4JLO1.j.1.[LEFT JOIN] + SELECT * + FROM Pet + LEFT JOIN Pet_tags_xa ON Pet.id = Pet_tags_xa.fk_xa + INNER JOIN Pet_photoUrls_xa ON Pet.id = Pet_photoUrls_xa.fk_xa + INNER JOIN Category Pet_category_xref ON Pet.category::id = Pet_category_xref.id + WHERE ((Pet_tags_xa.fk_xa IS NULL) AND (Pet.id IS NOT NULL)) + AND (Pet.category::name = 'Dogs' AND Pet.status = 'available') + +--The JOIN tables fulfill: + --summary: [Pet] left [Pet_tags_xa] ON Pet.id = Pet_tags_xa.fk_xa + --There exist a set of rows joined from tables Pet, Pet_photoUrls_xa, Category Pet_category_xref + --which does not join to any table in Pet_tags_xa +--The WHERE condition is TRUE. + + 5JLO1.j.2.[LEFT JOIN] + SELECT * + FROM Pet + INNER JOIN Pet_tags_xa ON Pet.id = Pet_tags_xa.fk_xa + LEFT JOIN Pet_photoUrls_xa ON Pet.id = Pet_photoUrls_xa.fk_xa + INNER JOIN Category Pet_category_xref ON Pet.category::id = Pet_category_xref.id + WHERE ((Pet_photoUrls_xa.fk_xa IS NULL) AND (Pet.id IS NOT NULL)) + AND (Pet.category::name = 'Dogs' AND Pet.status = 'available') + +--The JOIN tables fulfill: + --summary: [Pet] left [Pet_photoUrls_xa] ON Pet.id = Pet_photoUrls_xa.fk_xa + --There exist a set of rows joined from tables Pet, Pet_tags_xa, Category Pet_category_xref + --which does not join to any table in Pet_photoUrls_xa +--The WHERE condition is TRUE. + + 6JRO1.j.3.[LEFT JOIN] + SELECT * + FROM Pet + LEFT JOIN Pet_tags_xa ON Pet.id = Pet_tags_xa.fk_xa + LEFT JOIN Pet_photoUrls_xa ON Pet.id = Pet_photoUrls_xa.fk_xa + RIGHT JOIN Category Pet_category_xref ON Pet.category::id = Pet_category_xref.id + WHERE (Pet.category::id IS NULL) AND (Pet_category_xref.id IS NOT NULL) + +--The JOIN tables fulfill: +--COMMENT: Reduced conditions: [Pet.category::name = 'Dogs'] ; [Pet.status = 'available'] ; + --summary: [Pet] right [Category Pet_category_xref] ON Pet.category::id = Pet_category_xref.id + --There exist some row in table Category Pet_category_xref + --which does not join to any table in Pet + + + \ No newline at end of file diff --git a/st-tdg-test/src/test/resources/petstore/rules-pet-by-url-tag.xml b/st-tdg-test/src/test/resources/petstore/rules-pet-by-url-tag.xml new file mode 100644 index 0000000..d70fd11 --- /dev/null +++ b/st-tdg-test/src/test/resources/petstore/rules-pet-by-url-tag.xml @@ -0,0 +1,97 @@ + + +0.0.0 +tds Pet where Pet.photoUrls[]='URL' and Pet.tags[]::name='kitty' +SELECT * + FROM Pet + LEFT JOIN Pet_tags_xa ON Pet.id = Pet_tags_xa.fk_xa + LEFT JOIN Pet_photoUrls_xa ON Pet.id = Pet_photoUrls_xa.fk_xa + LEFT JOIN Category Pet_category_xref ON Pet.category::id = Pet_category_xref.id + WHERE Pet_photoUrls_xa.photoUrls = 'URL' AND Pet_tags_xa.name = 'kitty' + + 1STTT1.w.1.[Pet_photoUrls_xa.photoUrls = 'URL'] + SELECT * + FROM Pet + INNER JOIN Pet_tags_xa ON Pet.id = Pet_tags_xa.fk_xa + INNER JOIN Pet_photoUrls_xa ON Pet.id = Pet_photoUrls_xa.fk_xa + INNER JOIN Category Pet_category_xref ON Pet.category::id = Pet_category_xref.id + WHERE (Pet_photoUrls_xa.photoUrls = 'URL') AND (Pet_tags_xa.name = 'kitty') + --A set of joined tables such that: +--The WHERE condition fulfills: + --(T) Pet_photoUrls_xa.photoUrls = 'URL' is TRUE + --(T) Pet_tags_xa.name = 'kitty' is TRUE + + 2STFT1.w.1.[Pet_photoUrls_xa.photoUrls = 'URL'] + SELECT * + FROM Pet + INNER JOIN Pet_tags_xa ON Pet.id = Pet_tags_xa.fk_xa + INNER JOIN Pet_photoUrls_xa ON Pet.id = Pet_photoUrls_xa.fk_xa + INNER JOIN Category Pet_category_xref ON Pet.category::id = Pet_category_xref.id + WHERE NOT(Pet_photoUrls_xa.photoUrls = 'URL') AND (Pet_tags_xa.name = 'kitty') + --A set of joined tables such that: +--The WHERE condition fulfills: + --(F) Pet_photoUrls_xa.photoUrls = 'URL' is FALSE + --(T) Pet_tags_xa.name = 'kitty' is TRUE + + 3STFT1.w.2.[Pet_tags_xa.name = 'kitty'] + SELECT * + FROM Pet + INNER JOIN Pet_tags_xa ON Pet.id = Pet_tags_xa.fk_xa + INNER JOIN Pet_photoUrls_xa ON Pet.id = Pet_photoUrls_xa.fk_xa + INNER JOIN Category Pet_category_xref ON Pet.category::id = Pet_category_xref.id + WHERE NOT(Pet_tags_xa.name = 'kitty') AND (Pet_photoUrls_xa.photoUrls = 'URL') + --A set of joined tables such that: +--The WHERE condition fulfills: + --(F) Pet_tags_xa.name = 'kitty' is FALSE + --(T) Pet_photoUrls_xa.photoUrls = 'URL' is TRUE + + 4JLO1.j.1.[LEFT JOIN] + SELECT * + FROM Pet + LEFT JOIN Pet_tags_xa ON Pet.id = Pet_tags_xa.fk_xa + INNER JOIN Pet_photoUrls_xa ON Pet.id = Pet_photoUrls_xa.fk_xa + INNER JOIN Category Pet_category_xref ON Pet.category::id = Pet_category_xref.id + WHERE ((Pet_tags_xa.fk_xa IS NULL) AND (Pet.id IS NOT NULL)) + AND (Pet_photoUrls_xa.photoUrls = 'URL') + +--The JOIN tables fulfill: +--COMMENT: Reduced conditions: [Pet_tags_xa.name = 'kitty'] ; + --summary: [Pet] left [Pet_tags_xa] ON Pet.id = Pet_tags_xa.fk_xa + --There exist a set of rows joined from tables Pet, Pet_photoUrls_xa, Category Pet_category_xref + --which does not join to any table in Pet_tags_xa +--The following condition in the WHERE holds: + --WHERE Pet_photoUrls_xa.photoUrls = 'URL' + + 5JLO1.j.2.[LEFT JOIN] + SELECT * + FROM Pet + INNER JOIN Pet_tags_xa ON Pet.id = Pet_tags_xa.fk_xa + LEFT JOIN Pet_photoUrls_xa ON Pet.id = Pet_photoUrls_xa.fk_xa + INNER JOIN Category Pet_category_xref ON Pet.category::id = Pet_category_xref.id + WHERE ((Pet_photoUrls_xa.fk_xa IS NULL) AND (Pet.id IS NOT NULL)) + AND (Pet_tags_xa.name = 'kitty') + +--The JOIN tables fulfill: +--COMMENT: Reduced conditions: [Pet_photoUrls_xa.photoUrls = 'URL'] ; + --summary: [Pet] left [Pet_photoUrls_xa] ON Pet.id = Pet_photoUrls_xa.fk_xa + --There exist a set of rows joined from tables Pet, Pet_tags_xa, Category Pet_category_xref + --which does not join to any table in Pet_photoUrls_xa +--The following condition in the WHERE holds: + --WHERE Pet_tags_xa.name = 'kitty' + + 6JRO1.j.3.[LEFT JOIN] + SELECT * + FROM Pet + LEFT JOIN Pet_tags_xa ON Pet.id = Pet_tags_xa.fk_xa + LEFT JOIN Pet_photoUrls_xa ON Pet.id = Pet_photoUrls_xa.fk_xa + RIGHT JOIN Category Pet_category_xref ON Pet.category::id = Pet_category_xref.id + WHERE (Pet.category::id IS NULL) AND (Pet_category_xref.id IS NOT NULL) + +--The JOIN tables fulfill: +--COMMENT: Reduced conditions: [Pet_photoUrls_xa.photoUrls = 'URL'] ; [Pet_tags_xa.name = 'kitty'] ; + --summary: [Pet] right [Category Pet_category_xref] ON Pet.category::id = Pet_category_xref.id + --There exist some row in table Category Pet_category_xref + --which does not join to any table in Pet + + + \ No newline at end of file diff --git a/st-tdg-test/src/test/resources/petstore/rules-pet0-by-category-status.xml b/st-tdg-test/src/test/resources/petstore/rules-pet0-by-category-status.xml new file mode 100644 index 0000000..7281bf9 --- /dev/null +++ b/st-tdg-test/src/test/resources/petstore/rules-pet0-by-category-status.xml @@ -0,0 +1,37 @@ + + +0.0.0 +tds Pet0 where Pet0.category::name='Dogs' and Pet0.status='available' +SELECT * + FROM Pet0 + WHERE Pet0.category::name = 'Dogs' AND Pet0.status = 'available' + + 1STTT1.w.1.[Pet0.category::name = 'Dogs'] + SELECT * + FROM Pet0 + WHERE (Pet0.category::name = 'Dogs') AND (Pet0.status = 'available') + --Some row in the table such that: +--The WHERE condition fulfills: + --(T) Pet0.category::name = 'Dogs' is TRUE + --(T) Pet0.status = 'available' is TRUE + + 2STFT1.w.1.[Pet0.category::name = 'Dogs'] + SELECT * + FROM Pet0 + WHERE NOT(Pet0.category::name = 'Dogs') AND (Pet0.status = 'available') + --Some row in the table such that: +--The WHERE condition fulfills: + --(F) Pet0.category::name = 'Dogs' is FALSE + --(T) Pet0.status = 'available' is TRUE + + 3STFT1.w.2.[Pet0.status = 'available'] + SELECT * + FROM Pet0 + WHERE NOT(Pet0.status = 'available') AND (Pet0.category::name = 'Dogs') + --Some row in the table such that: +--The WHERE condition fulfills: + --(F) Pet0.status = 'available' is FALSE + --(T) Pet0.category::name = 'Dogs' is TRUE + + + \ No newline at end of file diff --git a/st-tdg-test/src/test/resources/petstore/rules-pet1-by-category-status.xml b/st-tdg-test/src/test/resources/petstore/rules-pet1-by-category-status.xml new file mode 100644 index 0000000..e9353af --- /dev/null +++ b/st-tdg-test/src/test/resources/petstore/rules-pet1-by-category-status.xml @@ -0,0 +1,53 @@ + + +0.0.0 +tds Pet1 where Pet1.category::name='Dogs' and Pet1.status='available' +SELECT * + FROM Pet1 + LEFT JOIN Category Pet1_category_xref ON Pet1.category::id = Pet1_category_xref.id + WHERE Pet1.category::name = 'Dogs' AND Pet1.status = 'available' + + 1STTT1.w.1.[Pet1.category::name = 'Dogs'] + SELECT * + FROM Pet1 + INNER JOIN Category Pet1_category_xref ON Pet1.category::id = Pet1_category_xref.id + WHERE (Pet1.category::name = 'Dogs') AND (Pet1.status = 'available') + --A set of joined tables such that: +--The WHERE condition fulfills: + --(T) Pet1.category::name = 'Dogs' is TRUE + --(T) Pet1.status = 'available' is TRUE + + 2STFT1.w.1.[Pet1.category::name = 'Dogs'] + SELECT * + FROM Pet1 + INNER JOIN Category Pet1_category_xref ON Pet1.category::id = Pet1_category_xref.id + WHERE NOT(Pet1.category::name = 'Dogs') AND (Pet1.status = 'available') + --A set of joined tables such that: +--The WHERE condition fulfills: + --(F) Pet1.category::name = 'Dogs' is FALSE + --(T) Pet1.status = 'available' is TRUE + + 3STFT1.w.2.[Pet1.status = 'available'] + SELECT * + FROM Pet1 + INNER JOIN Category Pet1_category_xref ON Pet1.category::id = Pet1_category_xref.id + WHERE NOT(Pet1.status = 'available') AND (Pet1.category::name = 'Dogs') + --A set of joined tables such that: +--The WHERE condition fulfills: + --(F) Pet1.status = 'available' is FALSE + --(T) Pet1.category::name = 'Dogs' is TRUE + + 4JRO1.j.1.[LEFT JOIN] + SELECT * + FROM Pet1 + RIGHT JOIN Category Pet1_category_xref ON Pet1.category::id = Pet1_category_xref.id + WHERE (Pet1.category::id IS NULL) AND (Pet1_category_xref.id IS NOT NULL) + +--The JOIN tables fulfill: +--COMMENT: Reduced conditions: [Pet1.category::name = 'Dogs'] ; [Pet1.status = 'available'] ; + --summary: [Pet1] right [Category Pet1_category_xref] ON Pet1.category::id = Pet1_category_xref.id + --There exist some row in table Category Pet1_category_xref + --which does not join to any table in Pet1 + + + \ No newline at end of file diff --git a/st-tdg-test/src/test/resources/petstore/rules-placed-pet-orders-by-address-order-status.xml b/st-tdg-test/src/test/resources/petstore/rules-placed-pet-orders-by-address-order-status.xml new file mode 100644 index 0000000..9214928 --- /dev/null +++ b/st-tdg-test/src/test/resources/petstore/rules-placed-pet-orders-by-address-order-status.xml @@ -0,0 +1,171 @@ + + +0.0.0 +tds Customer, "Order", Pet where Customer.address[]::zip='99999' and "Order".status='placed' +SELECT * + FROM Customer + LEFT JOIN Customer_address_xa ON Customer.id = Customer_address_xa.fk_xa + INNER JOIN "Order" ON Customer.id = Order.customerId + INNER JOIN Pet ON Order.petId = Pet.id + LEFT JOIN Pet_tags_xa ON Pet.id = Pet_tags_xa.fk_xa + LEFT JOIN Pet_photoUrls_xa ON Pet.id = Pet_photoUrls_xa.fk_xa + LEFT JOIN Category Pet_category_xref ON Pet.category::id = Pet_category_xref.id + WHERE Customer_address_xa.zip = '99999' AND "Order".status = 'placed' + + 1STTT1.w.1.[Customer_address_xa.zip = '99999'] + SELECT * + FROM Customer + INNER JOIN Customer_address_xa ON Customer.id = Customer_address_xa.fk_xa + INNER JOIN "Order" ON Customer.id = Order.customerId + INNER JOIN Pet ON Order.petId = Pet.id + INNER JOIN Pet_tags_xa ON Pet.id = Pet_tags_xa.fk_xa + INNER JOIN Pet_photoUrls_xa ON Pet.id = Pet_photoUrls_xa.fk_xa + INNER JOIN Category Pet_category_xref ON Pet.category::id = Pet_category_xref.id + WHERE (Customer_address_xa.zip = '99999') AND ("Order".status = 'placed') + --A set of joined tables such that: +--The WHERE condition fulfills: + --(T) Customer_address_xa.zip = '99999' is TRUE + --(T) "Order".status = 'placed' is TRUE + + 2STFT1.w.1.[Customer_address_xa.zip = '99999'] + SELECT * + FROM Customer + INNER JOIN Customer_address_xa ON Customer.id = Customer_address_xa.fk_xa + INNER JOIN "Order" ON Customer.id = Order.customerId + INNER JOIN Pet ON Order.petId = Pet.id + INNER JOIN Pet_tags_xa ON Pet.id = Pet_tags_xa.fk_xa + INNER JOIN Pet_photoUrls_xa ON Pet.id = Pet_photoUrls_xa.fk_xa + INNER JOIN Category Pet_category_xref ON Pet.category::id = Pet_category_xref.id + WHERE NOT(Customer_address_xa.zip = '99999') AND ("Order".status = 'placed') + --A set of joined tables such that: +--The WHERE condition fulfills: + --(F) Customer_address_xa.zip = '99999' is FALSE + --(T) "Order".status = 'placed' is TRUE + + 3STFT1.w.2.["Order".status = 'placed'] + SELECT * + FROM Customer + INNER JOIN Customer_address_xa ON Customer.id = Customer_address_xa.fk_xa + INNER JOIN "Order" ON Customer.id = Order.customerId + INNER JOIN Pet ON Order.petId = Pet.id + INNER JOIN Pet_tags_xa ON Pet.id = Pet_tags_xa.fk_xa + INNER JOIN Pet_photoUrls_xa ON Pet.id = Pet_photoUrls_xa.fk_xa + INNER JOIN Category Pet_category_xref ON Pet.category::id = Pet_category_xref.id + WHERE NOT("Order".status = 'placed') AND (Customer_address_xa.zip = '99999') + --A set of joined tables such that: +--The WHERE condition fulfills: + --(F) "Order".status = 'placed' is FALSE + --(T) Customer_address_xa.zip = '99999' is TRUE + + 4JLO1.j.1.[LEFT JOIN] + SELECT * + FROM Customer + LEFT JOIN Customer_address_xa ON Customer.id = Customer_address_xa.fk_xa + INNER JOIN "Order" ON Customer.id = Order.customerId + INNER JOIN Pet ON Order.petId = Pet.id + INNER JOIN Pet_tags_xa ON Pet.id = Pet_tags_xa.fk_xa + INNER JOIN Pet_photoUrls_xa ON Pet.id = Pet_photoUrls_xa.fk_xa + INNER JOIN Category Pet_category_xref ON Pet.category::id = Pet_category_xref.id + WHERE ((Customer_address_xa.fk_xa IS NULL) AND (Customer.id IS NOT NULL)) + AND ("Order".status = 'placed') + +--The JOIN tables fulfill: +--COMMENT: Reduced conditions: [Customer_address_xa.zip = '99999'] ; + --summary: [Customer] left [Customer_address_xa] ON Customer.id = Customer_address_xa.fk_xa + --There exist a set of rows joined from tables Customer, Order, Pet, Pet_tags_xa, Pet_photoUrls_xa, Category Pet_category_xref + --which does not join to any table in Customer_address_xa +--The following condition in the WHERE holds: + --WHERE "Order".status = 'placed' + + 5JLO1.j.2.[INNER JOIN] + SELECT * + FROM Customer + INNER JOIN Customer_address_xa ON Customer.id = Customer_address_xa.fk_xa + LEFT JOIN "Order" ON Customer.id = Order.customerId + LEFT JOIN Pet ON Order.petId = Pet.id + LEFT JOIN Pet_tags_xa ON Pet.id = Pet_tags_xa.fk_xa + LEFT JOIN Pet_photoUrls_xa ON Pet.id = Pet_photoUrls_xa.fk_xa + LEFT JOIN Category Pet_category_xref ON Pet.category::id = Pet_category_xref.id + WHERE ((Order.customerId IS NULL) AND (Customer.id IS NOT NULL)) + AND (Customer_address_xa.zip = '99999') + +--The JOIN tables fulfill: +--COMMENT: Reduced conditions: ["Order".status = 'placed'] ; + --summary: [Customer] left [Order] ON Customer.id = Order.customerId + --There exist a set of rows joined from tables Customer, Customer_address_xa + --which does not join to any table in Order +--The following condition in the WHERE holds: + --WHERE Customer_address_xa.zip = '99999' + + 6JRO1.j.3.[INNER JOIN] + SELECT * + FROM Customer + LEFT JOIN Customer_address_xa ON Customer.id = Customer_address_xa.fk_xa + RIGHT JOIN "Order" ON Customer.id = Order.customerId + RIGHT JOIN Pet ON Order.petId = Pet.id + INNER JOIN Pet_tags_xa ON Pet.id = Pet_tags_xa.fk_xa + INNER JOIN Pet_photoUrls_xa ON Pet.id = Pet_photoUrls_xa.fk_xa + INNER JOIN Category Pet_category_xref ON Pet.category::id = Pet_category_xref.id + WHERE (Order.petId IS NULL) AND (Pet.id IS NOT NULL) + +--The JOIN tables fulfill: +--COMMENT: Reduced conditions: [Customer_address_xa.zip = '99999'] ; ["Order".status = 'placed'] ; + --summary: [Order] right [Pet] ON Order.petId = Pet.id + --There exist a set of rows joined from tables Pet, Pet_tags_xa, Pet_photoUrls_xa, Category Pet_category_xref + --which does not join to any table in Order + + 7JLO1.j.4.[LEFT JOIN] + SELECT * + FROM Customer + INNER JOIN Customer_address_xa ON Customer.id = Customer_address_xa.fk_xa + INNER JOIN "Order" ON Customer.id = Order.customerId + INNER JOIN Pet ON Order.petId = Pet.id + LEFT JOIN Pet_tags_xa ON Pet.id = Pet_tags_xa.fk_xa + INNER JOIN Pet_photoUrls_xa ON Pet.id = Pet_photoUrls_xa.fk_xa + INNER JOIN Category Pet_category_xref ON Pet.category::id = Pet_category_xref.id + WHERE ((Pet_tags_xa.fk_xa IS NULL) AND (Pet.id IS NOT NULL)) + AND (Customer_address_xa.zip = '99999' AND "Order".status = 'placed') + +--The JOIN tables fulfill: + --summary: [Pet] left [Pet_tags_xa] ON Pet.id = Pet_tags_xa.fk_xa + --There exist a set of rows joined from tables Customer, Customer_address_xa, Order, Pet, Pet_photoUrls_xa, Category Pet_category_xref + --which does not join to any table in Pet_tags_xa +--The WHERE condition is TRUE. + + 8JLO1.j.5.[LEFT JOIN] + SELECT * + FROM Customer + INNER JOIN Customer_address_xa ON Customer.id = Customer_address_xa.fk_xa + INNER JOIN "Order" ON Customer.id = Order.customerId + INNER JOIN Pet ON Order.petId = Pet.id + INNER JOIN Pet_tags_xa ON Pet.id = Pet_tags_xa.fk_xa + LEFT JOIN Pet_photoUrls_xa ON Pet.id = Pet_photoUrls_xa.fk_xa + INNER JOIN Category Pet_category_xref ON Pet.category::id = Pet_category_xref.id + WHERE ((Pet_photoUrls_xa.fk_xa IS NULL) AND (Pet.id IS NOT NULL)) + AND (Customer_address_xa.zip = '99999' AND "Order".status = 'placed') + +--The JOIN tables fulfill: + --summary: [Pet] left [Pet_photoUrls_xa] ON Pet.id = Pet_photoUrls_xa.fk_xa + --There exist a set of rows joined from tables Customer, Customer_address_xa, Order, Pet, Pet_tags_xa, Category Pet_category_xref + --which does not join to any table in Pet_photoUrls_xa +--The WHERE condition is TRUE. + + 9JRO1.j.6.[LEFT JOIN] + SELECT * + FROM Customer + LEFT JOIN Customer_address_xa ON Customer.id = Customer_address_xa.fk_xa + RIGHT JOIN "Order" ON Customer.id = Order.customerId + RIGHT JOIN Pet ON Order.petId = Pet.id + LEFT JOIN Pet_tags_xa ON Pet.id = Pet_tags_xa.fk_xa + LEFT JOIN Pet_photoUrls_xa ON Pet.id = Pet_photoUrls_xa.fk_xa + RIGHT JOIN Category Pet_category_xref ON Pet.category::id = Pet_category_xref.id + WHERE (Pet.category::id IS NULL) AND (Pet_category_xref.id IS NOT NULL) + +--The JOIN tables fulfill: +--COMMENT: Reduced conditions: [Customer_address_xa.zip = '99999'] ; ["Order".status = 'placed'] ; + --summary: [Pet] right [Category Pet_category_xref] ON Pet.category::id = Pet_category_xref.id + --There exist some row in table Category Pet_category_xref + --which does not join to any table in Pet + + + \ No newline at end of file diff --git a/st-tdg-test/src/test/resources/petstore/rules-placed-pet-orders-by-category-order-status.xml b/st-tdg-test/src/test/resources/petstore/rules-placed-pet-orders-by-category-order-status.xml new file mode 100644 index 0000000..164cf91 --- /dev/null +++ b/st-tdg-test/src/test/resources/petstore/rules-placed-pet-orders-by-category-order-status.xml @@ -0,0 +1,169 @@ + + +0.0.0 +tds Customer, "Order", Pet where Pet.category::name='Dogs' and "Order".status='placed' +SELECT * + FROM Customer + LEFT JOIN Customer_address_xa ON Customer.id = Customer_address_xa.fk_xa + INNER JOIN "Order" ON Customer.id = Order.customerId + INNER JOIN Pet ON Order.petId = Pet.id + LEFT JOIN Pet_tags_xa ON Pet.id = Pet_tags_xa.fk_xa + LEFT JOIN Pet_photoUrls_xa ON Pet.id = Pet_photoUrls_xa.fk_xa + LEFT JOIN Category Pet_category_xref ON Pet.category::id = Pet_category_xref.id + WHERE Pet.category::name = 'Dogs' AND "Order".status = 'placed' + + 1STTT1.w.1.[Pet.category::name = 'Dogs'] + SELECT * + FROM Customer + INNER JOIN Customer_address_xa ON Customer.id = Customer_address_xa.fk_xa + INNER JOIN "Order" ON Customer.id = Order.customerId + INNER JOIN Pet ON Order.petId = Pet.id + INNER JOIN Pet_tags_xa ON Pet.id = Pet_tags_xa.fk_xa + INNER JOIN Pet_photoUrls_xa ON Pet.id = Pet_photoUrls_xa.fk_xa + INNER JOIN Category Pet_category_xref ON Pet.category::id = Pet_category_xref.id + WHERE (Pet.category::name = 'Dogs') AND ("Order".status = 'placed') + --A set of joined tables such that: +--The WHERE condition fulfills: + --(T) Pet.category::name = 'Dogs' is TRUE + --(T) "Order".status = 'placed' is TRUE + + 2STFT1.w.1.[Pet.category::name = 'Dogs'] + SELECT * + FROM Customer + INNER JOIN Customer_address_xa ON Customer.id = Customer_address_xa.fk_xa + INNER JOIN "Order" ON Customer.id = Order.customerId + INNER JOIN Pet ON Order.petId = Pet.id + INNER JOIN Pet_tags_xa ON Pet.id = Pet_tags_xa.fk_xa + INNER JOIN Pet_photoUrls_xa ON Pet.id = Pet_photoUrls_xa.fk_xa + INNER JOIN Category Pet_category_xref ON Pet.category::id = Pet_category_xref.id + WHERE NOT(Pet.category::name = 'Dogs') AND ("Order".status = 'placed') + --A set of joined tables such that: +--The WHERE condition fulfills: + --(F) Pet.category::name = 'Dogs' is FALSE + --(T) "Order".status = 'placed' is TRUE + + 3STFT1.w.2.["Order".status = 'placed'] + SELECT * + FROM Customer + INNER JOIN Customer_address_xa ON Customer.id = Customer_address_xa.fk_xa + INNER JOIN "Order" ON Customer.id = Order.customerId + INNER JOIN Pet ON Order.petId = Pet.id + INNER JOIN Pet_tags_xa ON Pet.id = Pet_tags_xa.fk_xa + INNER JOIN Pet_photoUrls_xa ON Pet.id = Pet_photoUrls_xa.fk_xa + INNER JOIN Category Pet_category_xref ON Pet.category::id = Pet_category_xref.id + WHERE NOT("Order".status = 'placed') AND (Pet.category::name = 'Dogs') + --A set of joined tables such that: +--The WHERE condition fulfills: + --(F) "Order".status = 'placed' is FALSE + --(T) Pet.category::name = 'Dogs' is TRUE + + 4JLO1.j.1.[LEFT JOIN] + SELECT * + FROM Customer + LEFT JOIN Customer_address_xa ON Customer.id = Customer_address_xa.fk_xa + INNER JOIN "Order" ON Customer.id = Order.customerId + INNER JOIN Pet ON Order.petId = Pet.id + INNER JOIN Pet_tags_xa ON Pet.id = Pet_tags_xa.fk_xa + INNER JOIN Pet_photoUrls_xa ON Pet.id = Pet_photoUrls_xa.fk_xa + INNER JOIN Category Pet_category_xref ON Pet.category::id = Pet_category_xref.id + WHERE ((Customer_address_xa.fk_xa IS NULL) AND (Customer.id IS NOT NULL)) + AND (Pet.category::name = 'Dogs' AND "Order".status = 'placed') + +--The JOIN tables fulfill: + --summary: [Customer] left [Customer_address_xa] ON Customer.id = Customer_address_xa.fk_xa + --There exist a set of rows joined from tables Customer, Order, Pet, Pet_tags_xa, Pet_photoUrls_xa, Category Pet_category_xref + --which does not join to any table in Customer_address_xa +--The WHERE condition is TRUE. + + 5JLO1.j.2.[INNER JOIN] + SELECT * + FROM Customer + INNER JOIN Customer_address_xa ON Customer.id = Customer_address_xa.fk_xa + LEFT JOIN "Order" ON Customer.id = Order.customerId + LEFT JOIN Pet ON Order.petId = Pet.id + LEFT JOIN Pet_tags_xa ON Pet.id = Pet_tags_xa.fk_xa + LEFT JOIN Pet_photoUrls_xa ON Pet.id = Pet_photoUrls_xa.fk_xa + LEFT JOIN Category Pet_category_xref ON Pet.category::id = Pet_category_xref.id + WHERE (Order.customerId IS NULL) AND (Customer.id IS NOT NULL) + +--The JOIN tables fulfill: +--COMMENT: Reduced conditions: [Pet.category::name = 'Dogs'] ; ["Order".status = 'placed'] ; + --summary: [Customer] left [Order] ON Customer.id = Order.customerId + --There exist a set of rows joined from tables Customer, Customer_address_xa + --which does not join to any table in Order + + 6JRO1.j.3.[INNER JOIN] + SELECT * + FROM Customer + LEFT JOIN Customer_address_xa ON Customer.id = Customer_address_xa.fk_xa + RIGHT JOIN "Order" ON Customer.id = Order.customerId + RIGHT JOIN Pet ON Order.petId = Pet.id + INNER JOIN Pet_tags_xa ON Pet.id = Pet_tags_xa.fk_xa + INNER JOIN Pet_photoUrls_xa ON Pet.id = Pet_photoUrls_xa.fk_xa + INNER JOIN Category Pet_category_xref ON Pet.category::id = Pet_category_xref.id + WHERE ((Order.petId IS NULL) AND (Pet.id IS NOT NULL)) + AND (Pet.category::name = 'Dogs') + +--The JOIN tables fulfill: +--COMMENT: Reduced conditions: ["Order".status = 'placed'] ; + --summary: [Order] right [Pet] ON Order.petId = Pet.id + --There exist a set of rows joined from tables Pet, Pet_tags_xa, Pet_photoUrls_xa, Category Pet_category_xref + --which does not join to any table in Order +--The following condition in the WHERE holds: + --WHERE Pet.category::name = 'Dogs' + + 7JLO1.j.4.[LEFT JOIN] + SELECT * + FROM Customer + INNER JOIN Customer_address_xa ON Customer.id = Customer_address_xa.fk_xa + INNER JOIN "Order" ON Customer.id = Order.customerId + INNER JOIN Pet ON Order.petId = Pet.id + LEFT JOIN Pet_tags_xa ON Pet.id = Pet_tags_xa.fk_xa + INNER JOIN Pet_photoUrls_xa ON Pet.id = Pet_photoUrls_xa.fk_xa + INNER JOIN Category Pet_category_xref ON Pet.category::id = Pet_category_xref.id + WHERE ((Pet_tags_xa.fk_xa IS NULL) AND (Pet.id IS NOT NULL)) + AND (Pet.category::name = 'Dogs' AND "Order".status = 'placed') + +--The JOIN tables fulfill: + --summary: [Pet] left [Pet_tags_xa] ON Pet.id = Pet_tags_xa.fk_xa + --There exist a set of rows joined from tables Customer, Customer_address_xa, Order, Pet, Pet_photoUrls_xa, Category Pet_category_xref + --which does not join to any table in Pet_tags_xa +--The WHERE condition is TRUE. + + 8JLO1.j.5.[LEFT JOIN] + SELECT * + FROM Customer + INNER JOIN Customer_address_xa ON Customer.id = Customer_address_xa.fk_xa + INNER JOIN "Order" ON Customer.id = Order.customerId + INNER JOIN Pet ON Order.petId = Pet.id + INNER JOIN Pet_tags_xa ON Pet.id = Pet_tags_xa.fk_xa + LEFT JOIN Pet_photoUrls_xa ON Pet.id = Pet_photoUrls_xa.fk_xa + INNER JOIN Category Pet_category_xref ON Pet.category::id = Pet_category_xref.id + WHERE ((Pet_photoUrls_xa.fk_xa IS NULL) AND (Pet.id IS NOT NULL)) + AND (Pet.category::name = 'Dogs' AND "Order".status = 'placed') + +--The JOIN tables fulfill: + --summary: [Pet] left [Pet_photoUrls_xa] ON Pet.id = Pet_photoUrls_xa.fk_xa + --There exist a set of rows joined from tables Customer, Customer_address_xa, Order, Pet, Pet_tags_xa, Category Pet_category_xref + --which does not join to any table in Pet_photoUrls_xa +--The WHERE condition is TRUE. + + 9JRO1.j.6.[LEFT JOIN] + SELECT * + FROM Customer + LEFT JOIN Customer_address_xa ON Customer.id = Customer_address_xa.fk_xa + RIGHT JOIN "Order" ON Customer.id = Order.customerId + RIGHT JOIN Pet ON Order.petId = Pet.id + LEFT JOIN Pet_tags_xa ON Pet.id = Pet_tags_xa.fk_xa + LEFT JOIN Pet_photoUrls_xa ON Pet.id = Pet_photoUrls_xa.fk_xa + RIGHT JOIN Category Pet_category_xref ON Pet.category::id = Pet_category_xref.id + WHERE (Pet.category::id IS NULL) AND (Pet_category_xref.id IS NOT NULL) + +--The JOIN tables fulfill: +--COMMENT: Reduced conditions: [Pet.category::name = 'Dogs'] ; ["Order".status = 'placed'] ; + --summary: [Pet] right [Category Pet_category_xref] ON Pet.category::id = Pet_category_xref.id + --There exist some row in table Category Pet_category_xref + --which does not join to any table in Pet + + + \ No newline at end of file diff --git a/st-tdg-test/src/test/resources/petstore/rules-placed-pet0-orders-by-category-order-status.xml b/st-tdg-test/src/test/resources/petstore/rules-placed-pet0-orders-by-category-order-status.xml new file mode 100644 index 0000000..c5af91c --- /dev/null +++ b/st-tdg-test/src/test/resources/petstore/rules-placed-pet0-orders-by-category-order-status.xml @@ -0,0 +1,74 @@ + + +0.0.0 +tds Customer0, "Order0", Pet0 where Pet0.category::name='Dogs' and "Order0".status='placed' +SELECT * + FROM Customer0 + INNER JOIN "Order0" ON Customer0.id = Order0.customerId + INNER JOIN Pet0 ON Order0.petId = Pet0.id + WHERE Pet0.category::name = 'Dogs' AND "Order0".status = 'placed' + + 1STTT1.w.1.[Pet0.category::name = 'Dogs'] + SELECT * + FROM Customer0 + INNER JOIN "Order0" ON Customer0.id = Order0.customerId + INNER JOIN Pet0 ON Order0.petId = Pet0.id + WHERE (Pet0.category::name = 'Dogs') AND ("Order0".status = 'placed') + --A set of joined tables such that: +--The WHERE condition fulfills: + --(T) Pet0.category::name = 'Dogs' is TRUE + --(T) "Order0".status = 'placed' is TRUE + + 2STFT1.w.1.[Pet0.category::name = 'Dogs'] + SELECT * + FROM Customer0 + INNER JOIN "Order0" ON Customer0.id = Order0.customerId + INNER JOIN Pet0 ON Order0.petId = Pet0.id + WHERE NOT(Pet0.category::name = 'Dogs') AND ("Order0".status = 'placed') + --A set of joined tables such that: +--The WHERE condition fulfills: + --(F) Pet0.category::name = 'Dogs' is FALSE + --(T) "Order0".status = 'placed' is TRUE + + 3STFT1.w.2.["Order0".status = 'placed'] + SELECT * + FROM Customer0 + INNER JOIN "Order0" ON Customer0.id = Order0.customerId + INNER JOIN Pet0 ON Order0.petId = Pet0.id + WHERE NOT("Order0".status = 'placed') AND (Pet0.category::name = 'Dogs') + --A set of joined tables such that: +--The WHERE condition fulfills: + --(F) "Order0".status = 'placed' is FALSE + --(T) Pet0.category::name = 'Dogs' is TRUE + + 4JLO1.j.1.[INNER JOIN] + SELECT * + FROM Customer0 + LEFT JOIN "Order0" ON Customer0.id = Order0.customerId + LEFT JOIN Pet0 ON Order0.petId = Pet0.id + WHERE (Order0.customerId IS NULL) AND (Customer0.id IS NOT NULL) + +--The JOIN tables fulfill: +--COMMENT: Reduced conditions: [Pet0.category::name = 'Dogs'] ; ["Order0".status = 'placed'] ; + --summary: [Customer0] left [Order0] ON Customer0.id = Order0.customerId + --There exist some row in table Customer0 + --which does not join to any table in Order0 + + 5JRO1.j.2.[INNER JOIN] + SELECT * + FROM Customer0 + RIGHT JOIN "Order0" ON Customer0.id = Order0.customerId + RIGHT JOIN Pet0 ON Order0.petId = Pet0.id + WHERE ((Order0.petId IS NULL) AND (Pet0.id IS NOT NULL)) + AND (Pet0.category::name = 'Dogs') + +--The JOIN tables fulfill: +--COMMENT: Reduced conditions: ["Order0".status = 'placed'] ; + --summary: [Order0] right [Pet0] ON Order0.petId = Pet0.id + --There exist some row in table Pet0 + --which does not join to any table in Order0 +--The following condition in the WHERE holds: + --WHERE Pet0.category::name = 'Dogs' + + + \ No newline at end of file diff --git a/st-tdg-test/src/test/resources/petstore/rules-placed-pet0-orders-with-alias.xml b/st-tdg-test/src/test/resources/petstore/rules-placed-pet0-orders-with-alias.xml new file mode 100644 index 0000000..7e481e3 --- /dev/null +++ b/st-tdg-test/src/test/resources/petstore/rules-placed-pet0-orders-with-alias.xml @@ -0,0 +1,74 @@ + + +0.0.0 +tds Customer0 c, "Order0" o, Pet0 where category::name='Dogs' and o.status='placed' +SELECT * + FROM Customer0 c + INNER JOIN "Order0" o ON c.id = o.customerId + INNER JOIN Pet0 ON o.petId = Pet0.id + WHERE Pet0.category::name = 'Dogs' AND o.status = 'placed' + + 1STTT1.w.1.[Pet0.category::name = 'Dogs'] + SELECT * + FROM Customer0 c + INNER JOIN "Order0" o ON c.id = o.customerId + INNER JOIN Pet0 ON o.petId = Pet0.id + WHERE (Pet0.category::name = 'Dogs') AND (o.status = 'placed') + --A set of joined tables such that: +--The WHERE condition fulfills: + --(T) Pet0.category::name = 'Dogs' is TRUE + --(T) o.status = 'placed' is TRUE + + 2STFT1.w.1.[Pet0.category::name = 'Dogs'] + SELECT * + FROM Customer0 c + INNER JOIN "Order0" o ON c.id = o.customerId + INNER JOIN Pet0 ON o.petId = Pet0.id + WHERE NOT(Pet0.category::name = 'Dogs') AND (o.status = 'placed') + --A set of joined tables such that: +--The WHERE condition fulfills: + --(F) Pet0.category::name = 'Dogs' is FALSE + --(T) o.status = 'placed' is TRUE + + 3STFT1.w.2.[o.status = 'placed'] + SELECT * + FROM Customer0 c + INNER JOIN "Order0" o ON c.id = o.customerId + INNER JOIN Pet0 ON o.petId = Pet0.id + WHERE NOT(o.status = 'placed') AND (Pet0.category::name = 'Dogs') + --A set of joined tables such that: +--The WHERE condition fulfills: + --(F) o.status = 'placed' is FALSE + --(T) Pet0.category::name = 'Dogs' is TRUE + + 4JLO1.j.1.[INNER JOIN] + SELECT * + FROM Customer0 c + LEFT JOIN "Order0" o ON c.id = o.customerId + LEFT JOIN Pet0 ON o.petId = Pet0.id + WHERE (o.customerId IS NULL) AND (c.id IS NOT NULL) + +--The JOIN tables fulfill: +--COMMENT: Reduced conditions: [Pet0.category::name = 'Dogs'] ; [o.status = 'placed'] ; + --summary: [Customer0 c] left [Order0 o] ON c.id = o.customerId + --There exist some row in table Customer0 c + --which does not join to any table in Order0 o + + 5JRO1.j.2.[INNER JOIN] + SELECT * + FROM Customer0 c + RIGHT JOIN "Order0" o ON c.id = o.customerId + RIGHT JOIN Pet0 ON o.petId = Pet0.id + WHERE ((o.petId IS NULL) AND (Pet0.id IS NOT NULL)) + AND (Pet0.category::name = 'Dogs') + +--The JOIN tables fulfill: +--COMMENT: Reduced conditions: [o.status = 'placed'] ; + --summary: [Order0 o] right [Pet0] ON o.petId = Pet0.id + --There exist some row in table Pet0 + --which does not join to any table in Order0 o +--The following condition in the WHERE holds: + --WHERE Pet0.category::name = 'Dogs' + + + \ No newline at end of file diff --git a/st-tdg-test/src/test/resources/petstore/rules-smoke.xml b/st-tdg-test/src/test/resources/petstore/rules-smoke.xml new file mode 100644 index 0000000..9760c3f --- /dev/null +++ b/st-tdg-test/src/test/resources/petstore/rules-smoke.xml @@ -0,0 +1,26 @@ + + +0.0.0 +select * from Category where name='Dogs' +SELECT * + FROM Category + WHERE name = 'Dogs' + + 1STFF1.w.1.[WHERE name = 'Dogs'] + SELECT * + FROM Category + WHERE NOT(name = 'Dogs') + --Some row in the table such that: +--The WHERE condition fulfills: + --(F) name = 'Dogs' is FALSE + + 2STTF1.w.1.[WHERE name = 'Dogs'] + SELECT * + FROM Category + WHERE (name = 'Dogs') + --Some row in the table such that: +--The WHERE condition fulfills: + --(T) name = 'Dogs' is TRUE + + + \ No newline at end of file diff --git a/st-tdg-test/src/test/resources/petstore/rules-total-pet-orders-by-address.xml b/st-tdg-test/src/test/resources/petstore/rules-total-pet-orders-by-address.xml new file mode 100644 index 0000000..fb224d3 --- /dev/null +++ b/st-tdg-test/src/test/resources/petstore/rules-total-pet-orders-by-address.xml @@ -0,0 +1,189 @@ + + +0.0.0 +tds Customer, "Order", Pet where "Order".status='approved' group by Customer.address[]::zip +SELECT Customer_address_xa.zip , COUNT(*) + FROM Customer + LEFT JOIN Customer_address_xa ON Customer.id = Customer_address_xa.fk_xa + INNER JOIN "Order" ON Customer.id = Order.customerId + INNER JOIN Pet ON Order.petId = Pet.id + LEFT JOIN Pet_tags_xa ON Pet.id = Pet_tags_xa.fk_xa + LEFT JOIN Pet_photoUrls_xa ON Pet.id = Pet_photoUrls_xa.fk_xa + LEFT JOIN Category Pet_category_xref ON Pet.category::id = Pet_category_xref.id + WHERE "Order".status = 'approved' + GROUP BY Customer_address_xa.zip + + 1STFF1.w.1.[WHERE "Order".status = 'approved'] + SELECT Customer_address_xa.zip , COUNT(*) + FROM Customer + INNER JOIN Customer_address_xa ON Customer.id = Customer_address_xa.fk_xa + INNER JOIN "Order" ON Customer.id = Order.customerId + INNER JOIN Pet ON Order.petId = Pet.id + INNER JOIN Pet_tags_xa ON Pet.id = Pet_tags_xa.fk_xa + INNER JOIN Pet_photoUrls_xa ON Pet.id = Pet_photoUrls_xa.fk_xa + INNER JOIN Category Pet_category_xref ON Pet.category::id = Pet_category_xref.id + WHERE NOT("Order".status = 'approved') + GROUP BY Customer_address_xa.zip + --A set of joined tables such that: +--The WHERE condition fulfills: + --(F) "Order".status = 'approved' is FALSE + + 2STTF1.w.1.[WHERE "Order".status = 'approved'] + SELECT Customer_address_xa.zip , COUNT(*) + FROM Customer + INNER JOIN Customer_address_xa ON Customer.id = Customer_address_xa.fk_xa + INNER JOIN "Order" ON Customer.id = Order.customerId + INNER JOIN Pet ON Order.petId = Pet.id + INNER JOIN Pet_tags_xa ON Pet.id = Pet_tags_xa.fk_xa + INNER JOIN Pet_photoUrls_xa ON Pet.id = Pet_photoUrls_xa.fk_xa + INNER JOIN Category Pet_category_xref ON Pet.category::id = Pet_category_xref.id + WHERE ("Order".status = 'approved') + GROUP BY Customer_address_xa.zip + --A set of joined tables such that: +--The WHERE condition fulfills: + --(T) "Order".status = 'approved' is TRUE + + 3JLO1.j.1.[LEFT JOIN] + SELECT Customer_address_xa.zip , COUNT(*) + FROM Customer + LEFT JOIN Customer_address_xa ON Customer.id = Customer_address_xa.fk_xa + INNER JOIN "Order" ON Customer.id = Order.customerId + INNER JOIN Pet ON Order.petId = Pet.id + INNER JOIN Pet_tags_xa ON Pet.id = Pet_tags_xa.fk_xa + INNER JOIN Pet_photoUrls_xa ON Pet.id = Pet_photoUrls_xa.fk_xa + INNER JOIN Category Pet_category_xref ON Pet.category::id = Pet_category_xref.id + WHERE ((Customer_address_xa.fk_xa IS NULL) AND (Customer.id IS NOT NULL)) + AND ("Order".status = 'approved') + GROUP BY Customer_address_xa.zip + +--The JOIN tables fulfill: + --summary: [Customer] left [Customer_address_xa] ON Customer.id = Customer_address_xa.fk_xa + --There exist a set of rows joined from tables Customer, Order, Pet, Pet_tags_xa, Pet_photoUrls_xa, Category Pet_category_xref + --which does not join to any table in Customer_address_xa +--The WHERE condition is TRUE. + + 4JLO1.j.2.[INNER JOIN] + SELECT Customer_address_xa.zip , COUNT(*) + FROM Customer + INNER JOIN Customer_address_xa ON Customer.id = Customer_address_xa.fk_xa + LEFT JOIN "Order" ON Customer.id = Order.customerId + LEFT JOIN Pet ON Order.petId = Pet.id + LEFT JOIN Pet_tags_xa ON Pet.id = Pet_tags_xa.fk_xa + LEFT JOIN Pet_photoUrls_xa ON Pet.id = Pet_photoUrls_xa.fk_xa + LEFT JOIN Category Pet_category_xref ON Pet.category::id = Pet_category_xref.id + WHERE (Order.customerId IS NULL) AND (Customer.id IS NOT NULL) + GROUP BY Customer_address_xa.zip + +--The JOIN tables fulfill: +--COMMENT: Reduced conditions: [WHERE "Order".status = 'approved'] ; + --summary: [Customer] left [Order] ON Customer.id = Order.customerId + --There exist a set of rows joined from tables Customer, Customer_address_xa + --which does not join to any table in Order + + 5JRO1.j.3.[INNER JOIN] + SELECT Customer_address_xa.zip , COUNT(*) + FROM Customer + LEFT JOIN Customer_address_xa ON Customer.id = Customer_address_xa.fk_xa + RIGHT JOIN "Order" ON Customer.id = Order.customerId + RIGHT JOIN Pet ON Order.petId = Pet.id + INNER JOIN Pet_tags_xa ON Pet.id = Pet_tags_xa.fk_xa + INNER JOIN Pet_photoUrls_xa ON Pet.id = Pet_photoUrls_xa.fk_xa + INNER JOIN Category Pet_category_xref ON Pet.category::id = Pet_category_xref.id + WHERE (Order.petId IS NULL) AND (Pet.id IS NOT NULL) + GROUP BY Customer_address_xa.zip + +--The JOIN tables fulfill: +--COMMENT: Reduced conditions: [WHERE "Order".status = 'approved'] ; + --summary: [Order] right [Pet] ON Order.petId = Pet.id + --There exist a set of rows joined from tables Pet, Pet_tags_xa, Pet_photoUrls_xa, Category Pet_category_xref + --which does not join to any table in Order + + 6JLO1.j.4.[LEFT JOIN] + SELECT Customer_address_xa.zip , COUNT(*) + FROM Customer + INNER JOIN Customer_address_xa ON Customer.id = Customer_address_xa.fk_xa + INNER JOIN "Order" ON Customer.id = Order.customerId + INNER JOIN Pet ON Order.petId = Pet.id + LEFT JOIN Pet_tags_xa ON Pet.id = Pet_tags_xa.fk_xa + INNER JOIN Pet_photoUrls_xa ON Pet.id = Pet_photoUrls_xa.fk_xa + INNER JOIN Category Pet_category_xref ON Pet.category::id = Pet_category_xref.id + WHERE ((Pet_tags_xa.fk_xa IS NULL) AND (Pet.id IS NOT NULL)) + AND ("Order".status = 'approved') + GROUP BY Customer_address_xa.zip + +--The JOIN tables fulfill: + --summary: [Pet] left [Pet_tags_xa] ON Pet.id = Pet_tags_xa.fk_xa + --There exist a set of rows joined from tables Customer, Customer_address_xa, Order, Pet, Pet_photoUrls_xa, Category Pet_category_xref + --which does not join to any table in Pet_tags_xa +--The WHERE condition is TRUE. + + 7JLO1.j.5.[LEFT JOIN] + SELECT Customer_address_xa.zip , COUNT(*) + FROM Customer + INNER JOIN Customer_address_xa ON Customer.id = Customer_address_xa.fk_xa + INNER JOIN "Order" ON Customer.id = Order.customerId + INNER JOIN Pet ON Order.petId = Pet.id + INNER JOIN Pet_tags_xa ON Pet.id = Pet_tags_xa.fk_xa + LEFT JOIN Pet_photoUrls_xa ON Pet.id = Pet_photoUrls_xa.fk_xa + INNER JOIN Category Pet_category_xref ON Pet.category::id = Pet_category_xref.id + WHERE ((Pet_photoUrls_xa.fk_xa IS NULL) AND (Pet.id IS NOT NULL)) + AND ("Order".status = 'approved') + GROUP BY Customer_address_xa.zip + +--The JOIN tables fulfill: + --summary: [Pet] left [Pet_photoUrls_xa] ON Pet.id = Pet_photoUrls_xa.fk_xa + --There exist a set of rows joined from tables Customer, Customer_address_xa, Order, Pet, Pet_tags_xa, Category Pet_category_xref + --which does not join to any table in Pet_photoUrls_xa +--The WHERE condition is TRUE. + + 8JRO1.j.6.[LEFT JOIN] + SELECT Customer_address_xa.zip , COUNT(*) + FROM Customer + LEFT JOIN Customer_address_xa ON Customer.id = Customer_address_xa.fk_xa + RIGHT JOIN "Order" ON Customer.id = Order.customerId + RIGHT JOIN Pet ON Order.petId = Pet.id + LEFT JOIN Pet_tags_xa ON Pet.id = Pet_tags_xa.fk_xa + LEFT JOIN Pet_photoUrls_xa ON Pet.id = Pet_photoUrls_xa.fk_xa + RIGHT JOIN Category Pet_category_xref ON Pet.category::id = Pet_category_xref.id + WHERE (Pet.category::id IS NULL) AND (Pet_category_xref.id IS NOT NULL) + GROUP BY Customer_address_xa.zip + +--The JOIN tables fulfill: +--COMMENT: Reduced conditions: [WHERE "Order".status = 'approved'] ; + --summary: [Pet] right [Category Pet_category_xref] ON Pet.category::id = Pet_category_xref.id + --There exist some row in table Category Pet_category_xref + --which does not join to any table in Pet + + 9GGG1.g.[GROUP BY Customer_address_xa.zip] + SELECT Customer_address_xa.zip , COUNT(*) + FROM Customer + LEFT JOIN Customer_address_xa ON Customer.id = Customer_address_xa.fk_xa + INNER JOIN "Order" ON Customer.id = Order.customerId + INNER JOIN Pet ON Order.petId = Pet.id + LEFT JOIN Pet_tags_xa ON Pet.id = Pet_tags_xa.fk_xa + LEFT JOIN Pet_photoUrls_xa ON Pet.id = Pet_photoUrls_xa.fk_xa + LEFT JOIN Category Pet_category_xref ON Pet.category::id = Pet_category_xref.id + WHERE "Order".status = 'approved' + GROUP BY Customer_address_xa.zip + HAVING count(*)>1 + --A set of joined tables such that: +--The WHERE condition is TRUE. +--There exist a set of rows in the input tables that make up a group with at least two rows + + 10GGA1.g.1.[Customer_address_xa.zip] + SELECT COUNT(*) + FROM Customer + LEFT JOIN Customer_address_xa ON Customer.id = Customer_address_xa.fk_xa + INNER JOIN "Order" ON Customer.id = Order.customerId + INNER JOIN Pet ON Order.petId = Pet.id + LEFT JOIN Pet_tags_xa ON Pet.id = Pet_tags_xa.fk_xa + LEFT JOIN Pet_photoUrls_xa ON Pet.id = Pet_photoUrls_xa.fk_xa + LEFT JOIN Category Pet_category_xref ON Pet.category::id = Pet_category_xref.id + WHERE "Order".status = 'approved' + HAVING count(distinct Customer_address_xa.zip)>1 + --A set of joined tables such that: +--The WHERE condition is TRUE. +--There exist a set of rows in the input tables that make up groups that are different only because of the filed: Customer_address_xa.zip + + + \ No newline at end of file diff --git a/st-tdg-test/src/test/resources/petstore/rules-total-pets-by-address.xml b/st-tdg-test/src/test/resources/petstore/rules-total-pets-by-address.xml new file mode 100644 index 0000000..1776bb6 --- /dev/null +++ b/st-tdg-test/src/test/resources/petstore/rules-total-pets-by-address.xml @@ -0,0 +1,206 @@ + + +0.0.0 +select Customer.address[]::zip, sum("Order".quantity) from Customer, "Order", Pet where "Order".status='approved' group by Customer.address[]::zip +SELECT Customer_address_xa.zip , SUM("Order".quantity) + FROM Customer + LEFT JOIN Customer_address_xa ON Customer.id = Customer_address_xa.fk_xa + INNER JOIN "Order" ON Customer.id = Order.customerId + INNER JOIN Pet ON Order.petId = Pet.id + LEFT JOIN Pet_tags_xa ON Pet.id = Pet_tags_xa.fk_xa + LEFT JOIN Pet_photoUrls_xa ON Pet.id = Pet_photoUrls_xa.fk_xa + LEFT JOIN Category Pet_category_xref ON Pet.category::id = Pet_category_xref.id + WHERE "Order".status = 'approved' + GROUP BY Customer_address_xa.zip + + 1STFF1.w.1.[WHERE "Order".status = 'approved'] + SELECT Customer_address_xa.zip , SUM("Order".quantity) + FROM Customer + INNER JOIN Customer_address_xa ON Customer.id = Customer_address_xa.fk_xa + INNER JOIN "Order" ON Customer.id = Order.customerId + INNER JOIN Pet ON Order.petId = Pet.id + INNER JOIN Pet_tags_xa ON Pet.id = Pet_tags_xa.fk_xa + INNER JOIN Pet_photoUrls_xa ON Pet.id = Pet_photoUrls_xa.fk_xa + INNER JOIN Category Pet_category_xref ON Pet.category::id = Pet_category_xref.id + WHERE NOT("Order".status = 'approved') + GROUP BY Customer_address_xa.zip + --A set of joined tables such that: +--The WHERE condition fulfills: + --(F) "Order".status = 'approved' is FALSE + + 2STTF1.w.1.[WHERE "Order".status = 'approved'] + SELECT Customer_address_xa.zip , SUM("Order".quantity) + FROM Customer + INNER JOIN Customer_address_xa ON Customer.id = Customer_address_xa.fk_xa + INNER JOIN "Order" ON Customer.id = Order.customerId + INNER JOIN Pet ON Order.petId = Pet.id + INNER JOIN Pet_tags_xa ON Pet.id = Pet_tags_xa.fk_xa + INNER JOIN Pet_photoUrls_xa ON Pet.id = Pet_photoUrls_xa.fk_xa + INNER JOIN Category Pet_category_xref ON Pet.category::id = Pet_category_xref.id + WHERE ("Order".status = 'approved') + GROUP BY Customer_address_xa.zip + --A set of joined tables such that: +--The WHERE condition fulfills: + --(T) "Order".status = 'approved' is TRUE + + 3JLO1.j.1.[LEFT JOIN] + SELECT Customer_address_xa.zip , SUM("Order".quantity) + FROM Customer + LEFT JOIN Customer_address_xa ON Customer.id = Customer_address_xa.fk_xa + INNER JOIN "Order" ON Customer.id = Order.customerId + INNER JOIN Pet ON Order.petId = Pet.id + INNER JOIN Pet_tags_xa ON Pet.id = Pet_tags_xa.fk_xa + INNER JOIN Pet_photoUrls_xa ON Pet.id = Pet_photoUrls_xa.fk_xa + INNER JOIN Category Pet_category_xref ON Pet.category::id = Pet_category_xref.id + WHERE ((Customer_address_xa.fk_xa IS NULL) AND (Customer.id IS NOT NULL)) + AND ("Order".status = 'approved') + GROUP BY Customer_address_xa.zip + +--The JOIN tables fulfill: + --summary: [Customer] left [Customer_address_xa] ON Customer.id = Customer_address_xa.fk_xa + --There exist a set of rows joined from tables Customer, Order, Pet, Pet_tags_xa, Pet_photoUrls_xa, Category Pet_category_xref + --which does not join to any table in Customer_address_xa +--The WHERE condition is TRUE. + + 4JLO1.j.2.[INNER JOIN] + SELECT Customer_address_xa.zip , SUM("Order".quantity) + FROM Customer + INNER JOIN Customer_address_xa ON Customer.id = Customer_address_xa.fk_xa + LEFT JOIN "Order" ON Customer.id = Order.customerId + LEFT JOIN Pet ON Order.petId = Pet.id + LEFT JOIN Pet_tags_xa ON Pet.id = Pet_tags_xa.fk_xa + LEFT JOIN Pet_photoUrls_xa ON Pet.id = Pet_photoUrls_xa.fk_xa + LEFT JOIN Category Pet_category_xref ON Pet.category::id = Pet_category_xref.id + WHERE (Order.customerId IS NULL) AND (Customer.id IS NOT NULL) + GROUP BY Customer_address_xa.zip + +--The JOIN tables fulfill: +--COMMENT: Reduced conditions: [WHERE "Order".status = 'approved'] ; + --summary: [Customer] left [Order] ON Customer.id = Order.customerId + --There exist a set of rows joined from tables Customer, Customer_address_xa + --which does not join to any table in Order + + 5JRO1.j.3.[INNER JOIN] + SELECT Customer_address_xa.zip , SUM("Order".quantity) + FROM Customer + LEFT JOIN Customer_address_xa ON Customer.id = Customer_address_xa.fk_xa + RIGHT JOIN "Order" ON Customer.id = Order.customerId + RIGHT JOIN Pet ON Order.petId = Pet.id + INNER JOIN Pet_tags_xa ON Pet.id = Pet_tags_xa.fk_xa + INNER JOIN Pet_photoUrls_xa ON Pet.id = Pet_photoUrls_xa.fk_xa + INNER JOIN Category Pet_category_xref ON Pet.category::id = Pet_category_xref.id + WHERE (Order.petId IS NULL) AND (Pet.id IS NOT NULL) + GROUP BY Customer_address_xa.zip + +--The JOIN tables fulfill: +--COMMENT: Reduced conditions: [WHERE "Order".status = 'approved'] ; + --summary: [Order] right [Pet] ON Order.petId = Pet.id + --There exist a set of rows joined from tables Pet, Pet_tags_xa, Pet_photoUrls_xa, Category Pet_category_xref + --which does not join to any table in Order + + 6JLO1.j.4.[LEFT JOIN] + SELECT Customer_address_xa.zip , SUM("Order".quantity) + FROM Customer + INNER JOIN Customer_address_xa ON Customer.id = Customer_address_xa.fk_xa + INNER JOIN "Order" ON Customer.id = Order.customerId + INNER JOIN Pet ON Order.petId = Pet.id + LEFT JOIN Pet_tags_xa ON Pet.id = Pet_tags_xa.fk_xa + INNER JOIN Pet_photoUrls_xa ON Pet.id = Pet_photoUrls_xa.fk_xa + INNER JOIN Category Pet_category_xref ON Pet.category::id = Pet_category_xref.id + WHERE ((Pet_tags_xa.fk_xa IS NULL) AND (Pet.id IS NOT NULL)) + AND ("Order".status = 'approved') + GROUP BY Customer_address_xa.zip + +--The JOIN tables fulfill: + --summary: [Pet] left [Pet_tags_xa] ON Pet.id = Pet_tags_xa.fk_xa + --There exist a set of rows joined from tables Customer, Customer_address_xa, Order, Pet, Pet_photoUrls_xa, Category Pet_category_xref + --which does not join to any table in Pet_tags_xa +--The WHERE condition is TRUE. + + 7JLO1.j.5.[LEFT JOIN] + SELECT Customer_address_xa.zip , SUM("Order".quantity) + FROM Customer + INNER JOIN Customer_address_xa ON Customer.id = Customer_address_xa.fk_xa + INNER JOIN "Order" ON Customer.id = Order.customerId + INNER JOIN Pet ON Order.petId = Pet.id + INNER JOIN Pet_tags_xa ON Pet.id = Pet_tags_xa.fk_xa + LEFT JOIN Pet_photoUrls_xa ON Pet.id = Pet_photoUrls_xa.fk_xa + INNER JOIN Category Pet_category_xref ON Pet.category::id = Pet_category_xref.id + WHERE ((Pet_photoUrls_xa.fk_xa IS NULL) AND (Pet.id IS NOT NULL)) + AND ("Order".status = 'approved') + GROUP BY Customer_address_xa.zip + +--The JOIN tables fulfill: + --summary: [Pet] left [Pet_photoUrls_xa] ON Pet.id = Pet_photoUrls_xa.fk_xa + --There exist a set of rows joined from tables Customer, Customer_address_xa, Order, Pet, Pet_tags_xa, Category Pet_category_xref + --which does not join to any table in Pet_photoUrls_xa +--The WHERE condition is TRUE. + + 8JRO1.j.6.[LEFT JOIN] + SELECT Customer_address_xa.zip , SUM("Order".quantity) + FROM Customer + LEFT JOIN Customer_address_xa ON Customer.id = Customer_address_xa.fk_xa + RIGHT JOIN "Order" ON Customer.id = Order.customerId + RIGHT JOIN Pet ON Order.petId = Pet.id + LEFT JOIN Pet_tags_xa ON Pet.id = Pet_tags_xa.fk_xa + LEFT JOIN Pet_photoUrls_xa ON Pet.id = Pet_photoUrls_xa.fk_xa + RIGHT JOIN Category Pet_category_xref ON Pet.category::id = Pet_category_xref.id + WHERE (Pet.category::id IS NULL) AND (Pet_category_xref.id IS NOT NULL) + GROUP BY Customer_address_xa.zip + +--The JOIN tables fulfill: +--COMMENT: Reduced conditions: [WHERE "Order".status = 'approved'] ; + --summary: [Pet] right [Category Pet_category_xref] ON Pet.category::id = Pet_category_xref.id + --There exist some row in table Category Pet_category_xref + --which does not join to any table in Pet + + 9GGG1.g.[GROUP BY Customer_address_xa.zip] + SELECT Customer_address_xa.zip , SUM("Order".quantity) + FROM Customer + LEFT JOIN Customer_address_xa ON Customer.id = Customer_address_xa.fk_xa + INNER JOIN "Order" ON Customer.id = Order.customerId + INNER JOIN Pet ON Order.petId = Pet.id + LEFT JOIN Pet_tags_xa ON Pet.id = Pet_tags_xa.fk_xa + LEFT JOIN Pet_photoUrls_xa ON Pet.id = Pet_photoUrls_xa.fk_xa + LEFT JOIN Category Pet_category_xref ON Pet.category::id = Pet_category_xref.id + WHERE "Order".status = 'approved' + GROUP BY Customer_address_xa.zip + HAVING count(*)>1 + --A set of joined tables such that: +--The WHERE condition is TRUE. +--There exist a set of rows in the input tables that make up a group with at least two rows + + 10GGA1.g.1.[Customer_address_xa.zip] + SELECT COUNT(*) + FROM Customer + LEFT JOIN Customer_address_xa ON Customer.id = Customer_address_xa.fk_xa + INNER JOIN "Order" ON Customer.id = Order.customerId + INNER JOIN Pet ON Order.petId = Pet.id + LEFT JOIN Pet_tags_xa ON Pet.id = Pet_tags_xa.fk_xa + LEFT JOIN Pet_photoUrls_xa ON Pet.id = Pet_photoUrls_xa.fk_xa + LEFT JOIN Category Pet_category_xref ON Pet.category::id = Pet_category_xref.id + WHERE "Order".status = 'approved' + HAVING count(distinct Customer_address_xa.zip)>1 + --A set of joined tables such that: +--The WHERE condition is TRUE. +--There exist a set of rows in the input tables that make up groups that are different only because of the filed: Customer_address_xa.zip + + 11GAA1.g.1.[SUM("Order".quantity)] + SELECT Customer_address_xa.zip , SUM("Order".quantity) + FROM Customer + LEFT JOIN Customer_address_xa ON Customer.id = Customer_address_xa.fk_xa + INNER JOIN "Order" ON Customer.id = Order.customerId + INNER JOIN Pet ON Order.petId = Pet.id + LEFT JOIN Pet_tags_xa ON Pet.id = Pet_tags_xa.fk_xa + LEFT JOIN Pet_photoUrls_xa ON Pet.id = Pet_photoUrls_xa.fk_xa + LEFT JOIN Category Pet_category_xref ON Pet.category::id = Pet_category_xref.id + WHERE "Order".status = 'approved' + GROUP BY Customer_address_xa.zip + HAVING COUNT("Order".quantity) > COUNT(DISTINCT "Order".quantity) + AND COUNT(DISTINCT "Order".quantity) > 1 + --A set of joined tables such that: +--The WHERE condition is TRUE. +--There exist a set of rows in the input tables that make up a group with at least two equal values and another one distinct in: "Order".quantity + + + \ No newline at end of file diff --git a/st-tdg-test/src/test/resources/petstore/schema-petstore.json b/st-tdg-test/src/test/resources/petstore/schema-petstore.json new file mode 100644 index 0000000..ab828fc --- /dev/null +++ b/st-tdg-test/src/test/resources/petstore/schema-petstore.json @@ -0,0 +1,451 @@ +{ + "storetype" : "openapi", + "entities" : [ { + "name" : "Order", + "entitytype" : "table", + "attributes" : [ { + "name" : "id", + "datatype" : "int64", + "uid" : "true", + "notnull" : "true" + }, { + "name" : "petId", + "datatype" : "int64", + "notnull" : "true", + "rid" : "Pet.id", + "ridname" : "fk_Order_petId" + }, { + "name" : "customerId", + "datatype" : "int64", + "notnull" : "true", + "rid" : "Customer.id", + "ridname" : "fk_Order_customerId" + }, { + "name" : "quantity", + "datatype" : "int32", + "notnull" : "true" + }, { + "name" : "shipDate", + "datatype" : "date-time", + "notnull" : "true" + }, { + "name" : "status", + "datatype" : "string", + "notnull" : "true", + "checkin" : "placed,approved,delivered" + }, { + "name" : "complete", + "datatype" : "boolean", + "notnull" : "true" + } ], + "ddls" : [ { + "command" : "post", + "query" : "/store/order" + } ] + }, { + "name" : "Customer_address_xa", + "entitytype" : "array", + "subtype" : "Address", + "attributes" : [ { + "name" : "pk_xa", + "datatype" : "integer", + "uid" : "true", + "notnull" : "true" + }, { + "name" : "fk_xa", + "datatype" : "int64", + "notnull" : "true", + "rid" : "Customer.id" + }, { + "name" : "street", + "datatype" : "string", + "notnull" : "true" + }, { + "name" : "city", + "datatype" : "string", + "notnull" : "true" + }, { + "name" : "state", + "datatype" : "string", + "notnull" : "true" + }, { + "name" : "zip", + "datatype" : "string", + "notnull" : "true" + } ] + }, { + "name" : "Customer", + "entitytype" : "table", + "attributes" : [ { + "name" : "id", + "datatype" : "int64", + "uid" : "true", + "notnull" : "true" + }, { + "name" : "username", + "datatype" : "string", + "notnull" : "true" + }, { + "name" : "address", + "datatype" : "Customer_address_xa", + "compositetype" : "array", + "subtype" : "object", + "notnull" : "true" + } ], + "ddls" : [ { + "command" : "post", + "query" : "/store/customer" + } ] + }, { + "name" : "Address", + "entitytype" : "table", + "attributes" : [ { + "name" : "street", + "datatype" : "string", + "notnull" : "true" + }, { + "name" : "city", + "datatype" : "string", + "notnull" : "true" + }, { + "name" : "state", + "datatype" : "string", + "notnull" : "true" + }, { + "name" : "zip", + "datatype" : "string", + "notnull" : "true" + } ] + }, { + "name" : "Category", + "entitytype" : "table", + "attributes" : [ { + "name" : "id", + "datatype" : "int64", + "uid" : "true", + "notnull" : "true" + }, { + "name" : "name", + "datatype" : "string", + "notnull" : "true" + } ], + "ddls" : [ { + "command" : "post", + "query" : "/category" + }, { + "command" : "post", + "query" : "/backid/category" + } ] + }, { + "name" : "User", + "entitytype" : "table", + "attributes" : [ { + "name" : "id", + "datatype" : "int64", + "uid" : "true", + "notnull" : "true" + }, { + "name" : "username", + "datatype" : "string", + "notnull" : "true" + }, { + "name" : "firstName", + "datatype" : "string", + "notnull" : "true" + }, { + "name" : "lastName", + "datatype" : "string", + "notnull" : "true" + }, { + "name" : "email", + "datatype" : "string", + "notnull" : "true" + }, { + "name" : "password", + "datatype" : "string", + "notnull" : "true" + }, { + "name" : "phone", + "datatype" : "string", + "notnull" : "true" + }, { + "name" : "userStatus", + "datatype" : "int32", + "notnull" : "true" + } ], + "ddls" : [ { + "command" : "post", + "query" : "/user" + }, { + "command" : "put", + "query" : "/user/{username}" + } ] + }, { + "name" : "Tag", + "entitytype" : "table", + "attributes" : [ { + "name" : "id", + "datatype" : "int64", + "notnull" : "true" + }, { + "name" : "name", + "datatype" : "string", + "notnull" : "true" + } ] + }, { + "name" : "Pet_category_xt", + "entitytype" : "type", + "subtype" : "Category", + "attributes" : [ { + "name" : "id", + "datatype" : "int64", + "notnull" : "true", + "rid" : "Category.id" + }, { + "name" : "name", + "datatype" : "string", + "notnull" : "true" + } ] + }, { + "name" : "Pet_photoUrls_xa", + "entitytype" : "array", + "attributes" : [ { + "name" : "pk_xa", + "datatype" : "integer", + "uid" : "true", + "notnull" : "true" + }, { + "name" : "fk_xa", + "datatype" : "int64", + "notnull" : "true", + "rid" : "Pet.id" + }, { + "name" : "photoUrls", + "datatype" : "string", + "notnull" : "true" + } ] + }, { + "name" : "Pet_tags_xa", + "entitytype" : "array", + "subtype" : "Tag", + "attributes" : [ { + "name" : "pk_xa", + "datatype" : "integer", + "uid" : "true", + "notnull" : "true" + }, { + "name" : "fk_xa", + "datatype" : "int64", + "notnull" : "true", + "rid" : "Pet.id" + }, { + "name" : "id", + "datatype" : "int64", + "notnull" : "true" + }, { + "name" : "name", + "datatype" : "string", + "notnull" : "true" + } ] + }, { + "name" : "Pet", + "entitytype" : "table", + "attributes" : [ { + "name" : "id", + "datatype" : "int64", + "uid" : "true", + "notnull" : "true" + }, { + "name" : "name", + "datatype" : "string", + "notnull" : "true" + }, { + "name" : "category", + "datatype" : "Pet_category_xt", + "compositetype" : "type", + "notnull" : "true" + }, { + "name" : "photoUrls", + "datatype" : "Pet_photoUrls_xa", + "compositetype" : "array", + "subtype" : "string", + "notnull" : "true" + }, { + "name" : "tags", + "datatype" : "Pet_tags_xa", + "compositetype" : "array", + "subtype" : "object", + "notnull" : "true" + }, { + "name" : "status", + "datatype" : "string", + "notnull" : "true", + "checkin" : "available,pending,sold" + } ], + "ddls" : [ { + "command" : "post", + "query" : "/pet" + }, { + "command" : "put", + "query" : "/pet" + }, { + "command" : "post", + "query" : "/backid/pet" + } ] + }, { + "name" : "Customer0", + "entitytype" : "table", + "attributes" : [ { + "name" : "id", + "datatype" : "int64", + "uid" : "true", + "notnull" : "true" + }, { + "name" : "username", + "datatype" : "string", + "notnull" : "true" + } ], + "ddls" : [ { + "command" : "post", + "query" : "/store/customer0" + } ] + }, { + "name" : "Order0", + "entitytype" : "table", + "attributes" : [ { + "name" : "id", + "datatype" : "int64", + "uid" : "true", + "notnull" : "true" + }, { + "name" : "petId", + "datatype" : "int64", + "notnull" : "true", + "rid" : "Pet0.id", + "ridname" : "fk_Order0_petId" + }, { + "name" : "customerId", + "datatype" : "int64", + "notnull" : "true", + "rid" : "Customer0.id", + "ridname" : "fk_Order0_customerId" + }, { + "name" : "quantity", + "datatype" : "int32", + "notnull" : "true" + }, { + "name" : "shipDate", + "datatype" : "date-time", + "notnull" : "true" + }, { + "name" : "status", + "datatype" : "string", + "notnull" : "true", + "checkin" : "placed,approved,delivered" + }, { + "name" : "complete", + "datatype" : "boolean", + "notnull" : "true" + } ], + "ddls" : [ { + "command" : "post", + "query" : "/store/order0" + } ] + }, { + "name" : "Pet0_category_xt", + "entitytype" : "type", + "attributes" : [ { + "name" : "id", + "datatype" : "int64", + "notnull" : "true" + }, { + "name" : "name", + "datatype" : "string", + "notnull" : "true" + } ] + }, { + "name" : "Pet0", + "entitytype" : "table", + "attributes" : [ { + "name" : "id", + "datatype" : "int64", + "uid" : "true", + "notnull" : "true" + }, { + "name" : "name", + "datatype" : "string", + "notnull" : "true" + }, { + "name" : "category", + "datatype" : "Pet0_category_xt", + "compositetype" : "type", + "notnull" : "true" + }, { + "name" : "status", + "datatype" : "string", + "notnull" : "true", + "checkin" : "available,pending,sold" + } ], + "ddls" : [ { + "command" : "post", + "query" : "/pet0" + } ] + }, { + "name" : "Pet1_category_xt", + "entitytype" : "type", + "subtype" : "Category", + "attributes" : [ { + "name" : "id", + "datatype" : "int64", + "notnull" : "true", + "rid" : "Category.id" + }, { + "name" : "name", + "datatype" : "string", + "notnull" : "true" + } ] + }, { + "name" : "Pet1", + "entitytype" : "table", + "attributes" : [ { + "name" : "id", + "datatype" : "int64", + "uid" : "true", + "notnull" : "true" + }, { + "name" : "name", + "datatype" : "string", + "notnull" : "true" + }, { + "name" : "category", + "datatype" : "Pet1_category_xt", + "compositetype" : "type", + "notnull" : "true" + }, { + "name" : "status", + "datatype" : "string", + "notnull" : "true", + "checkin" : "available,pending,sold" + } ], + "ddls" : [ { + "command" : "post", + "query" : "/pet1" + } ] + }, { + "name" : "ApiResponse", + "entitytype" : "table", + "attributes" : [ { + "name" : "code", + "datatype" : "int32", + "notnull" : "true" + }, { + "name" : "type", + "datatype" : "string", + "notnull" : "true" + }, { + "name" : "message", + "datatype" : "string", + "notnull" : "true" + } ] + } ] +} \ No newline at end of file diff --git a/st-tdg-test/src/test/resources/petstore/schema-petstore.md b/st-tdg-test/src/test/resources/petstore/schema-petstore.md new file mode 100644 index 0000000..ace7981 --- /dev/null +++ b/st-tdg-test/src/test/resources/petstore/schema-petstore.md @@ -0,0 +1,32 @@ +classDiagram + Pet <--"*" Order + Customer <--"*" Order + Customer *--"*" Customer_address_xa + Category <--"*" Pet_category_xt + Pet *--"*" Pet_photoUrls_xa + Pet *--"*" Pet_tags_xa + Pet *--"1" Pet_category_xt + Pet0 <--"*" Order0 + Customer0 <--"*" Order0 + Pet0 *--"1" Pet0_category_xt + Category <--"*" Pet1_category_xt + Pet1 *--"1" Pet1_category_xt + Customer_address_xa ..|> Address + Pet_category_xt ..|> Category + Pet_tags_xa ..|> Tag + Pet1_category_xt ..|> Category + class User + class ApiResponse + Order: +post(/store/order) + Customer: +post(/store/customer) + Category: +post(/category) + Category: +post(/backid/category) + User: +post(/user) + User: +put(/user/{username}) + Pet: +post(/pet) + Pet: +put(/pet) + Pet: +post(/backid/pet) + Customer0: +post(/store/customer0) + Order0: +post(/store/order0) + Pet0: +post(/pet0) + Pet1: +post(/pet1) \ No newline at end of file diff --git a/st-tdg-test/src/test/resources/petstore/schema-petstore.xml b/st-tdg-test/src/test/resources/petstore/schema-petstore.xml new file mode 100644 index 0000000..4fa82cb --- /dev/null +++ b/st-tdg-test/src/test/resources/petstore/schema-petstore.xml @@ -0,0 +1,122 @@ + + + + + + + + + +/store/order +
+ + + + + + + +
+ + + + +/store/customer +
+ + + + + +
+ + + +/category +/backid/category +
+ + + + + + + + + +/user +/user/{username} +
+ + + +
+ + + +
+ + + + +
+ + + + + +
+ + + + + + + +/pet +/pet +/backid/pet +
+ + + +/store/customer0 +
+ + + + + + + + +/store/order0 +
+ + + +
+ + + + + +/pet0 +
+ + + +
+ + + + + +/pet1 +
+ + + + +
+
\ No newline at end of file diff --git a/sut-gestaoHospital/.gitignore b/sut-gestaoHospital/.gitignore new file mode 100644 index 0000000..486a506 --- /dev/null +++ b/sut-gestaoHospital/.gitignore @@ -0,0 +1,30 @@ +HELP.md +/target/ +/bin/ +/.mvn/ + +### STS ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache + +### IntelliJ IDEA ### +.idea +*.iws +*.iml +*.ipr + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ +/build/ + +### VS Code ### +.vscode/ diff --git a/sut-gestaoHospital/Dockerfile b/sut-gestaoHospital/Dockerfile new file mode 100644 index 0000000..80bcf95 --- /dev/null +++ b/sut-gestaoHospital/Dockerfile @@ -0,0 +1,8 @@ +FROM openjdk:8-jre-alpine + +WORKDIR /gestaoHospital +COPY target/*.jar /gestaoHospital/hospital.jar + +EXPOSE 8080 + +CMD ["java", "-jar", "/gestaoHospital/hospital.jar"] \ No newline at end of file diff --git a/sut-gestaoHospital/README.md b/sut-gestaoHospital/README.md new file mode 100644 index 0000000..6ee5499 --- /dev/null +++ b/sut-gestaoHospital/README.md @@ -0,0 +1,80 @@ +# Sistema de Gestão Hospitalar + +## Objetivo + +O objetivo do projeto é criar uma API para organizar um sistema público de saúde. + +## Contextualização + +O Sistema Único de Saúde (SUS) é um dos maiores e mais complexos sistemas de saúde pública do mundo, abrangendo desde o simples atendimento para avaliação da pressão arterial, por meio da Atenção Básica, até o transplante de órgãos, garantindo acesso integral, universal e gratuito para toda a população do país. Com a sua criação, o SUS proporcionou o acesso universal ao sistema público de saúde, sem discriminação. A atenção integral à saúde, e não somente aos cuidados assistenciais, passou a ser um direito de todos os brasileiros, desde a gestação e por toda a vida, com foco na saúde com qualidade de vida, visando a prevenção e a promoção da saúde. O objetivo desse projeto é criar uma ferramenta para auxiliar o SUS, evitar desperdício e potencializar os recursos a partir dos pacientes. + +Ao final do programa, além de apresentar a API criada, cada squad deverá expor quais são os pontos de melhoria e quais seriam os próximos passos no projeto, caso fossem seguir adiante. + +## Requisitos técnicos obrigatórios + +Para o projeto é necessário que as squads se utilizem dos aprendizados repassados pelo programa AceleraDev, portanto a solução deve ser construída de acordo com os seguintes requisitos: + +- Banco de dados; +- Desenvolvimento do backend e APIs com Java Spring Boot; +- Testes unitários são obrigatórios + +## Definições do Sistema + +- Todo hospital tem um estoque contendo diversos produtos. +- Um estoque tem produtos e suas respectivas quantidades. +- Dentro do estoque também tem banco de sangue +- O hospital também possui leitos. +- Ao encontrar um paciente é importante o recomendar para um hospital mais próximo e que tenha vaga disponível. +- Caso o hospital precise de um produto, por exemplo, um banco de sangue, é importante fazer o envio do hospital mais próximo ao local. +- Caso o hospital tenha apenas 4 itens ou 4 litros ele terá apenas o suficiente para o próprio hospital. + +## Exemplos de chamadas: + +A partir da API que gerencia os hospitais dentro do SUS o usuário conseguirá, por exemplo: + +- Cadastrar o hospital +- A partir da localização do paciente indicar o hospital mais próximo. +- Realizar check-in/ check-out do paciente no hospital +- Verificar quantos leitos disponíveis no hospital +- Cadastrar produtos e suas respectivas quantidades +- Cadastrar e gerenciar banco de sangue + +### /v1/hospitais/{id} + +Método: GET +Retorna as informações do hospital, por exemplo: + +- Nome +- Endereço +- Número de leitos +- Números de leitos disponíveis + +### /v1/hospitais/{id}/estoque + +Método: GET +Retorna as informações dos produtos existentes estoque, por exemplo. + +### /v1/hospitais/{id}/estoque/{produto} + +Método: GET +Retorna mais detalhes de um produto. + +- Nome +- Descrição +- Quantidade + +### /v1/hospitais/{id}/pacientes + +Método: GET +Retorna o nome dos pacientes dentro do hospital. + +### /v1/hospitais/{id}/pacientes/{paciente} + +Método: GET +Retorna todas as informações do paciente cadastrado, por exemplo: + +- Nome completo +- CPF +- Data de nascimento +- Sexo +- Data de entrada no hospital. diff --git a/sut-gestaoHospital/docker-compose-jenkins.yaml b/sut-gestaoHospital/docker-compose-jenkins.yaml new file mode 100644 index 0000000..03607be --- /dev/null +++ b/sut-gestaoHospital/docker-compose-jenkins.yaml @@ -0,0 +1,23 @@ +version: '3' +services: + mongodb: + container_name: swagger-gestaohospital-mongodb + image: mongo + #ports: + # - 8386:27017 + networks: + - net-swagger-test + gestaohospital: + container_name: swagger-gestaohospital-rest + image: gestaohospital + depends_on: + - mongodb + build: . + ports: + - 8385:8080 + networks: + - net-swagger-test +networks: + # all containers are in this network, same as the dedicated jenkins agent + net-swagger-test: + external: true diff --git a/sut-gestaoHospital/docker-compose.yaml b/sut-gestaoHospital/docker-compose.yaml new file mode 100644 index 0000000..b79ddad --- /dev/null +++ b/sut-gestaoHospital/docker-compose.yaml @@ -0,0 +1,16 @@ +version: '3' +services: + mongodb: + container_name: mongodb + image: mongo + #Only default port is defined: https://www.mongodb.com/docs/v3.4/reference/default-mongodb-port/ + ports: + - 8086:27017 + gestaohospital: + container_name: gestaohospital + image: gestaohospital + depends_on: + - mongodb + build: . + ports: + - 8085:8080 diff --git a/sut-gestaoHospital/hospital-ui/.editorconfig b/sut-gestaoHospital/hospital-ui/.editorconfig new file mode 100644 index 0000000..e89330a --- /dev/null +++ b/sut-gestaoHospital/hospital-ui/.editorconfig @@ -0,0 +1,13 @@ +# Editor configuration, see https://editorconfig.org +root = true + +[*] +charset = utf-8 +indent_style = space +indent_size = 2 +insert_final_newline = true +trim_trailing_whitespace = true + +[*.md] +max_line_length = off +trim_trailing_whitespace = false diff --git a/sut-gestaoHospital/hospital-ui/.gitignore b/sut-gestaoHospital/hospital-ui/.gitignore new file mode 100644 index 0000000..f4f46a5 --- /dev/null +++ b/sut-gestaoHospital/hospital-ui/.gitignore @@ -0,0 +1,46 @@ +# See http://help.github.com/ignore-files/ for more about ignoring files. + +# compiled output +/dist +/tmp +/out-tsc +# Only exists if Bazel was run +/bazel-out + +# dependencies +/node_modules + +# profiling files +chrome-profiler-events.json +speed-measure-plugin.json + +# IDEs and editors +/.idea +.project +.classpath +.c9/ +*.launch +.settings/ +*.sublime-workspace + +# IDE - VSCode +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json +.history/* + +# misc +/.sass-cache +/connect.lock +/coverage +/libpeerconnection.log +npm-debug.log +yarn-error.log +testem.log +/typings + +# System Files +.DS_Store +Thumbs.db diff --git a/sut-gestaoHospital/hospital-ui/README.md b/sut-gestaoHospital/hospital-ui/README.md new file mode 100644 index 0000000..c20c0a7 --- /dev/null +++ b/sut-gestaoHospital/hospital-ui/README.md @@ -0,0 +1,27 @@ +# HospitalUi + +This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 7.3.7. + +## Development server + +Run `ng serve` for a dev server. Navigate to `http://localhost:4200/`. The app will automatically reload if you change any of the source files. + +## Code scaffolding + +Run `ng generate component component-name` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module`. + +## Build + +Run `ng build` to build the project. The build artifacts will be stored in the `dist/` directory. Use the `--prod` flag for a production build. + +## Running unit tests + +Run `ng test` to execute the unit tests via [Karma](https://karma-runner.github.io). + +## Running end-to-end tests + +Run `ng e2e` to execute the end-to-end tests via [Protractor](http://www.protractortest.org/). + +## Further help + +To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI README](https://github.com/angular/angular-cli/blob/master/README.md). diff --git a/sut-gestaoHospital/hospital-ui/angular.json b/sut-gestaoHospital/hospital-ui/angular.json new file mode 100644 index 0000000..77e7508 --- /dev/null +++ b/sut-gestaoHospital/hospital-ui/angular.json @@ -0,0 +1,140 @@ +{ + "$schema": "./node_modules/@angular/cli/lib/config/schema.json", + "version": 1, + "newProjectRoot": "projects", + "projects": { + "hospital-ui": { + "root": "", + "sourceRoot": "src", + "projectType": "application", + "prefix": "app", + "schematics": {}, + "architect": { + "build": { + "builder": "@angular-devkit/build-angular:browser", + "options": { + "outputPath": "dist/hospital-ui", + "index": "src/index.html", + "main": "src/main.ts", + "polyfills": "src/polyfills.ts", + "tsConfig": "src/tsconfig.app.json", + "assets": [ + "src/favicon.ico", + "src/assets" + ], + "styles": [ + "node_modules/primeflex/primeflex.css", + "node_modules/primeicons/primeicons.css", + "node_modules/primeng/resources/themes/nova-light/theme.css", + "node_modules/primeng/resources/primeng.min.css", + "src/styles.css" + ], + "scripts": [], + "es5BrowserSupport": true + }, + "configurations": { + "production": { + "fileReplacements": [ + { + "replace": "src/environments/environment.ts", + "with": "src/environments/environment.prod.ts" + } + ], + "optimization": true, + "outputHashing": "all", + "sourceMap": false, + "extractCss": true, + "namedChunks": false, + "aot": true, + "extractLicenses": true, + "vendorChunk": false, + "buildOptimizer": true, + "budgets": [ + { + "type": "initial", + "maximumWarning": "2mb", + "maximumError": "5mb" + } + ] + } + } + }, + "serve": { + "builder": "@angular-devkit/build-angular:dev-server", + "options": { + "browserTarget": "hospital-ui:build" + }, + "configurations": { + "production": { + "browserTarget": "hospital-ui:build:production" + } + } + }, + "extract-i18n": { + "builder": "@angular-devkit/build-angular:extract-i18n", + "options": { + "browserTarget": "hospital-ui:build" + } + }, + "test": { + "builder": "@angular-devkit/build-angular:karma", + "options": { + "main": "src/test.ts", + "polyfills": "src/polyfills.ts", + "tsConfig": "src/tsconfig.spec.json", + "karmaConfig": "src/karma.conf.js", + "styles": [ + "src/styles.css" + ], + "scripts": [], + "assets": [ + "src/favicon.ico", + "src/assets" + ] + } + }, + "lint": { + "builder": "@angular-devkit/build-angular:tslint", + "options": { + "tsConfig": [ + "src/tsconfig.app.json", + "src/tsconfig.spec.json" + ], + "exclude": [ + "**/node_modules/**" + ] + } + } + } + }, + "hospital-ui-e2e": { + "root": "e2e/", + "projectType": "application", + "prefix": "", + "architect": { + "e2e": { + "builder": "@angular-devkit/build-angular:protractor", + "options": { + "protractorConfig": "e2e/protractor.conf.js", + "devServerTarget": "hospital-ui:serve" + }, + "configurations": { + "production": { + "devServerTarget": "hospital-ui:serve:production" + } + } + }, + "lint": { + "builder": "@angular-devkit/build-angular:tslint", + "options": { + "tsConfig": "e2e/tsconfig.e2e.json", + "exclude": [ + "**/node_modules/**" + ] + } + } + } + } + }, + "defaultProject": "hospital-ui" +} \ No newline at end of file diff --git a/sut-gestaoHospital/hospital-ui/e2e/protractor.conf.js b/sut-gestaoHospital/hospital-ui/e2e/protractor.conf.js new file mode 100644 index 0000000..86776a3 --- /dev/null +++ b/sut-gestaoHospital/hospital-ui/e2e/protractor.conf.js @@ -0,0 +1,28 @@ +// Protractor configuration file, see link for more information +// https://github.com/angular/protractor/blob/master/lib/config.ts + +const { SpecReporter } = require('jasmine-spec-reporter'); + +exports.config = { + allScriptsTimeout: 11000, + specs: [ + './src/**/*.e2e-spec.ts' + ], + capabilities: { + 'browserName': 'chrome' + }, + directConnect: true, + baseUrl: 'http://localhost:4200/', + framework: 'jasmine', + jasmineNodeOpts: { + showColors: true, + defaultTimeoutInterval: 30000, + print: function() {} + }, + onPrepare() { + require('ts-node').register({ + project: require('path').join(__dirname, './tsconfig.e2e.json') + }); + jasmine.getEnv().addReporter(new SpecReporter({ spec: { displayStacktrace: true } })); + } +}; \ No newline at end of file diff --git a/sut-gestaoHospital/hospital-ui/e2e/src/app.e2e-spec.ts b/sut-gestaoHospital/hospital-ui/e2e/src/app.e2e-spec.ts new file mode 100644 index 0000000..e5b57a9 --- /dev/null +++ b/sut-gestaoHospital/hospital-ui/e2e/src/app.e2e-spec.ts @@ -0,0 +1,23 @@ +import { AppPage } from './app.po'; +import { browser, logging } from 'protractor'; + +describe('workspace-project App', () => { + let page: AppPage; + + beforeEach(() => { + page = new AppPage(); + }); + + it('should display welcome message', () => { + page.navigateTo(); + expect(page.getTitleText()).toEqual('Welcome to hospital-ui!'); + }); + + afterEach(async () => { + // Assert that there are no errors emitted from the browser + const logs = await browser.manage().logs().get(logging.Type.BROWSER); + expect(logs).not.toContain(jasmine.objectContaining({ + level: logging.Level.SEVERE, + } as logging.Entry)); + }); +}); diff --git a/sut-gestaoHospital/hospital-ui/e2e/src/app.po.ts b/sut-gestaoHospital/hospital-ui/e2e/src/app.po.ts new file mode 100644 index 0000000..5776aa9 --- /dev/null +++ b/sut-gestaoHospital/hospital-ui/e2e/src/app.po.ts @@ -0,0 +1,11 @@ +import { browser, by, element } from 'protractor'; + +export class AppPage { + navigateTo() { + return browser.get(browser.baseUrl) as Promise; + } + + getTitleText() { + return element(by.css('app-root h1')).getText() as Promise; + } +} diff --git a/sut-gestaoHospital/hospital-ui/e2e/tsconfig.e2e.json b/sut-gestaoHospital/hospital-ui/e2e/tsconfig.e2e.json new file mode 100644 index 0000000..a6dd622 --- /dev/null +++ b/sut-gestaoHospital/hospital-ui/e2e/tsconfig.e2e.json @@ -0,0 +1,13 @@ +{ + "extends": "../tsconfig.json", + "compilerOptions": { + "outDir": "../out-tsc/app", + "module": "commonjs", + "target": "es5", + "types": [ + "jasmine", + "jasminewd2", + "node" + ] + } +} \ No newline at end of file diff --git a/sut-gestaoHospital/hospital-ui/iconHospital.ico b/sut-gestaoHospital/hospital-ui/iconHospital.ico new file mode 100644 index 0000000..e69de29 diff --git a/sut-gestaoHospital/hospital-ui/package-lock.json b/sut-gestaoHospital/hospital-ui/package-lock.json new file mode 100644 index 0000000..1d61057 --- /dev/null +++ b/sut-gestaoHospital/hospital-ui/package-lock.json @@ -0,0 +1,10523 @@ +{ + "name": "hospital-ui", + "version": "0.0.0", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "@angular-devkit/architect": { + "version": "0.13.7", + "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.13.7.tgz", + "integrity": "sha512-1N4R7cuwbbz7Z76v0Fi5nkxg6eS6FA58Y7SnTYCxI2HRJ1lqlafRjr89eoKzzbCH8ZGa7NG0wfRXtUwF/IrGmQ==", + "dev": true, + "requires": { + "@angular-devkit/core": "7.3.7", + "rxjs": "6.3.3" + } + }, + "@angular-devkit/build-angular": { + "version": "0.13.8", + "resolved": "https://registry.npmjs.org/@angular-devkit/build-angular/-/build-angular-0.13.8.tgz", + "integrity": "sha512-uRb8CKC0hUdcE+Fv2Ov9LJNelyjsiMuddBpo8pdTKCIHVVC6hvip9S/Z18Tvb207kKI3k7Dn+Ji1J63mCqmQzA==", + "dev": true, + "requires": { + "@angular-devkit/architect": "0.13.8", + "@angular-devkit/build-optimizer": "0.13.8", + "@angular-devkit/build-webpack": "0.13.8", + "@angular-devkit/core": "7.3.8", + "@ngtools/webpack": "7.3.8", + "ajv": "6.9.1", + "autoprefixer": "9.4.6", + "circular-dependency-plugin": "5.0.2", + "clean-css": "4.2.1", + "copy-webpack-plugin": "4.6.0", + "file-loader": "3.0.1", + "glob": "7.1.3", + "istanbul-instrumenter-loader": "3.0.1", + "karma-source-map-support": "1.3.0", + "less": "3.9.0", + "less-loader": "4.1.0", + "license-webpack-plugin": "2.1.0", + "loader-utils": "1.2.3", + "mini-css-extract-plugin": "0.5.0", + "minimatch": "3.0.4", + "node-sass": "4.11.0", + "open": "6.0.0", + "parse5": "4.0.0", + "postcss": "7.0.14", + "postcss-import": "12.0.1", + "postcss-loader": "3.0.0", + "raw-loader": "1.0.0", + "rxjs": "6.3.3", + "sass-loader": "7.1.0", + "semver": "5.6.0", + "source-map-loader": "0.2.4", + "source-map-support": "0.5.10", + "speed-measure-webpack-plugin": "1.3.1", + "stats-webpack-plugin": "0.7.0", + "style-loader": "0.23.1", + "stylus": "0.54.5", + "stylus-loader": "3.0.2", + "terser-webpack-plugin": "1.2.2", + "tree-kill": "1.2.1", + "webpack": "4.29.0", + "webpack-dev-middleware": "3.5.1", + "webpack-dev-server": "3.1.14", + "webpack-merge": "4.2.1", + "webpack-sources": "1.3.0", + "webpack-subresource-integrity": "1.1.0-rc.6" + }, + "dependencies": { + "@angular-devkit/architect": { + "version": "0.13.8", + "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.13.8.tgz", + "integrity": "sha512-gxUs5rhnP576T8ZclKqxlspiChrqRtqaJo54wqNVFvYKEjRZKyMa+1AK6p0oD9zcIToEkcjknj3BbtQa27lLHg==", + "dev": true, + "requires": { + "@angular-devkit/core": "7.3.8", + "rxjs": "6.3.3" + } + }, + "@angular-devkit/core": { + "version": "7.3.8", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-7.3.8.tgz", + "integrity": "sha512-3X9uzaZXFpm5o2TSzhD6wEOtVU32CgeytKjD1Scxj+uMMVo48SWLlKiFh312T+smI9ko7tOT8VqxglwYkWosgg==", + "dev": true, + "requires": { + "ajv": "6.9.1", + "chokidar": "2.0.4", + "fast-json-stable-stringify": "2.0.0", + "rxjs": "6.3.3", + "source-map": "0.7.3" + } + } + } + }, + "@angular-devkit/build-optimizer": { + "version": "0.13.8", + "resolved": "https://registry.npmjs.org/@angular-devkit/build-optimizer/-/build-optimizer-0.13.8.tgz", + "integrity": "sha512-RvYxtsdYuvpFb1iivVixylSVN/Q8LsQ449uYuqEe3OsDjQBvUVG2fMLPOQjmKWhi0NC9WSsNiUluxLDNdvd0Vw==", + "dev": true, + "requires": { + "loader-utils": "1.2.3", + "source-map": "0.5.6", + "typescript": "3.2.4", + "webpack-sources": "1.3.0" + }, + "dependencies": { + "source-map": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.6.tgz", + "integrity": "sha1-dc449SvwczxafwwRjYEzSiu19BI=", + "dev": true + } + } + }, + "@angular-devkit/build-webpack": { + "version": "0.13.8", + "resolved": "https://registry.npmjs.org/@angular-devkit/build-webpack/-/build-webpack-0.13.8.tgz", + "integrity": "sha512-WMyn1vUHyx+VfJKgYuEHrICwQzPMDTaUNB1zlvzZt9gX/9H+XnetrebeWBZCITPXHBw/377oA6wmiHWJ0yaZRw==", + "dev": true, + "requires": { + "@angular-devkit/architect": "0.13.8", + "@angular-devkit/core": "7.3.8", + "rxjs": "6.3.3" + }, + "dependencies": { + "@angular-devkit/architect": { + "version": "0.13.8", + "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.13.8.tgz", + "integrity": "sha512-gxUs5rhnP576T8ZclKqxlspiChrqRtqaJo54wqNVFvYKEjRZKyMa+1AK6p0oD9zcIToEkcjknj3BbtQa27lLHg==", + "dev": true, + "requires": { + "@angular-devkit/core": "7.3.8", + "rxjs": "6.3.3" + } + }, + "@angular-devkit/core": { + "version": "7.3.8", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-7.3.8.tgz", + "integrity": "sha512-3X9uzaZXFpm5o2TSzhD6wEOtVU32CgeytKjD1Scxj+uMMVo48SWLlKiFh312T+smI9ko7tOT8VqxglwYkWosgg==", + "dev": true, + "requires": { + "ajv": "6.9.1", + "chokidar": "2.0.4", + "fast-json-stable-stringify": "2.0.0", + "rxjs": "6.3.3", + "source-map": "0.7.3" + } + } + } + }, + "@angular-devkit/core": { + "version": "7.3.7", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-7.3.7.tgz", + "integrity": "sha512-RRDCqSX4wF3Nxxls+W0BzCHz2HYWFP6bUpWxmzXF8EETGlZq/b/ZsEnVV41jtsqC4PCTks4k0qUFMaKE7XCupg==", + "dev": true, + "requires": { + "ajv": "6.9.1", + "chokidar": "2.0.4", + "fast-json-stable-stringify": "2.0.0", + "rxjs": "6.3.3", + "source-map": "0.7.3" + } + }, + "@angular-devkit/schematics": { + "version": "7.3.7", + "resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-7.3.7.tgz", + "integrity": "sha512-XHrr63E6p9kLNR83D4AcUkdBFLw4yxeQihual3I88eJUPJdHAvQYkp1sBThlMkNmBWPVagUD4efknkUFfRkFmg==", + "dev": true, + "requires": { + "@angular-devkit/core": "7.3.7", + "rxjs": "6.3.3" + } + }, + "@angular/animations": { + "version": "7.2.11", + "resolved": "https://registry.npmjs.org/@angular/animations/-/animations-7.2.11.tgz", + "integrity": "sha512-YlZATC4Gnd3eOBuSW/QqsMPHeJWVKR8yfen2qKgFO3waK+6qXHXIdRF3j81l07jlUx5kBw+f1OCEeHl2QChBiw==", + "requires": { + "tslib": "^1.9.0" + } + }, + "@angular/cdk": { + "version": "7.3.6", + "resolved": "https://registry.npmjs.org/@angular/cdk/-/cdk-7.3.6.tgz", + "integrity": "sha512-ZuOz8nQk0bdo8YyNFcwnmSl4MPaQDAFTbLK29w4Vd/LfPnhBI3pAr0wVuPFb0fl3eSvvUrfTb/+kPbQcE07A0A==", + "requires": { + "parse5": "^5.0.0", + "tslib": "^1.7.1" + }, + "dependencies": { + "parse5": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-5.1.0.tgz", + "integrity": "sha512-fxNG2sQjHvlVAYmzBZS9YlDp6PTSSDwa98vkD4QgVDDCAo84z5X1t5XyJQ62ImdLXx5NdIIfihey6xpum9/gRQ==", + "optional": true + } + } + }, + "@angular/cli": { + "version": "7.3.7", + "resolved": "https://registry.npmjs.org/@angular/cli/-/cli-7.3.7.tgz", + "integrity": "sha512-b8YT+HXkhpK1A6lo56VvnUb07s2DWJSKdyhlJ3vIKwlYRg5svcmxh+AS6Oz0rzcQc++a0I5IODd9yWrMAkCBog==", + "dev": true, + "requires": { + "@angular-devkit/architect": "0.13.7", + "@angular-devkit/core": "7.3.7", + "@angular-devkit/schematics": "7.3.7", + "@schematics/angular": "7.3.7", + "@schematics/update": "0.13.7", + "@yarnpkg/lockfile": "1.1.0", + "ini": "1.3.5", + "inquirer": "6.2.1", + "npm-package-arg": "6.1.0", + "opn": "5.4.0", + "pacote": "9.4.0", + "semver": "5.6.0", + "symbol-observable": "1.2.0" + } + }, + "@angular/common": { + "version": "7.2.11", + "resolved": "https://registry.npmjs.org/@angular/common/-/common-7.2.11.tgz", + "integrity": "sha512-78T65tt3zD0Oss6pyRvzLhPlTJMhPpXMCsJdr1s821i+/xg8TUzgU3Cqxvz37r3dysnY08r6BmEqJg3+fGxEwA==", + "requires": { + "tslib": "^1.9.0" + } + }, + "@angular/compiler": { + "version": "7.2.11", + "resolved": "https://registry.npmjs.org/@angular/compiler/-/compiler-7.2.11.tgz", + "integrity": "sha512-PptrtUgzuxLtBFxcmkkmhr16Pu9A3xqRQGmgTKL2mzwpt/fZswYEv4QT2E5Br8f94UP5XPv+K30kY+exjPnFjg==", + "requires": { + "tslib": "^1.9.0" + } + }, + "@angular/compiler-cli": { + "version": "7.2.11", + "resolved": "https://registry.npmjs.org/@angular/compiler-cli/-/compiler-cli-7.2.11.tgz", + "integrity": "sha512-JlyJcRdD0oltKbFKGA83g4+vitF4e7ReqAHYTGbNTDIicWhCXYwwGAqj0HYAUHsv9KnP9F5ojZOJ7FRIN7mPgw==", + "dev": true, + "requires": { + "canonical-path": "1.0.0", + "chokidar": "^2.1.1", + "convert-source-map": "^1.5.1", + "dependency-graph": "^0.7.2", + "magic-string": "^0.25.0", + "minimist": "^1.2.0", + "reflect-metadata": "^0.1.2", + "shelljs": "^0.8.1", + "source-map": "^0.6.1", + "tslib": "^1.9.0", + "yargs": "9.0.1" + }, + "dependencies": { + "ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "dev": true + }, + "camelcase": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-4.1.0.tgz", + "integrity": "sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0=", + "dev": true + }, + "chokidar": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.1.5.tgz", + "integrity": "sha512-i0TprVWp+Kj4WRPtInjexJ8Q+BqTE909VpH8xVhXrJkoc5QC8VO9TryGOqTr+2hljzc1sC62t22h5tZePodM/A==", + "dev": true, + "requires": { + "anymatch": "^2.0.0", + "async-each": "^1.0.1", + "braces": "^2.3.2", + "fsevents": "^1.2.7", + "glob-parent": "^3.1.0", + "inherits": "^2.0.3", + "is-binary-path": "^1.0.0", + "is-glob": "^4.0.0", + "normalize-path": "^3.0.0", + "path-is-absolute": "^1.0.0", + "readdirp": "^2.2.1", + "upath": "^1.1.1" + } + }, + "cross-spawn": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz", + "integrity": "sha1-6L0O/uWPz/b4+UUQoKVUu/ojVEk=", + "dev": true, + "requires": { + "lru-cache": "^4.0.1", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + } + }, + "execa": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-0.7.0.tgz", + "integrity": "sha1-lEvs00zEHuMqY6n68nrVpl/Fl3c=", + "dev": true, + "requires": { + "cross-spawn": "^5.0.1", + "get-stream": "^3.0.0", + "is-stream": "^1.1.0", + "npm-run-path": "^2.0.0", + "p-finally": "^1.0.0", + "signal-exit": "^3.0.0", + "strip-eof": "^1.0.0" + } + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "dev": true + }, + "load-json-file": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-2.0.0.tgz", + "integrity": "sha1-eUfkIUmvgNaWy/eXvKq8/h/inKg=", + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "parse-json": "^2.2.0", + "pify": "^2.0.0", + "strip-bom": "^3.0.0" + } + }, + "mem": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/mem/-/mem-1.1.0.tgz", + "integrity": "sha1-Xt1StIXKHZAP5kiVUFOZoN+kX3Y=", + "dev": true, + "requires": { + "mimic-fn": "^1.0.0" + } + }, + "mimic-fn": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.2.0.tgz", + "integrity": "sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ==", + "dev": true + }, + "normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true + }, + "os-locale": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-2.1.0.tgz", + "integrity": "sha512-3sslG3zJbEYcaC4YVAvDorjGxc7tv6KVATnLPZONiljsUncvihe9BQoVCEs0RZ1kmf4Hk9OBqlZfJZWI4GanKA==", + "dev": true, + "requires": { + "execa": "^0.7.0", + "lcid": "^1.0.0", + "mem": "^1.1.0" + } + }, + "path-type": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-2.0.0.tgz", + "integrity": "sha1-8BLMuEFbcJb8LaoQVMPXI4lZTHM=", + "dev": true, + "requires": { + "pify": "^2.0.0" + } + }, + "pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", + "dev": true + }, + "read-pkg": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-2.0.0.tgz", + "integrity": "sha1-jvHAYjxqbbDcZxPEv6xGMysjaPg=", + "dev": true, + "requires": { + "load-json-file": "^2.0.0", + "normalize-package-data": "^2.3.2", + "path-type": "^2.0.0" + } + }, + "read-pkg-up": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-2.0.0.tgz", + "integrity": "sha1-a3KoBImE4MQeeVEP1en6mbO1Sb4=", + "dev": true, + "requires": { + "find-up": "^2.0.0", + "read-pkg": "^2.0.0" + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + }, + "string-width": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", + "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "dev": true, + "requires": { + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^4.0.0" + } + }, + "strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "dev": true, + "requires": { + "ansi-regex": "^3.0.0" + } + }, + "strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", + "dev": true + }, + "which-module": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", + "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=", + "dev": true + }, + "y18n": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-3.2.1.tgz", + "integrity": "sha1-bRX7qITAhnnA136I53WegR4H+kE=", + "dev": true + }, + "yargs": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-9.0.1.tgz", + "integrity": "sha1-UqzCP+7Kw0BCB47njAwAf1CF20w=", + "dev": true, + "requires": { + "camelcase": "^4.1.0", + "cliui": "^3.2.0", + "decamelize": "^1.1.1", + "get-caller-file": "^1.0.1", + "os-locale": "^2.0.0", + "read-pkg-up": "^2.0.0", + "require-directory": "^2.1.1", + "require-main-filename": "^1.0.1", + "set-blocking": "^2.0.0", + "string-width": "^2.0.0", + "which-module": "^2.0.0", + "y18n": "^3.2.1", + "yargs-parser": "^7.0.0" + } + }, + "yargs-parser": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-7.0.0.tgz", + "integrity": "sha1-jQrELxbqVd69MyyvTEA4s+P139k=", + "dev": true, + "requires": { + "camelcase": "^4.1.0" + } + } + } + }, + "@angular/core": { + "version": "7.2.11", + "resolved": "https://registry.npmjs.org/@angular/core/-/core-7.2.11.tgz", + "integrity": "sha512-2FUgXbGn75D6HQESVVmnrjqP1l2YNwwIZISembzpr4WvTm0lxzq/9WsuPFJNWggwCerajyjYz+kDJT3RsonGZg==", + "requires": { + "tslib": "^1.9.0" + } + }, + "@angular/forms": { + "version": "7.2.11", + "resolved": "https://registry.npmjs.org/@angular/forms/-/forms-7.2.11.tgz", + "integrity": "sha512-S7qpaA4lP4yU5HsOvPuf4bpDGiqP6YjwcWHcTkm1kx9oiHGvmUoQAkFw06SMh2YCsGi413Qqys/4L0H8+8Za2A==", + "requires": { + "tslib": "^1.9.0" + } + }, + "@angular/language-service": { + "version": "7.2.11", + "resolved": "https://registry.npmjs.org/@angular/language-service/-/language-service-7.2.11.tgz", + "integrity": "sha512-hq6ROxnqghmKXwJ8eIPb0wlz/PSmVTpFNQGrk3H6BFdRxPIHxvYU94mcLiV0/rMLZsSSSd4oANRUQToJR9upUA==", + "dev": true + }, + "@angular/platform-browser": { + "version": "7.2.11", + "resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-7.2.11.tgz", + "integrity": "sha512-M/XB1TQDHx2Ni+2NR75Zc1N+UuwfAMwNcBaOxtKRLkZhpyOAneA0ZyqT2GQm6XTGxDwQhz6Q3wG6SwGTm9aLYg==", + "requires": { + "tslib": "^1.9.0" + } + }, + "@angular/platform-browser-dynamic": { + "version": "7.2.11", + "resolved": "https://registry.npmjs.org/@angular/platform-browser-dynamic/-/platform-browser-dynamic-7.2.11.tgz", + "integrity": "sha512-MDwNjH0BKfYabTJtfKAy87o+pRVcHsCwlrkp8/LBGNS42vDuzWWNUepMRTf2SloDUthL9Tsibdl1LfbMhUoGpw==", + "requires": { + "tslib": "^1.9.0" + } + }, + "@angular/router": { + "version": "7.2.11", + "resolved": "https://registry.npmjs.org/@angular/router/-/router-7.2.11.tgz", + "integrity": "sha512-6TcXQgTCPrOgkGWkxO879t+kcMovno6VfN4xfqr36IMBRxhmeWFEhLJmsVGPa/TT4XZjYQXyBk57ZXmneUqjQQ==", + "requires": { + "tslib": "^1.9.0" + } + }, + "@babel/code-frame": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.0.0.tgz", + "integrity": "sha512-OfC2uemaknXr87bdLUkWog7nYuliM9Ij5HUcajsVcMCpQrcLmtxRbVFTIqmcSkSeYRBFBRxs2FiUqFJDLdiebA==", + "dev": true, + "requires": { + "@babel/highlight": "^7.0.0" + } + }, + "@babel/generator": { + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.4.0.tgz", + "integrity": "sha512-/v5I+a1jhGSKLgZDcmAUZ4K/VePi43eRkUs3yePW1HB1iANOD5tqJXwGSG4BZhSksP8J9ejSlwGeTiiOFZOrXQ==", + "dev": true, + "requires": { + "@babel/types": "^7.4.0", + "jsesc": "^2.5.1", + "lodash": "^4.17.11", + "source-map": "^0.5.0", + "trim-right": "^1.0.1" + }, + "dependencies": { + "jsesc": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", + "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", + "dev": true + }, + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true + } + } + }, + "@babel/helper-function-name": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.1.0.tgz", + "integrity": "sha512-A95XEoCpb3TO+KZzJ4S/5uW5fNe26DjBGqf1o9ucyLyCmi1dXq/B3c8iaWTfBk3VvetUxl16e8tIrd5teOCfGw==", + "dev": true, + "requires": { + "@babel/helper-get-function-arity": "^7.0.0", + "@babel/template": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "@babel/helper-get-function-arity": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.0.0.tgz", + "integrity": "sha512-r2DbJeg4svYvt3HOS74U4eWKsUAMRH01Z1ds1zx8KNTPtpTL5JAsdFv8BNyOpVqdFhHkkRDIg5B4AsxmkjAlmQ==", + "dev": true, + "requires": { + "@babel/types": "^7.0.0" + } + }, + "@babel/helper-split-export-declaration": { + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.4.0.tgz", + "integrity": "sha512-7Cuc6JZiYShaZnybDmfwhY4UYHzI6rlqhWjaIqbsJGsIqPimEYy5uh3akSRLMg65LSdSEnJ8a8/bWQN6u2oMGw==", + "dev": true, + "requires": { + "@babel/types": "^7.4.0" + } + }, + "@babel/highlight": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.0.0.tgz", + "integrity": "sha512-UFMC4ZeFC48Tpvj7C8UgLvtkaUuovQX+5xNWrsIoMG8o2z+XFKjKaN9iVmS84dPwVN00W4wPmqvYoZF3EGAsfw==", + "dev": true, + "requires": { + "chalk": "^2.0.0", + "esutils": "^2.0.2", + "js-tokens": "^4.0.0" + }, + "dependencies": { + "js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true + } + } + }, + "@babel/parser": { + "version": "7.4.2", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.4.2.tgz", + "integrity": "sha512-9fJTDipQFvlfSVdD/JBtkiY0br9BtfvW2R8wo6CX/Ej2eMuV0gWPk1M67Mt3eggQvBqYW1FCEk8BN7WvGm/g5g==", + "dev": true + }, + "@babel/template": { + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.4.0.tgz", + "integrity": "sha512-SOWwxxClTTh5NdbbYZ0BmaBVzxzTh2tO/TeLTbF6MO6EzVhHTnff8CdBXx3mEtazFBoysmEM6GU/wF+SuSx4Fw==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.0.0", + "@babel/parser": "^7.4.0", + "@babel/types": "^7.4.0" + } + }, + "@babel/traverse": { + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.4.0.tgz", + "integrity": "sha512-/DtIHKfyg2bBKnIN+BItaIlEg5pjAnzHOIQe5w+rHAw/rg9g0V7T4rqPX8BJPfW11kt3koyjAnTNwCzb28Y1PA==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.0.0", + "@babel/generator": "^7.4.0", + "@babel/helper-function-name": "^7.1.0", + "@babel/helper-split-export-declaration": "^7.4.0", + "@babel/parser": "^7.4.0", + "@babel/types": "^7.4.0", + "debug": "^4.1.0", + "globals": "^11.1.0", + "lodash": "^4.17.11" + }, + "dependencies": { + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "globals": { + "version": "11.11.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.11.0.tgz", + "integrity": "sha512-WHq43gS+6ufNOEqlrDBxVEbb8ntfXrfAUU2ZOpCxrBdGKW3gyv8mCxAfIBD0DroPKGrJ2eSsXsLtY9MPntsyTw==", + "dev": true + }, + "ms": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", + "dev": true + } + } + }, + "@babel/types": { + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.4.0.tgz", + "integrity": "sha512-aPvkXyU2SPOnztlgo8n9cEiXW755mgyvueUPcpStqdzoSPm0fjO0vQBjLkt3JKJW7ufikfcnMTTPsN1xaTsBPA==", + "dev": true, + "requires": { + "esutils": "^2.0.2", + "lodash": "^4.17.11", + "to-fast-properties": "^2.0.0" + }, + "dependencies": { + "to-fast-properties": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", + "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=", + "dev": true + } + } + }, + "@ngtools/webpack": { + "version": "7.3.8", + "resolved": "https://registry.npmjs.org/@ngtools/webpack/-/webpack-7.3.8.tgz", + "integrity": "sha512-gfjSKz+F/2T4tZHpnQ1XqelKP/CIfI87XdoHsOI53ceTUrAkVKsOb3ULmEfkcdsdQZ/HhmCiLivcutHcW8xkhQ==", + "dev": true, + "requires": { + "@angular-devkit/core": "7.3.8", + "enhanced-resolve": "4.1.0", + "rxjs": "6.3.3", + "tree-kill": "1.2.1", + "webpack-sources": "1.3.0" + }, + "dependencies": { + "@angular-devkit/core": { + "version": "7.3.8", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-7.3.8.tgz", + "integrity": "sha512-3X9uzaZXFpm5o2TSzhD6wEOtVU32CgeytKjD1Scxj+uMMVo48SWLlKiFh312T+smI9ko7tOT8VqxglwYkWosgg==", + "dev": true, + "requires": { + "ajv": "6.9.1", + "chokidar": "2.0.4", + "fast-json-stable-stringify": "2.0.0", + "rxjs": "6.3.3", + "source-map": "0.7.3" + } + } + } + }, + "@schematics/angular": { + "version": "7.3.7", + "resolved": "https://registry.npmjs.org/@schematics/angular/-/angular-7.3.7.tgz", + "integrity": "sha512-X769unu8NViF4fjQhe3kFSLy2fmigWUJiTdEA76CPHsh5ka7trNhLaKcorXvYQDX+OhT5WHwBx5shg/3aJ3GYw==", + "dev": true, + "requires": { + "@angular-devkit/core": "7.3.7", + "@angular-devkit/schematics": "7.3.7", + "typescript": "3.2.4" + } + }, + "@schematics/update": { + "version": "0.13.7", + "resolved": "https://registry.npmjs.org/@schematics/update/-/update-0.13.7.tgz", + "integrity": "sha512-7GiQiRkZlrPqINCWf/tKSUMfKz5dCwTFijkN/Hr0z2c679Q8kjiBnibNnzHG+zgmE7pH9gPRPvIa4hL+/zdtYw==", + "dev": true, + "requires": { + "@angular-devkit/core": "7.3.7", + "@angular-devkit/schematics": "7.3.7", + "@yarnpkg/lockfile": "1.1.0", + "ini": "1.3.5", + "pacote": "9.4.0", + "rxjs": "6.3.3", + "semver": "5.6.0", + "semver-intersect": "1.4.0" + } + }, + "@types/jasmine": { + "version": "2.8.16", + "resolved": "https://registry.npmjs.org/@types/jasmine/-/jasmine-2.8.16.tgz", + "integrity": "sha512-056oRlBBp7MDzr+HoU5su099s/s7wjZ3KcHxLfv+Byqb9MwdLUvsfLgw1VS97hsh3ddxSPyQu+olHMnoVTUY6g==", + "dev": true + }, + "@types/jasminewd2": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@types/jasminewd2/-/jasminewd2-2.0.6.tgz", + "integrity": "sha512-2ZOKrxb8bKRmP/po5ObYnRDgFE4i+lQiEB27bAMmtMWLgJSqlIDqlLx6S0IRorpOmOPRQ6O80NujTmQAtBkeNw==", + "dev": true, + "requires": { + "@types/jasmine": "*" + } + }, + "@types/node": { + "version": "8.9.5", + "resolved": "https://registry.npmjs.org/@types/node/-/node-8.9.5.tgz", + "integrity": "sha512-jRHfWsvyMtXdbhnz5CVHxaBgnV6duZnPlQuRSo/dm/GnmikNcmZhxIES4E9OZjUmQ8C+HCl4KJux+cXN/ErGDQ==", + "dev": true + }, + "@types/q": { + "version": "0.0.32", + "resolved": "https://registry.npmjs.org/@types/q/-/q-0.0.32.tgz", + "integrity": "sha1-vShOV8hPEyXacCur/IKlMoGQwMU=", + "dev": true + }, + "@types/selenium-webdriver": { + "version": "3.0.15", + "resolved": "https://registry.npmjs.org/@types/selenium-webdriver/-/selenium-webdriver-3.0.15.tgz", + "integrity": "sha512-5nh8/K2u9p4bk95GGCJB7KBvewaB0TUziZ9DTr+mR2I6RoO4OJVqx7rxK83hs2J1tomwtCGkhiW+Dy8EUnfB+Q==", + "dev": true + }, + "@types/source-list-map": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/@types/source-list-map/-/source-list-map-0.1.2.tgz", + "integrity": "sha512-K5K+yml8LTo9bWJI/rECfIPrGgxdpeNbj+d53lwN4QjW1MCwlkhUms+gtdzigTeUyBr09+u8BwOIY3MXvHdcsA==", + "dev": true + }, + "@types/webpack-sources": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/@types/webpack-sources/-/webpack-sources-0.1.5.tgz", + "integrity": "sha512-zfvjpp7jiafSmrzJ2/i3LqOyTYTuJ7u1KOXlKgDlvsj9Rr0x7ZiYu5lZbXwobL7lmsRNtPXlBfmaUD8eU2Hu8w==", + "dev": true, + "requires": { + "@types/node": "*", + "@types/source-list-map": "*", + "source-map": "^0.6.1" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + } + } + }, + "@webassemblyjs/ast": { + "version": "1.7.11", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.7.11.tgz", + "integrity": "sha512-ZEzy4vjvTzScC+SH8RBssQUawpaInUdMTYwYYLh54/s8TuT0gBLuyUnppKsVyZEi876VmmStKsUs28UxPgdvrA==", + "dev": true, + "requires": { + "@webassemblyjs/helper-module-context": "1.7.11", + "@webassemblyjs/helper-wasm-bytecode": "1.7.11", + "@webassemblyjs/wast-parser": "1.7.11" + } + }, + "@webassemblyjs/floating-point-hex-parser": { + "version": "1.7.11", + "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.7.11.tgz", + "integrity": "sha512-zY8dSNyYcgzNRNT666/zOoAyImshm3ycKdoLsyDw/Bwo6+/uktb7p4xyApuef1dwEBo/U/SYQzbGBvV+nru2Xg==", + "dev": true + }, + "@webassemblyjs/helper-api-error": { + "version": "1.7.11", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.7.11.tgz", + "integrity": "sha512-7r1qXLmiglC+wPNkGuXCvkmalyEstKVwcueZRP2GNC2PAvxbLYwLLPr14rcdJaE4UtHxQKfFkuDFuv91ipqvXg==", + "dev": true + }, + "@webassemblyjs/helper-buffer": { + "version": "1.7.11", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.7.11.tgz", + "integrity": "sha512-MynuervdylPPh3ix+mKZloTcL06P8tenNH3sx6s0qE8SLR6DdwnfgA7Hc9NSYeob2jrW5Vql6GVlsQzKQCa13w==", + "dev": true + }, + "@webassemblyjs/helper-code-frame": { + "version": "1.7.11", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-code-frame/-/helper-code-frame-1.7.11.tgz", + "integrity": "sha512-T8ESC9KMXFTXA5urJcyor5cn6qWeZ4/zLPyWeEXZ03hj/x9weSokGNkVCdnhSabKGYWxElSdgJ+sFa9G/RdHNw==", + "dev": true, + "requires": { + "@webassemblyjs/wast-printer": "1.7.11" + } + }, + "@webassemblyjs/helper-fsm": { + "version": "1.7.11", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-fsm/-/helper-fsm-1.7.11.tgz", + "integrity": "sha512-nsAQWNP1+8Z6tkzdYlXT0kxfa2Z1tRTARd8wYnc/e3Zv3VydVVnaeePgqUzFrpkGUyhUUxOl5ML7f1NuT+gC0A==", + "dev": true + }, + "@webassemblyjs/helper-module-context": { + "version": "1.7.11", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-module-context/-/helper-module-context-1.7.11.tgz", + "integrity": "sha512-JxfD5DX8Ygq4PvXDucq0M+sbUFA7BJAv/GGl9ITovqE+idGX+J3QSzJYz+LwQmL7fC3Rs+utvWoJxDb6pmC0qg==", + "dev": true + }, + "@webassemblyjs/helper-wasm-bytecode": { + "version": "1.7.11", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.7.11.tgz", + "integrity": "sha512-cMXeVS9rhoXsI9LLL4tJxBgVD/KMOKXuFqYb5oCJ/opScWpkCMEz9EJtkonaNcnLv2R3K5jIeS4TRj/drde1JQ==", + "dev": true + }, + "@webassemblyjs/helper-wasm-section": { + "version": "1.7.11", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.7.11.tgz", + "integrity": "sha512-8ZRY5iZbZdtNFE5UFunB8mmBEAbSI3guwbrsCl4fWdfRiAcvqQpeqd5KHhSWLL5wuxo53zcaGZDBU64qgn4I4Q==", + "dev": true, + "requires": { + "@webassemblyjs/ast": "1.7.11", + "@webassemblyjs/helper-buffer": "1.7.11", + "@webassemblyjs/helper-wasm-bytecode": "1.7.11", + "@webassemblyjs/wasm-gen": "1.7.11" + } + }, + "@webassemblyjs/ieee754": { + "version": "1.7.11", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.7.11.tgz", + "integrity": "sha512-Mmqx/cS68K1tSrvRLtaV/Lp3NZWzXtOHUW2IvDvl2sihAwJh4ACE0eL6A8FvMyDG9abes3saB6dMimLOs+HMoQ==", + "dev": true, + "requires": { + "@xtuc/ieee754": "^1.2.0" + } + }, + "@webassemblyjs/leb128": { + "version": "1.7.11", + "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.7.11.tgz", + "integrity": "sha512-vuGmgZjjp3zjcerQg+JA+tGOncOnJLWVkt8Aze5eWQLwTQGNgVLcyOTqgSCxWTR4J42ijHbBxnuRaL1Rv7XMdw==", + "dev": true, + "requires": { + "@xtuc/long": "4.2.1" + } + }, + "@webassemblyjs/utf8": { + "version": "1.7.11", + "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.7.11.tgz", + "integrity": "sha512-C6GFkc7aErQIAH+BMrIdVSmW+6HSe20wg57HEC1uqJP8E/xpMjXqQUxkQw07MhNDSDcGpxI9G5JSNOQCqJk4sA==", + "dev": true + }, + "@webassemblyjs/wasm-edit": { + "version": "1.7.11", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.7.11.tgz", + "integrity": "sha512-FUd97guNGsCZQgeTPKdgxJhBXkUbMTY6hFPf2Y4OedXd48H97J+sOY2Ltaq6WGVpIH8o/TGOVNiVz/SbpEMJGg==", + "dev": true, + "requires": { + "@webassemblyjs/ast": "1.7.11", + "@webassemblyjs/helper-buffer": "1.7.11", + "@webassemblyjs/helper-wasm-bytecode": "1.7.11", + "@webassemblyjs/helper-wasm-section": "1.7.11", + "@webassemblyjs/wasm-gen": "1.7.11", + "@webassemblyjs/wasm-opt": "1.7.11", + "@webassemblyjs/wasm-parser": "1.7.11", + "@webassemblyjs/wast-printer": "1.7.11" + } + }, + "@webassemblyjs/wasm-gen": { + "version": "1.7.11", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.7.11.tgz", + "integrity": "sha512-U/KDYp7fgAZX5KPfq4NOupK/BmhDc5Kjy2GIqstMhvvdJRcER/kUsMThpWeRP8BMn4LXaKhSTggIJPOeYHwISA==", + "dev": true, + "requires": { + "@webassemblyjs/ast": "1.7.11", + "@webassemblyjs/helper-wasm-bytecode": "1.7.11", + "@webassemblyjs/ieee754": "1.7.11", + "@webassemblyjs/leb128": "1.7.11", + "@webassemblyjs/utf8": "1.7.11" + } + }, + "@webassemblyjs/wasm-opt": { + "version": "1.7.11", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.7.11.tgz", + "integrity": "sha512-XynkOwQyiRidh0GLua7SkeHvAPXQV/RxsUeERILmAInZegApOUAIJfRuPYe2F7RcjOC9tW3Cb9juPvAC/sCqvg==", + "dev": true, + "requires": { + "@webassemblyjs/ast": "1.7.11", + "@webassemblyjs/helper-buffer": "1.7.11", + "@webassemblyjs/wasm-gen": "1.7.11", + "@webassemblyjs/wasm-parser": "1.7.11" + } + }, + "@webassemblyjs/wasm-parser": { + "version": "1.7.11", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.7.11.tgz", + "integrity": "sha512-6lmXRTrrZjYD8Ng8xRyvyXQJYUQKYSXhJqXOBLw24rdiXsHAOlvw5PhesjdcaMadU/pyPQOJ5dHreMjBxwnQKg==", + "dev": true, + "requires": { + "@webassemblyjs/ast": "1.7.11", + "@webassemblyjs/helper-api-error": "1.7.11", + "@webassemblyjs/helper-wasm-bytecode": "1.7.11", + "@webassemblyjs/ieee754": "1.7.11", + "@webassemblyjs/leb128": "1.7.11", + "@webassemblyjs/utf8": "1.7.11" + } + }, + "@webassemblyjs/wast-parser": { + "version": "1.7.11", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-parser/-/wast-parser-1.7.11.tgz", + "integrity": "sha512-lEyVCg2np15tS+dm7+JJTNhNWq9yTZvi3qEhAIIOaofcYlUp0UR5/tVqOwa/gXYr3gjwSZqw+/lS9dscyLelbQ==", + "dev": true, + "requires": { + "@webassemblyjs/ast": "1.7.11", + "@webassemblyjs/floating-point-hex-parser": "1.7.11", + "@webassemblyjs/helper-api-error": "1.7.11", + "@webassemblyjs/helper-code-frame": "1.7.11", + "@webassemblyjs/helper-fsm": "1.7.11", + "@xtuc/long": "4.2.1" + } + }, + "@webassemblyjs/wast-printer": { + "version": "1.7.11", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.7.11.tgz", + "integrity": "sha512-m5vkAsuJ32QpkdkDOUPGSltrg8Cuk3KBx4YrmAGQwCZPRdUHXxG4phIOuuycLemHFr74sWL9Wthqss4fzdzSwg==", + "dev": true, + "requires": { + "@webassemblyjs/ast": "1.7.11", + "@webassemblyjs/wast-parser": "1.7.11", + "@xtuc/long": "4.2.1" + } + }, + "@xtuc/ieee754": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", + "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==", + "dev": true + }, + "@xtuc/long": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.1.tgz", + "integrity": "sha512-FZdkNBDqBRHKQ2MEbSC17xnPFOhZxeJ2YGSfr2BKf3sujG49Qe3bB+rGCwQfIaA7WHnGeGkSijX4FuBCdrzW/g==", + "dev": true + }, + "@yarnpkg/lockfile": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@yarnpkg/lockfile/-/lockfile-1.1.0.tgz", + "integrity": "sha512-GpSwvyXOcOOlV70vbnzjj4fW5xW/FdUF6nQEt1ENy7m4ZCczi1+/buVUPAqmGfqznsORNFzUMjctTIp8a9tuCQ==", + "dev": true + }, + "JSONStream": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/JSONStream/-/JSONStream-1.3.5.tgz", + "integrity": "sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ==", + "dev": true, + "requires": { + "jsonparse": "^1.2.0", + "through": ">=2.2.7 <3" + } + }, + "abbrev": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", + "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", + "dev": true, + "optional": true + }, + "accepts": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.5.tgz", + "integrity": "sha1-63d99gEXI6OxTopywIBcjoZ0a9I=", + "dev": true, + "requires": { + "mime-types": "~2.1.18", + "negotiator": "0.6.1" + } + }, + "acorn": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.1.1.tgz", + "integrity": "sha512-jPTiwtOxaHNaAPg/dmrJ/beuzLRnXtB0kQPQ8JpotKJgTB6rX6c8mlf315941pyjBSaPg8NHXS9fhP4u17DpGA==", + "dev": true + }, + "acorn-dynamic-import": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/acorn-dynamic-import/-/acorn-dynamic-import-4.0.0.tgz", + "integrity": "sha512-d3OEjQV4ROpoflsnUA8HozoIR504TFxNivYEUi6uwz0IYhBkTDXGuWlNdMtybRt3nqVx/L6XqMt0FxkXuWKZhw==", + "dev": true + }, + "adm-zip": { + "version": "0.4.13", + "resolved": "https://registry.npmjs.org/adm-zip/-/adm-zip-0.4.13.tgz", + "integrity": "sha512-fERNJX8sOXfel6qCBCMPvZLzENBEhZTzKqg6vrOW5pvoEaQuJhRU4ndTAh6lHOxn1I6jnz2NHra56ZODM751uw==", + "dev": true + }, + "after": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/after/-/after-0.8.2.tgz", + "integrity": "sha1-/ts5T58OAqqXaOcCvaI7UF+ufh8=", + "dev": true + }, + "agent-base": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-4.2.1.tgz", + "integrity": "sha512-JVwXMr9nHYTUXsBFKUqhJwvlcYU/blreOEUkhNR2eXZIvwd+c+o5V4MgDPKWnMS/56awN3TRzIP+KoPn+roQtg==", + "dev": true, + "requires": { + "es6-promisify": "^5.0.0" + } + }, + "agentkeepalive": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-3.5.2.tgz", + "integrity": "sha512-e0L/HNe6qkQ7H19kTlRRqUibEAwDK5AFk6y3PtMsuut2VAH6+Q4xZml1tNDJD7kSAyqmbG/K08K5WEJYtUrSlQ==", + "dev": true, + "requires": { + "humanize-ms": "^1.2.1" + } + }, + "ajv": { + "version": "6.9.1", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.9.1.tgz", + "integrity": "sha512-XDN92U311aINL77ieWHmqCcNlwjoP5cHXDxIxbf2MaPYuCXOHS7gHH8jktxeK5omgd52XbSTX6a4Piwd1pQmzA==", + "dev": true, + "requires": { + "fast-deep-equal": "^2.0.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, + "ajv-errors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/ajv-errors/-/ajv-errors-1.0.1.tgz", + "integrity": "sha512-DCRfO/4nQ+89p/RK43i8Ezd41EqdGIU4ld7nGF8OQ14oc/we5rEntLCUa7+jrn3nn83BosfwZA0wb4pon2o8iQ==", + "dev": true + }, + "ajv-keywords": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.4.0.tgz", + "integrity": "sha512-aUjdRFISbuFOl0EIZc+9e4FfZp0bDZgAdOOf30bJmw8VM9v84SHyVyxDfbWxpGYbdZD/9XoKxfHVNmxPkhwyGw==", + "dev": true + }, + "amdefine": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/amdefine/-/amdefine-1.0.1.tgz", + "integrity": "sha1-SlKCrBZHKek2Gbz9OtFR+BfOkfU=", + "dev": true + }, + "ansi-colors": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-3.2.4.tgz", + "integrity": "sha512-hHUXGagefjN2iRrID63xckIvotOXOojhQKWIPUZ4mNUZ9nLZW+7FMNoE1lOkEhNWYsx/7ysGIuJYCiMAA9FnrA==", + "dev": true + }, + "ansi-escapes": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.2.0.tgz", + "integrity": "sha512-cBhpre4ma+U0T1oM5fXg7Dy1Jw7zzwv7lt/GoCpr+hDQJoYnKVPLL4dCvSEFMmQurOQvSrwT7SL/DAlhBI97RQ==", + "dev": true + }, + "ansi-html": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/ansi-html/-/ansi-html-0.0.7.tgz", + "integrity": "sha1-gTWEAhliqenm/QOflA0S9WynhZ4=", + "dev": true + }, + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", + "dev": true + }, + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "anymatch": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-2.0.0.tgz", + "integrity": "sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw==", + "dev": true, + "requires": { + "micromatch": "^3.1.4", + "normalize-path": "^2.1.1" + } + }, + "app-root-path": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/app-root-path/-/app-root-path-2.2.1.tgz", + "integrity": "sha512-91IFKeKk7FjfmezPKkwtaRvSpnUc4gDwPAjA1YZ9Gn0q0PPeW+vbeUsZuyDwjI7+QTHhcLen2v25fi/AmhvbJA==", + "dev": true + }, + "append-transform": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/append-transform/-/append-transform-1.0.0.tgz", + "integrity": "sha512-P009oYkeHyU742iSZJzZZywj4QRJdnTWffaKuJQLablCZ1uz6/cW4yaRgcDaoQ+uwOxxnt0gRUcwfsNP2ri0gw==", + "dev": true, + "requires": { + "default-require-extensions": "^2.0.0" + } + }, + "aproba": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz", + "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==", + "dev": true + }, + "are-we-there-yet": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.5.tgz", + "integrity": "sha512-5hYdAkZlcG8tOLujVDTgCT+uPX0VnpAH28gWsLfzpXYm7wP6mp5Q/gYyR7YQ0cKVJcXJnl3j2kpBan13PtQf6w==", + "dev": true, + "optional": true, + "requires": { + "delegates": "^1.0.0", + "readable-stream": "^2.0.6" + } + }, + "argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "requires": { + "sprintf-js": "~1.0.2" + } + }, + "arr-diff": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", + "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=", + "dev": true + }, + "arr-flatten": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.1.0.tgz", + "integrity": "sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==", + "dev": true + }, + "arr-union": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz", + "integrity": "sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ=", + "dev": true + }, + "array-find-index": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/array-find-index/-/array-find-index-1.0.2.tgz", + "integrity": "sha1-3wEKoSh+Fku9pvlyOwqWoexBh6E=", + "dev": true, + "optional": true + }, + "array-flatten": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-2.1.2.tgz", + "integrity": "sha512-hNfzcOV8W4NdualtqBFPyVO+54DSJuZGY9qT4pRroB6S9e3iiido2ISIC5h9R2sPJ8H3FHCIiEnsv1lPXO3KtQ==", + "dev": true + }, + "array-union": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz", + "integrity": "sha1-mjRBDk9OPaI96jdb5b5w8kd47Dk=", + "dev": true, + "requires": { + "array-uniq": "^1.0.1" + } + }, + "array-uniq": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/array-uniq/-/array-uniq-1.0.3.tgz", + "integrity": "sha1-r2rId6Jcx/dOBYiUdThY39sk/bY=", + "dev": true + }, + "array-unique": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", + "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=", + "dev": true + }, + "arraybuffer.slice": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/arraybuffer.slice/-/arraybuffer.slice-0.0.7.tgz", + "integrity": "sha512-wGUIVQXuehL5TCqQun8OW81jGzAWycqzFF8lFp+GOM5BXLYj3bKNsYC4daB7n6XjCqxQA/qgTJ+8ANR3acjrog==", + "dev": true + }, + "arrify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", + "integrity": "sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0=", + "dev": true + }, + "asap": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", + "integrity": "sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY=", + "dev": true, + "optional": true + }, + "asn1": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz", + "integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==", + "dev": true, + "requires": { + "safer-buffer": "~2.1.0" + } + }, + "asn1.js": { + "version": "4.10.1", + "resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-4.10.1.tgz", + "integrity": "sha512-p32cOF5q0Zqs9uBiONKYLm6BClCoBCM5O9JfeUSlnQLBTxYdTK+pW+nXflm8UkKd2UYlEbYz5qEi0JuZR9ckSw==", + "dev": true, + "requires": { + "bn.js": "^4.0.0", + "inherits": "^2.0.1", + "minimalistic-assert": "^1.0.0" + } + }, + "assert": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/assert/-/assert-1.4.1.tgz", + "integrity": "sha1-mZEtWRg2tab1s0XA8H7vwI/GXZE=", + "dev": true, + "requires": { + "util": "0.10.3" + }, + "dependencies": { + "inherits": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz", + "integrity": "sha1-sX0I0ya0Qj5Wjv9xn5GwscvfafE=", + "dev": true + }, + "util": { + "version": "0.10.3", + "resolved": "https://registry.npmjs.org/util/-/util-0.10.3.tgz", + "integrity": "sha1-evsa/lCAUkZInj23/g7TeTNqwPk=", + "dev": true, + "requires": { + "inherits": "2.0.1" + } + } + } + }, + "assert-plus": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", + "dev": true + }, + "assign-symbols": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz", + "integrity": "sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=", + "dev": true + }, + "async": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/async/-/async-2.6.2.tgz", + "integrity": "sha512-H1qVYh1MYhEEFLsP97cVKqCGo7KfCyTt6uEWqsTBr9SO84oK9Uwbyd/yCW+6rKJLHksBNUVWZDAjfS+Ccx0Bbg==", + "dev": true, + "requires": { + "lodash": "^4.17.11" + } + }, + "async-each": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/async-each/-/async-each-1.0.2.tgz", + "integrity": "sha512-6xrbvN0MOBKSJDdonmSSz2OwFSgxRaVtBDes26mj9KIGtDo+g9xosFRSC+i1gQh2oAN/tQ62AI/pGZGQjVOiRg==", + "dev": true + }, + "async-foreach": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/async-foreach/-/async-foreach-0.1.3.tgz", + "integrity": "sha1-NhIfhFwFeBct5Bmpfb6x0W7DRUI=", + "dev": true, + "optional": true + }, + "async-limiter": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.0.tgz", + "integrity": "sha512-jp/uFnooOiO+L211eZOoSyzpOITMXx1rBITauYykG3BRYPu8h0UcxsPNB04RR5vo4Tyz3+ay17tR6JVf9qzYWg==", + "dev": true + }, + "asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=", + "dev": true + }, + "atob": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz", + "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==", + "dev": true + }, + "autoprefixer": { + "version": "9.4.6", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-9.4.6.tgz", + "integrity": "sha512-Yp51mevbOEdxDUy5WjiKtpQaecqYq9OqZSL04rSoCiry7Tc5I9FEyo3bfxiTJc1DfHeKwSFCUYbBAiOQ2VGfiw==", + "dev": true, + "requires": { + "browserslist": "^4.4.1", + "caniuse-lite": "^1.0.30000929", + "normalize-range": "^0.1.2", + "num2fraction": "^1.2.2", + "postcss": "^7.0.13", + "postcss-value-parser": "^3.3.1" + } + }, + "aws-sign2": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", + "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=", + "dev": true + }, + "aws4": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.8.0.tgz", + "integrity": "sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ==", + "dev": true + }, + "babel-code-frame": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.26.0.tgz", + "integrity": "sha1-Y/1D99weO7fONZR9uP42mj9Yx0s=", + "dev": true, + "requires": { + "chalk": "^1.1.3", + "esutils": "^2.0.2", + "js-tokens": "^3.0.2" + }, + "dependencies": { + "ansi-styles": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", + "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", + "dev": true + }, + "chalk": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "dev": true, + "requires": { + "ansi-styles": "^2.2.1", + "escape-string-regexp": "^1.0.2", + "has-ansi": "^2.0.0", + "strip-ansi": "^3.0.0", + "supports-color": "^2.0.0" + } + }, + "supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", + "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", + "dev": true + } + } + }, + "babel-generator": { + "version": "6.26.1", + "resolved": "https://registry.npmjs.org/babel-generator/-/babel-generator-6.26.1.tgz", + "integrity": "sha512-HyfwY6ApZj7BYTcJURpM5tznulaBvyio7/0d4zFOeMPUmfxkCjHocCuoLa2SAGzBI8AREcH3eP3758F672DppA==", + "dev": true, + "requires": { + "babel-messages": "^6.23.0", + "babel-runtime": "^6.26.0", + "babel-types": "^6.26.0", + "detect-indent": "^4.0.0", + "jsesc": "^1.3.0", + "lodash": "^4.17.4", + "source-map": "^0.5.7", + "trim-right": "^1.0.1" + }, + "dependencies": { + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true + } + } + }, + "babel-messages": { + "version": "6.23.0", + "resolved": "https://registry.npmjs.org/babel-messages/-/babel-messages-6.23.0.tgz", + "integrity": "sha1-8830cDhYA1sqKVHG7F7fbGLyYw4=", + "dev": true, + "requires": { + "babel-runtime": "^6.22.0" + } + }, + "babel-runtime": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", + "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", + "dev": true, + "requires": { + "core-js": "^2.4.0", + "regenerator-runtime": "^0.11.0" + } + }, + "babel-template": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-template/-/babel-template-6.26.0.tgz", + "integrity": "sha1-3gPi0WOWsGn0bdn/+FIfsaDjXgI=", + "dev": true, + "requires": { + "babel-runtime": "^6.26.0", + "babel-traverse": "^6.26.0", + "babel-types": "^6.26.0", + "babylon": "^6.18.0", + "lodash": "^4.17.4" + } + }, + "babel-traverse": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-traverse/-/babel-traverse-6.26.0.tgz", + "integrity": "sha1-RqnL1+3MYsjlwGTi0tjQ9ANXZu4=", + "dev": true, + "requires": { + "babel-code-frame": "^6.26.0", + "babel-messages": "^6.23.0", + "babel-runtime": "^6.26.0", + "babel-types": "^6.26.0", + "babylon": "^6.18.0", + "debug": "^2.6.8", + "globals": "^9.18.0", + "invariant": "^2.2.2", + "lodash": "^4.17.4" + } + }, + "babel-types": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-types/-/babel-types-6.26.0.tgz", + "integrity": "sha1-o7Bz+Uq0nrb6Vc1lInozQ4BjJJc=", + "dev": true, + "requires": { + "babel-runtime": "^6.26.0", + "esutils": "^2.0.2", + "lodash": "^4.17.4", + "to-fast-properties": "^1.0.3" + } + }, + "babylon": { + "version": "6.18.0", + "resolved": "https://registry.npmjs.org/babylon/-/babylon-6.18.0.tgz", + "integrity": "sha512-q/UEjfGJ2Cm3oKV71DJz9d25TPnq5rhBVL2Q4fA5wcC3jcrdn7+SssEybFIxwAvvP+YCsCYNKughoF33GxgycQ==", + "dev": true + }, + "backo2": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/backo2/-/backo2-1.0.2.tgz", + "integrity": "sha1-MasayLEpNjRj41s+u2n038+6eUc=", + "dev": true + }, + "balanced-match": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", + "dev": true + }, + "base": { + "version": "0.11.2", + "resolved": "https://registry.npmjs.org/base/-/base-0.11.2.tgz", + "integrity": "sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg==", + "dev": true, + "requires": { + "cache-base": "^1.0.1", + "class-utils": "^0.3.5", + "component-emitter": "^1.2.1", + "define-property": "^1.0.0", + "isobject": "^3.0.1", + "mixin-deep": "^1.2.0", + "pascalcase": "^0.1.1" + }, + "dependencies": { + "define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "dev": true, + "requires": { + "is-descriptor": "^1.0.0" + } + }, + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + } + } + } + }, + "base64-arraybuffer": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-0.1.5.tgz", + "integrity": "sha1-c5JncZI7Whl0etZmqlzUv5xunOg=", + "dev": true + }, + "base64-js": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.3.0.tgz", + "integrity": "sha512-ccav/yGvoa80BQDljCxsmmQ3Xvx60/UpBIij5QN21W3wBi/hhIC9OoO+KLpu9IJTS9j4DRVJ3aDDF9cMSoa2lw==", + "dev": true + }, + "base64id": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/base64id/-/base64id-1.0.0.tgz", + "integrity": "sha1-R2iMuZu2gE8OBtPnY7HDLlfY5rY=", + "dev": true + }, + "batch": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/batch/-/batch-0.6.1.tgz", + "integrity": "sha1-3DQxT05nkxgJP8dgJyUl+UvyXBY=", + "dev": true + }, + "bcrypt-pbkdf": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", + "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=", + "dev": true, + "requires": { + "tweetnacl": "^0.14.3" + } + }, + "better-assert": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/better-assert/-/better-assert-1.0.2.tgz", + "integrity": "sha1-QIZrnhueC1W0gYlDEeaPr/rrxSI=", + "dev": true, + "requires": { + "callsite": "1.0.0" + } + }, + "big.js": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz", + "integrity": "sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==", + "dev": true + }, + "binary-extensions": { + "version": "1.13.1", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-1.13.1.tgz", + "integrity": "sha512-Un7MIEDdUC5gNpcGDV97op1Ywk748MpHcFTHoYs6qnj1Z3j7I53VG3nwZhKzoBZmbdRNnb6WRdFlwl7tSDuZGw==", + "dev": true + }, + "blob": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/blob/-/blob-0.0.5.tgz", + "integrity": "sha512-gaqbzQPqOoamawKg0LGVd7SzLgXS+JH61oWprSLH+P+abTczqJbhTR8CmJ2u9/bUYNmHTGJx/UEmn6doAvvuig==", + "dev": true + }, + "block-stream": { + "version": "0.0.9", + "resolved": "https://registry.npmjs.org/block-stream/-/block-stream-0.0.9.tgz", + "integrity": "sha1-E+v+d4oDIFz+A3UUgeu0szAMEmo=", + "dev": true, + "optional": true, + "requires": { + "inherits": "~2.0.0" + } + }, + "blocking-proxy": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/blocking-proxy/-/blocking-proxy-1.0.1.tgz", + "integrity": "sha512-KE8NFMZr3mN2E0HcvCgRtX7DjhiIQrwle+nSVJVC/yqFb9+xznHl2ZcoBp2L9qzkI4t4cBFJ1efXF8Dwi132RA==", + "dev": true, + "requires": { + "minimist": "^1.2.0" + } + }, + "bluebird": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.3.tgz", + "integrity": "sha512-/qKPUQlaW1OyR51WeCPBvRnAlnZFUJkCSG5HzGnuIqhgyJtF+T94lFnn33eiazjRm2LAHVy2guNnaq48X9SJuw==", + "dev": true + }, + "bn.js": { + "version": "4.11.8", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.8.tgz", + "integrity": "sha512-ItfYfPLkWHUjckQCk8xC+LwxgK8NYcXywGigJgSwOP8Y2iyWT4f2vsZnoOXTTbo+o5yXmIUJ4gn5538SO5S3gA==", + "dev": true + }, + "body-parser": { + "version": "1.18.3", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.18.3.tgz", + "integrity": "sha1-WykhmP/dVTs6DyDe0FkrlWlVyLQ=", + "dev": true, + "requires": { + "bytes": "3.0.0", + "content-type": "~1.0.4", + "debug": "2.6.9", + "depd": "~1.1.2", + "http-errors": "~1.6.3", + "iconv-lite": "0.4.23", + "on-finished": "~2.3.0", + "qs": "6.5.2", + "raw-body": "2.3.3", + "type-is": "~1.6.16" + } + }, + "bonjour": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/bonjour/-/bonjour-3.5.0.tgz", + "integrity": "sha1-jokKGD2O6aI5OzhExpGkK897yfU=", + "dev": true, + "requires": { + "array-flatten": "^2.1.0", + "deep-equal": "^1.0.1", + "dns-equal": "^1.0.0", + "dns-txt": "^2.0.2", + "multicast-dns": "^6.0.1", + "multicast-dns-service-types": "^1.1.0" + } + }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "braces": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", + "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", + "dev": true, + "requires": { + "arr-flatten": "^1.1.0", + "array-unique": "^0.3.2", + "extend-shallow": "^2.0.1", + "fill-range": "^4.0.0", + "isobject": "^3.0.1", + "repeat-element": "^1.1.2", + "snapdragon": "^0.8.1", + "snapdragon-node": "^2.0.1", + "split-string": "^3.0.2", + "to-regex": "^3.0.1" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "brorand": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz", + "integrity": "sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8=", + "dev": true + }, + "browserify-aes": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/browserify-aes/-/browserify-aes-1.2.0.tgz", + "integrity": "sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA==", + "dev": true, + "requires": { + "buffer-xor": "^1.0.3", + "cipher-base": "^1.0.0", + "create-hash": "^1.1.0", + "evp_bytestokey": "^1.0.3", + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, + "browserify-cipher": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/browserify-cipher/-/browserify-cipher-1.0.1.tgz", + "integrity": "sha512-sPhkz0ARKbf4rRQt2hTpAHqn47X3llLkUGn+xEJzLjwY8LRs2p0v7ljvI5EyoRO/mexrNunNECisZs+gw2zz1w==", + "dev": true, + "requires": { + "browserify-aes": "^1.0.4", + "browserify-des": "^1.0.0", + "evp_bytestokey": "^1.0.0" + } + }, + "browserify-des": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/browserify-des/-/browserify-des-1.0.2.tgz", + "integrity": "sha512-BioO1xf3hFwz4kc6iBhI3ieDFompMhrMlnDFC4/0/vd5MokpuAc3R+LYbwTA9A5Yc9pq9UYPqffKpW2ObuwX5A==", + "dev": true, + "requires": { + "cipher-base": "^1.0.1", + "des.js": "^1.0.0", + "inherits": "^2.0.1", + "safe-buffer": "^5.1.2" + } + }, + "browserify-rsa": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/browserify-rsa/-/browserify-rsa-4.0.1.tgz", + "integrity": "sha1-IeCr+vbyApzy+vsTNWenAdQTVSQ=", + "dev": true, + "requires": { + "bn.js": "^4.1.0", + "randombytes": "^2.0.1" + } + }, + "browserify-sign": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/browserify-sign/-/browserify-sign-4.0.4.tgz", + "integrity": "sha1-qk62jl17ZYuqa/alfmMMvXqT0pg=", + "dev": true, + "requires": { + "bn.js": "^4.1.1", + "browserify-rsa": "^4.0.0", + "create-hash": "^1.1.0", + "create-hmac": "^1.1.2", + "elliptic": "^6.0.0", + "inherits": "^2.0.1", + "parse-asn1": "^5.0.0" + } + }, + "browserify-zlib": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/browserify-zlib/-/browserify-zlib-0.2.0.tgz", + "integrity": "sha512-Z942RysHXmJrhqk88FmKBVq/v5tqmSkDz7p54G/MGyjMnCFFnC79XWNbg+Vta8W6Wb2qtSZTSxIGkJrRpCFEiA==", + "dev": true, + "requires": { + "pako": "~1.0.5" + } + }, + "browserslist": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.5.4.tgz", + "integrity": "sha512-rAjx494LMjqKnMPhFkuLmLp8JWEX0o8ADTGeAbOqaF+XCvYLreZrG5uVjnPBlAQ8REZK4pzXGvp0bWgrFtKaag==", + "dev": true, + "requires": { + "caniuse-lite": "^1.0.30000955", + "electron-to-chromium": "^1.3.122", + "node-releases": "^1.1.13" + } + }, + "browserstack": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/browserstack/-/browserstack-1.5.2.tgz", + "integrity": "sha512-+6AFt9HzhKykcPF79W6yjEUJcdvZOV0lIXdkORXMJftGrDl0OKWqRF4GHqpDNkxiceDT/uB7Fb/aDwktvXX7dg==", + "dev": true, + "requires": { + "https-proxy-agent": "^2.2.1" + } + }, + "buffer": { + "version": "4.9.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-4.9.1.tgz", + "integrity": "sha1-bRu2AbB6TvztlwlBMgkwJ8lbwpg=", + "dev": true, + "requires": { + "base64-js": "^1.0.2", + "ieee754": "^1.1.4", + "isarray": "^1.0.0" + } + }, + "buffer-alloc": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/buffer-alloc/-/buffer-alloc-1.2.0.tgz", + "integrity": "sha512-CFsHQgjtW1UChdXgbyJGtnm+O/uLQeZdtbDo8mfUgYXCHSM1wgrVxXm6bSyrUuErEb+4sYVGCzASBRot7zyrow==", + "dev": true, + "requires": { + "buffer-alloc-unsafe": "^1.1.0", + "buffer-fill": "^1.0.0" + } + }, + "buffer-alloc-unsafe": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/buffer-alloc-unsafe/-/buffer-alloc-unsafe-1.1.0.tgz", + "integrity": "sha512-TEM2iMIEQdJ2yjPJoSIsldnleVaAk1oW3DBVUykyOLsEsFmEc9kn+SFFPz+gl54KQNxlDnAwCXosOS9Okx2xAg==", + "dev": true + }, + "buffer-fill": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/buffer-fill/-/buffer-fill-1.0.0.tgz", + "integrity": "sha1-+PeLdniYiO858gXNY39o5wISKyw=", + "dev": true + }, + "buffer-from": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", + "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==", + "dev": true + }, + "buffer-indexof": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/buffer-indexof/-/buffer-indexof-1.1.1.tgz", + "integrity": "sha512-4/rOEg86jivtPTeOUUT61jJO1Ya1TrR/OkqCSZDyq84WJh3LuuiphBYJN+fm5xufIk4XAFcEwte/8WzC8If/1g==", + "dev": true + }, + "buffer-xor": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/buffer-xor/-/buffer-xor-1.0.3.tgz", + "integrity": "sha1-JuYe0UIvtw3ULm42cp7VHYVf6Nk=", + "dev": true + }, + "builtin-modules": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-1.1.1.tgz", + "integrity": "sha1-Jw8HbFpywC9bZaR9+Uxf46J4iS8=", + "dev": true + }, + "builtin-status-codes": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz", + "integrity": "sha1-hZgoeOIbmOHGZCXgPQF0eI9Wnug=", + "dev": true + }, + "builtins": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/builtins/-/builtins-1.0.3.tgz", + "integrity": "sha1-y5T662HIaWRR2zZTThQi+U8K7og=", + "dev": true + }, + "bytes": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", + "integrity": "sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg=", + "dev": true + }, + "cacache": { + "version": "10.0.4", + "resolved": "https://registry.npmjs.org/cacache/-/cacache-10.0.4.tgz", + "integrity": "sha512-Dph0MzuH+rTQzGPNT9fAnrPmMmjKfST6trxJeK7NQuHRaVw24VzPRWTmg9MpcwOVQZO0E1FBICUlFeNaKPIfHA==", + "dev": true, + "requires": { + "bluebird": "^3.5.1", + "chownr": "^1.0.1", + "glob": "^7.1.2", + "graceful-fs": "^4.1.11", + "lru-cache": "^4.1.1", + "mississippi": "^2.0.0", + "mkdirp": "^0.5.1", + "move-concurrently": "^1.0.1", + "promise-inflight": "^1.0.1", + "rimraf": "^2.6.2", + "ssri": "^5.2.4", + "unique-filename": "^1.1.0", + "y18n": "^4.0.0" + } + }, + "cache-base": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/cache-base/-/cache-base-1.0.1.tgz", + "integrity": "sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ==", + "dev": true, + "requires": { + "collection-visit": "^1.0.0", + "component-emitter": "^1.2.1", + "get-value": "^2.0.6", + "has-value": "^1.0.0", + "isobject": "^3.0.1", + "set-value": "^2.0.0", + "to-object-path": "^0.3.0", + "union-value": "^1.0.0", + "unset-value": "^1.0.0" + } + }, + "callsite": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/callsite/-/callsite-1.0.0.tgz", + "integrity": "sha1-KAOY5dZkvXQDi28JBRU+borxvCA=", + "dev": true + }, + "camelcase": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-2.1.1.tgz", + "integrity": "sha1-fB0W1nmhu+WcoCys7PsBHiAfWh8=", + "dev": true, + "optional": true + }, + "camelcase-keys": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-2.1.0.tgz", + "integrity": "sha1-MIvur/3ygRkFHvodkyITyRuPkuc=", + "dev": true, + "optional": true, + "requires": { + "camelcase": "^2.0.0", + "map-obj": "^1.0.0" + } + }, + "caniuse-lite": { + "version": "1.0.30000957", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30000957.tgz", + "integrity": "sha512-8wxNrjAzyiHcLXN/iunskqQnJquQQ6VX8JHfW5kLgAPRSiSuKZiNfmIkP5j7jgyXqAQBSoXyJxfnbCFS0ThSiQ==", + "dev": true + }, + "canonical-path": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/canonical-path/-/canonical-path-1.0.0.tgz", + "integrity": "sha512-feylzsbDxi1gPZ1IjystzIQZagYYLvfKrSuygUCgf7z6x790VEzze5QEkdSV1U58RA7Hi0+v6fv4K54atOzATg==", + "dev": true + }, + "caseless": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", + "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=", + "dev": true + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "dependencies": { + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "chardet": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz", + "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==", + "dev": true + }, + "chokidar": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.0.4.tgz", + "integrity": "sha512-z9n7yt9rOvIJrMhvDtDictKrkFHeihkNl6uWMmZlmL6tJtX9Cs+87oK+teBx+JIgzvbX3yZHT3eF8vpbDxHJXQ==", + "dev": true, + "requires": { + "anymatch": "^2.0.0", + "async-each": "^1.0.0", + "braces": "^2.3.0", + "fsevents": "^1.2.2", + "glob-parent": "^3.1.0", + "inherits": "^2.0.1", + "is-binary-path": "^1.0.0", + "is-glob": "^4.0.0", + "lodash.debounce": "^4.0.8", + "normalize-path": "^2.1.1", + "path-is-absolute": "^1.0.0", + "readdirp": "^2.0.0", + "upath": "^1.0.5" + } + }, + "chownr": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.1.tgz", + "integrity": "sha512-j38EvO5+LHX84jlo6h4UzmOwi0UgW61WRyPtJz4qaadK5eY3BTS5TY/S1Stc3Uk2lIM6TPevAlULiEJwie860g==", + "dev": true + }, + "chrome-trace-event": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.0.tgz", + "integrity": "sha512-xDbVgyfDTT2piup/h8dK/y4QZfJRSa73bw1WZ8b4XM1o7fsFubUVGYcE+1ANtOzJJELGpYoG2961z0Z6OAld9A==", + "dev": true, + "requires": { + "tslib": "^1.9.0" + } + }, + "cipher-base": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.4.tgz", + "integrity": "sha512-Kkht5ye6ZGmwv40uUDZztayT2ThLQGfnj/T71N/XzeZeo3nf8foyW7zGTsPYkEya3m5f3cAypH+qe7YOrM1U2Q==", + "dev": true, + "requires": { + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, + "circular-dependency-plugin": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/circular-dependency-plugin/-/circular-dependency-plugin-5.0.2.tgz", + "integrity": "sha512-oC7/DVAyfcY3UWKm0sN/oVoDedQDQiw/vIiAnuTWTpE5s0zWf7l3WY417Xw/Fbi/QbAjctAkxgMiS9P0s3zkmA==", + "dev": true + }, + "class-utils": { + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/class-utils/-/class-utils-0.3.6.tgz", + "integrity": "sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg==", + "dev": true, + "requires": { + "arr-union": "^3.1.0", + "define-property": "^0.2.5", + "isobject": "^3.0.0", + "static-extend": "^0.1.1" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "requires": { + "is-descriptor": "^0.1.0" + } + } + } + }, + "clean-css": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-4.2.1.tgz", + "integrity": "sha512-4ZxI6dy4lrY6FHzfiy1aEOXgu4LIsW2MhwG0VBKdcoGoH/XLFgaHSdLTGr4O8Be6A8r3MOphEiI8Gc1n0ecf3g==", + "dev": true, + "requires": { + "source-map": "~0.6.0" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + } + } + }, + "cli-cursor": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-2.1.0.tgz", + "integrity": "sha1-s12sN2R5+sw+lHR9QdDQ9SOP/LU=", + "dev": true, + "requires": { + "restore-cursor": "^2.0.0" + } + }, + "cli-width": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-2.2.0.tgz", + "integrity": "sha1-/xnt6Kml5XkyQUewwR8PvLq+1jk=", + "dev": true + }, + "cliui": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-3.2.0.tgz", + "integrity": "sha1-EgYBU3qRbSmUD5NNo7SNWFo5IT0=", + "dev": true, + "requires": { + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1", + "wrap-ansi": "^2.0.0" + } + }, + "clone": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz", + "integrity": "sha1-G39Ln1kfHo+DZwQBYANFoCiHQ18=", + "dev": true + }, + "clone-deep": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-2.0.2.tgz", + "integrity": "sha512-SZegPTKjCgpQH63E+eN6mVEEPdQBOUzjyJm5Pora4lrwWRFS8I0QAxV/KD6vV/i0WuijHZWQC1fMsPEdxfdVCQ==", + "dev": true, + "requires": { + "for-own": "^1.0.0", + "is-plain-object": "^2.0.4", + "kind-of": "^6.0.0", + "shallow-clone": "^1.0.0" + } + }, + "co": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", + "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=", + "dev": true + }, + "code-point-at": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", + "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=", + "dev": true + }, + "codelyzer": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/codelyzer/-/codelyzer-4.5.0.tgz", + "integrity": "sha512-oO6vCkjqsVrEsmh58oNlnJkRXuA30hF8cdNAQV9DytEalDwyOFRvHMnlKFzmOStNerOmPGZU9GAHnBo4tGvtiQ==", + "dev": true, + "requires": { + "app-root-path": "^2.1.0", + "css-selector-tokenizer": "^0.7.0", + "cssauron": "^1.4.0", + "semver-dsl": "^1.0.1", + "source-map": "^0.5.7", + "sprintf-js": "^1.1.1" + }, + "dependencies": { + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true + }, + "sprintf-js": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.2.tgz", + "integrity": "sha512-VE0SOVEHCk7Qc8ulkWw3ntAzXuqf7S2lvwQaDLRnUeIEaKNQJzV6BwmLKhOqT61aGhfUMrXeaBk+oDGCzvhcug==", + "dev": true + } + } + }, + "collection-visit": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/collection-visit/-/collection-visit-1.0.0.tgz", + "integrity": "sha1-S8A3PBZLwykbTTaMgpzxqApZ3KA=", + "dev": true, + "requires": { + "map-visit": "^1.0.0", + "object-visit": "^1.0.0" + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", + "dev": true + }, + "colors": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/colors/-/colors-1.1.2.tgz", + "integrity": "sha1-FopHAXVran9RoSzgyXv6KMCE7WM=", + "dev": true + }, + "combined-stream": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.7.tgz", + "integrity": "sha512-brWl9y6vOB1xYPZcpZde3N9zDByXTosAeMDo4p1wzo6UMOX4vumB+TP1RZ76sfE6Md68Q0NJSrE/gbezd4Ul+w==", + "dev": true, + "requires": { + "delayed-stream": "~1.0.0" + } + }, + "commander": { + "version": "2.19.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.19.0.tgz", + "integrity": "sha512-6tvAOO+D6OENvRAh524Dh9jcfKTYDQAqvqezbCW82xj5X0pSrcpxtvRKHLG0yBY6SD7PSDrJaj+0AiOcKVd1Xg==", + "dev": true + }, + "commondir": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", + "integrity": "sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=", + "dev": true + }, + "compare-versions": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/compare-versions/-/compare-versions-3.4.0.tgz", + "integrity": "sha512-tK69D7oNXXqUW3ZNo/z7NXTEz22TCF0pTE+YF9cxvaAM9XnkLo1fV621xCLrRR6aevJlKxExkss0vWqUCUpqdg==", + "dev": true + }, + "component-bind": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/component-bind/-/component-bind-1.0.0.tgz", + "integrity": "sha1-AMYIq33Nk4l8AAllGx06jh5zu9E=", + "dev": true + }, + "component-emitter": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.2.1.tgz", + "integrity": "sha1-E3kY1teCg/ffemt8WmPhQOaUJeY=", + "dev": true + }, + "component-inherit": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/component-inherit/-/component-inherit-0.0.3.tgz", + "integrity": "sha1-ZF/ErfWLcrZJ1crmUTVhnbJv8UM=", + "dev": true + }, + "compressible": { + "version": "2.0.16", + "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.16.tgz", + "integrity": "sha512-JQfEOdnI7dASwCuSPWIeVYwc/zMsu/+tRhoUvEfXz2gxOA2DNjmG5vhtFdBlhWPPGo+RdT9S3tgc/uH5qgDiiA==", + "dev": true, + "requires": { + "mime-db": ">= 1.38.0 < 2" + } + }, + "compression": { + "version": "1.7.4", + "resolved": "https://registry.npmjs.org/compression/-/compression-1.7.4.tgz", + "integrity": "sha512-jaSIDzP9pZVS4ZfQ+TzvtiWhdpFhE2RDHz8QJkpX9SIpLq88VueF5jJw6t+6CUQcAoA6t+x89MLrWAqpfDE8iQ==", + "dev": true, + "requires": { + "accepts": "~1.3.5", + "bytes": "3.0.0", + "compressible": "~2.0.16", + "debug": "2.6.9", + "on-headers": "~1.0.2", + "safe-buffer": "5.1.2", + "vary": "~1.1.2" + } + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "dev": true + }, + "concat-stream": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", + "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", + "dev": true, + "requires": { + "buffer-from": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^2.2.2", + "typedarray": "^0.0.6" + } + }, + "connect": { + "version": "3.6.6", + "resolved": "https://registry.npmjs.org/connect/-/connect-3.6.6.tgz", + "integrity": "sha1-Ce/2xVr3I24TcTWnJXSFi2eG9SQ=", + "dev": true, + "requires": { + "debug": "2.6.9", + "finalhandler": "1.1.0", + "parseurl": "~1.3.2", + "utils-merge": "1.0.1" + }, + "dependencies": { + "finalhandler": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.0.tgz", + "integrity": "sha1-zgtoVbRYU+eRsvzGgARtiCU91/U=", + "dev": true, + "requires": { + "debug": "2.6.9", + "encodeurl": "~1.0.1", + "escape-html": "~1.0.3", + "on-finished": "~2.3.0", + "parseurl": "~1.3.2", + "statuses": "~1.3.1", + "unpipe": "~1.0.0" + } + }, + "statuses": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.3.1.tgz", + "integrity": "sha1-+vUbnrdKrvOzrPStX2Gr8ky3uT4=", + "dev": true + } + } + }, + "connect-history-api-fallback": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/connect-history-api-fallback/-/connect-history-api-fallback-1.6.0.tgz", + "integrity": "sha512-e54B99q/OUoH64zYYRf3HBP5z24G38h5D3qXu23JGRoigpX5Ss4r9ZnDk3g0Z8uQC2x2lPaJ+UlWBc1ZWBWdLg==", + "dev": true + }, + "console-browserify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/console-browserify/-/console-browserify-1.1.0.tgz", + "integrity": "sha1-8CQcRXMKn8YyOyBtvzjtx0HQuxA=", + "dev": true, + "requires": { + "date-now": "^0.1.4" + } + }, + "console-control-strings": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", + "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=", + "dev": true, + "optional": true + }, + "constants-browserify": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/constants-browserify/-/constants-browserify-1.0.0.tgz", + "integrity": "sha1-wguW2MYXdIqvHBYCF2DNJ/y4y3U=", + "dev": true + }, + "content-disposition": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.2.tgz", + "integrity": "sha1-DPaLud318r55YcOoUXjLhdunjLQ=", + "dev": true + }, + "content-type": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", + "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==", + "dev": true + }, + "convert-source-map": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.6.0.tgz", + "integrity": "sha512-eFu7XigvxdZ1ETfbgPBohgyQ/Z++C0eEhTor0qRwBw9unw+L0/6V8wkSuGgzdThkiS5lSpdptOQPD8Ak40a+7A==", + "dev": true, + "requires": { + "safe-buffer": "~5.1.1" + } + }, + "cookie": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.3.1.tgz", + "integrity": "sha1-5+Ch+e9DtMi6klxcWpboBtFoc7s=", + "dev": true + }, + "cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=", + "dev": true + }, + "copy-concurrently": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/copy-concurrently/-/copy-concurrently-1.0.5.tgz", + "integrity": "sha512-f2domd9fsVDFtaFcbaRZuYXwtdmnzqbADSwhSWYxYB/Q8zsdUUFMXVRwXGDMWmbEzAn1kdRrtI1T/KTFOL4X2A==", + "dev": true, + "requires": { + "aproba": "^1.1.1", + "fs-write-stream-atomic": "^1.0.8", + "iferr": "^0.1.5", + "mkdirp": "^0.5.1", + "rimraf": "^2.5.4", + "run-queue": "^1.0.0" + } + }, + "copy-descriptor": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/copy-descriptor/-/copy-descriptor-0.1.1.tgz", + "integrity": "sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=", + "dev": true + }, + "copy-webpack-plugin": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/copy-webpack-plugin/-/copy-webpack-plugin-4.6.0.tgz", + "integrity": "sha512-Y+SQCF+0NoWQryez2zXn5J5knmr9z/9qSQt7fbL78u83rxmigOy8X5+BFn8CFSuX+nKT8gpYwJX68ekqtQt6ZA==", + "dev": true, + "requires": { + "cacache": "^10.0.4", + "find-cache-dir": "^1.0.0", + "globby": "^7.1.1", + "is-glob": "^4.0.0", + "loader-utils": "^1.1.0", + "minimatch": "^3.0.4", + "p-limit": "^1.0.0", + "serialize-javascript": "^1.4.0" + } + }, + "core-js": { + "version": "2.6.5", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.5.tgz", + "integrity": "sha512-klh/kDpwX8hryYL14M9w/xei6vrv6sE8gTHDG7/T/+SEovB/G4ejwcfE/CBzO6Edsu+OETZMZ3wcX/EjUkrl5A==" + }, + "core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", + "dev": true + }, + "cosmiconfig": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-4.0.0.tgz", + "integrity": "sha512-6e5vDdrXZD+t5v0L8CrurPeybg4Fmf+FCSYxXKYVAqLUtyCSbuyqE059d0kDthTNRzKVjL7QMgNpEUlsoYH3iQ==", + "dev": true, + "requires": { + "is-directory": "^0.3.1", + "js-yaml": "^3.9.0", + "parse-json": "^4.0.0", + "require-from-string": "^2.0.1" + }, + "dependencies": { + "parse-json": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", + "integrity": "sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=", + "dev": true, + "requires": { + "error-ex": "^1.3.1", + "json-parse-better-errors": "^1.0.1" + } + } + } + }, + "create-ecdh": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/create-ecdh/-/create-ecdh-4.0.3.tgz", + "integrity": "sha512-GbEHQPMOswGpKXM9kCWVrremUcBmjteUaQ01T9rkKCPDXfUHX0IoP9LpHYo2NPFampa4e+/pFDc3jQdxrxQLaw==", + "dev": true, + "requires": { + "bn.js": "^4.1.0", + "elliptic": "^6.0.0" + } + }, + "create-hash": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz", + "integrity": "sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==", + "dev": true, + "requires": { + "cipher-base": "^1.0.1", + "inherits": "^2.0.1", + "md5.js": "^1.3.4", + "ripemd160": "^2.0.1", + "sha.js": "^2.4.0" + } + }, + "create-hmac": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/create-hmac/-/create-hmac-1.1.7.tgz", + "integrity": "sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg==", + "dev": true, + "requires": { + "cipher-base": "^1.0.3", + "create-hash": "^1.1.0", + "inherits": "^2.0.1", + "ripemd160": "^2.0.0", + "safe-buffer": "^5.0.1", + "sha.js": "^2.4.8" + } + }, + "cross-spawn": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-3.0.1.tgz", + "integrity": "sha1-ElYDfsufDF9549bvE14wdwGEuYI=", + "dev": true, + "optional": true, + "requires": { + "lru-cache": "^4.0.1", + "which": "^1.2.9" + } + }, + "crypto-browserify": { + "version": "3.12.0", + "resolved": "https://registry.npmjs.org/crypto-browserify/-/crypto-browserify-3.12.0.tgz", + "integrity": "sha512-fz4spIh+znjO2VjL+IdhEpRJ3YN6sMzITSBijk6FK2UvTqruSQW+/cCZTSNsMiZNvUeq0CqurF+dAbyiGOY6Wg==", + "dev": true, + "requires": { + "browserify-cipher": "^1.0.0", + "browserify-sign": "^4.0.0", + "create-ecdh": "^4.0.0", + "create-hash": "^1.1.0", + "create-hmac": "^1.1.0", + "diffie-hellman": "^5.0.0", + "inherits": "^2.0.1", + "pbkdf2": "^3.0.3", + "public-encrypt": "^4.0.0", + "randombytes": "^2.0.0", + "randomfill": "^1.0.3" + } + }, + "css-parse": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/css-parse/-/css-parse-1.7.0.tgz", + "integrity": "sha1-Mh9s9zeCpv91ERE5D8BeLGV9jJs=", + "dev": true + }, + "css-selector-tokenizer": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/css-selector-tokenizer/-/css-selector-tokenizer-0.7.1.tgz", + "integrity": "sha512-xYL0AMZJ4gFzJQsHUKa5jiWWi2vH77WVNg7JYRyewwj6oPh4yb/y6Y9ZCw9dsj/9UauMhtuxR+ogQd//EdEVNA==", + "dev": true, + "requires": { + "cssesc": "^0.1.0", + "fastparse": "^1.1.1", + "regexpu-core": "^1.0.0" + } + }, + "cssauron": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/cssauron/-/cssauron-1.4.0.tgz", + "integrity": "sha1-pmAt/34EqDBtwNuaVR6S6LVmKtg=", + "dev": true, + "requires": { + "through": "X.X.X" + } + }, + "cssesc": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-0.1.0.tgz", + "integrity": "sha1-yBSQPkViM3GgR3tAEJqq++6t27Q=", + "dev": true + }, + "currently-unhandled": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/currently-unhandled/-/currently-unhandled-0.4.1.tgz", + "integrity": "sha1-mI3zP+qxke95mmE2nddsF635V+o=", + "dev": true, + "optional": true, + "requires": { + "array-find-index": "^1.0.1" + } + }, + "custom-event": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/custom-event/-/custom-event-1.0.1.tgz", + "integrity": "sha1-XQKkaFCt8bSjF5RqOSj8y1v9BCU=", + "dev": true + }, + "cyclist": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/cyclist/-/cyclist-0.2.2.tgz", + "integrity": "sha1-GzN5LhHpFKL9bW7WRHRkRE5fpkA=", + "dev": true + }, + "dashdash": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", + "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", + "dev": true, + "requires": { + "assert-plus": "^1.0.0" + } + }, + "date-format": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/date-format/-/date-format-2.0.0.tgz", + "integrity": "sha512-M6UqVvZVgFYqZL1SfHsRGIQSz3ZL+qgbsV5Lp1Vj61LZVYuEwcMXYay7DRDtYs2HQQBK5hQtQ0fD9aEJ89V0LA==", + "dev": true + }, + "date-now": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/date-now/-/date-now-0.1.4.tgz", + "integrity": "sha1-6vQ5/U1ISK105cx9vvIAZyueNFs=", + "dev": true + }, + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", + "dev": true + }, + "decode-uri-component": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz", + "integrity": "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=", + "dev": true + }, + "deep-equal": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-1.0.1.tgz", + "integrity": "sha1-9dJgKStmDghO/0zbyfCK0yR0SLU=", + "dev": true + }, + "default-gateway": { + "version": "2.7.2", + "resolved": "https://registry.npmjs.org/default-gateway/-/default-gateway-2.7.2.tgz", + "integrity": "sha512-lAc4i9QJR0YHSDFdzeBQKfZ1SRDG3hsJNEkrpcZa8QhBfidLAilT60BDEIVUUGqosFp425KOgB3uYqcnQrWafQ==", + "dev": true, + "requires": { + "execa": "^0.10.0", + "ip-regex": "^2.1.0" + } + }, + "default-require-extensions": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/default-require-extensions/-/default-require-extensions-2.0.0.tgz", + "integrity": "sha1-9fj7sYp9bVCyH2QfZJ67Uiz+JPc=", + "dev": true, + "requires": { + "strip-bom": "^3.0.0" + }, + "dependencies": { + "strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", + "dev": true + } + } + }, + "define-property": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-2.0.2.tgz", + "integrity": "sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==", + "dev": true, + "requires": { + "is-descriptor": "^1.0.2", + "isobject": "^3.0.1" + }, + "dependencies": { + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + } + } + } + }, + "del": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/del/-/del-3.0.0.tgz", + "integrity": "sha1-U+z2mf/LyzljdpGrE7rxYIGXZuU=", + "dev": true, + "requires": { + "globby": "^6.1.0", + "is-path-cwd": "^1.0.0", + "is-path-in-cwd": "^1.0.0", + "p-map": "^1.1.1", + "pify": "^3.0.0", + "rimraf": "^2.2.8" + }, + "dependencies": { + "globby": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-6.1.0.tgz", + "integrity": "sha1-9abXDoOV4hyFj7BInWTfAkJNUGw=", + "dev": true, + "requires": { + "array-union": "^1.0.1", + "glob": "^7.0.3", + "object-assign": "^4.0.1", + "pify": "^2.0.0", + "pinkie-promise": "^2.0.0" + }, + "dependencies": { + "pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", + "dev": true + } + } + } + } + }, + "delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=", + "dev": true + }, + "delegates": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", + "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=", + "dev": true, + "optional": true + }, + "depd": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=", + "dev": true + }, + "dependency-graph": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/dependency-graph/-/dependency-graph-0.7.2.tgz", + "integrity": "sha512-KqtH4/EZdtdfWX0p6MGP9jljvxSY6msy/pRUD4jgNwVpv3v1QmNLlsB3LDSSUg79BRVSn7jI1QPRtArGABovAQ==", + "dev": true + }, + "des.js": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/des.js/-/des.js-1.0.0.tgz", + "integrity": "sha1-wHTS4qpqipoH29YfmhXCzYPsjsw=", + "dev": true, + "requires": { + "inherits": "^2.0.1", + "minimalistic-assert": "^1.0.0" + } + }, + "destroy": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", + "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=", + "dev": true + }, + "detect-indent": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/detect-indent/-/detect-indent-4.0.0.tgz", + "integrity": "sha1-920GQ1LN9Docts5hnE7jqUdd4gg=", + "dev": true, + "requires": { + "repeating": "^2.0.0" + } + }, + "detect-node": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/detect-node/-/detect-node-2.0.4.tgz", + "integrity": "sha512-ZIzRpLJrOj7jjP2miAtgqIfmzbxa4ZOr5jJc601zklsfEx9oTzmmj2nVpIPRpNlRTIh8lc1kyViIY7BWSGNmKw==", + "dev": true + }, + "di": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/di/-/di-0.0.1.tgz", + "integrity": "sha1-gGZJMmzqp8qjMG112YXqJ0i6kTw=", + "dev": true + }, + "diff": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", + "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==", + "dev": true + }, + "diffie-hellman": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/diffie-hellman/-/diffie-hellman-5.0.3.tgz", + "integrity": "sha512-kqag/Nl+f3GwyK25fhUMYj81BUOrZ9IuJsjIcDE5icNM9FJHAVm3VcUDxdLPoQtTuUylWm6ZIknYJwwaPxsUzg==", + "dev": true, + "requires": { + "bn.js": "^4.1.0", + "miller-rabin": "^4.0.0", + "randombytes": "^2.0.0" + } + }, + "dir-glob": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-2.2.2.tgz", + "integrity": "sha512-f9LBi5QWzIW3I6e//uxZoLBlUt9kcp66qo0sSCxL6YZKc75R1c4MFCoe/LaZiBGmgujvQdxc5Bn3QhfyvK5Hsw==", + "dev": true, + "requires": { + "path-type": "^3.0.0" + } + }, + "dns-equal": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/dns-equal/-/dns-equal-1.0.0.tgz", + "integrity": "sha1-s55/HabrCnW6nBcySzR1PEfgZU0=", + "dev": true + }, + "dns-packet": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/dns-packet/-/dns-packet-1.3.1.tgz", + "integrity": "sha512-0UxfQkMhYAUaZI+xrNZOz/as5KgDU0M/fQ9b6SpkyLbk3GEswDi6PADJVaYJradtRVsRIlF1zLyOodbcTCDzUg==", + "dev": true, + "requires": { + "ip": "^1.1.0", + "safe-buffer": "^5.0.1" + } + }, + "dns-txt": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/dns-txt/-/dns-txt-2.0.2.tgz", + "integrity": "sha1-uR2Ab10nGI5Ks+fRB9iBocxGQrY=", + "dev": true, + "requires": { + "buffer-indexof": "^1.0.0" + } + }, + "dom-serialize": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/dom-serialize/-/dom-serialize-2.2.1.tgz", + "integrity": "sha1-ViromZ9Evl6jB29UGdzVnrQ6yVs=", + "dev": true, + "requires": { + "custom-event": "~1.0.0", + "ent": "~2.2.0", + "extend": "^3.0.0", + "void-elements": "^2.0.0" + } + }, + "domain-browser": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/domain-browser/-/domain-browser-1.2.0.tgz", + "integrity": "sha512-jnjyiM6eRyZl2H+W8Q/zLMA481hzi0eszAaBUzIVnmYVDBbnLxVNnfu1HgEBvCbL+71FrxMl3E6lpKH7Ge3OXA==", + "dev": true + }, + "duplexify": { + "version": "3.7.1", + "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-3.7.1.tgz", + "integrity": "sha512-07z8uv2wMyS51kKhD1KsdXJg5WQ6t93RneqRxUHnskXVtlYYkLqM0gqStQZ3pj073g687jPCHrqNfCzawLYh5g==", + "dev": true, + "requires": { + "end-of-stream": "^1.0.0", + "inherits": "^2.0.1", + "readable-stream": "^2.0.0", + "stream-shift": "^1.0.0" + } + }, + "ecc-jsbn": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", + "integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=", + "dev": true, + "requires": { + "jsbn": "~0.1.0", + "safer-buffer": "^2.1.0" + } + }, + "ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=", + "dev": true + }, + "electron-to-chromium": { + "version": "1.3.124", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.124.tgz", + "integrity": "sha512-glecGr/kFdfeXUHOHAWvGcXrxNU+1wSO/t5B23tT1dtlvYB26GY8aHzZSWD7HqhqC800Lr+w/hQul6C5AF542w==", + "dev": true + }, + "elliptic": { + "version": "6.4.1", + "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.4.1.tgz", + "integrity": "sha512-BsXLz5sqX8OHcsh7CqBMztyXARmGQ3LWPtGjJi6DiJHq5C/qvi9P3OqgswKSDftbu8+IoI/QDTAm2fFnQ9SZSQ==", + "dev": true, + "requires": { + "bn.js": "^4.4.0", + "brorand": "^1.0.1", + "hash.js": "^1.0.0", + "hmac-drbg": "^1.0.0", + "inherits": "^2.0.1", + "minimalistic-assert": "^1.0.0", + "minimalistic-crypto-utils": "^1.0.0" + } + }, + "emojis-list": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-2.1.0.tgz", + "integrity": "sha1-TapNnbAPmBmIDHn6RXrlsJof04k=", + "dev": true + }, + "encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=", + "dev": true + }, + "encoding": { + "version": "0.1.12", + "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.12.tgz", + "integrity": "sha1-U4tm8+5izRq1HsMjgp0flIDHS+s=", + "dev": true, + "requires": { + "iconv-lite": "~0.4.13" + } + }, + "end-of-stream": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.1.tgz", + "integrity": "sha512-1MkrZNvWTKCaigbn+W15elq2BB/L22nqrSY5DKlo3X6+vclJm8Bb5djXJBmEX6fS3+zCh/F4VBK5Z2KxJt4s2Q==", + "dev": true, + "requires": { + "once": "^1.4.0" + } + }, + "engine.io": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-3.2.1.tgz", + "integrity": "sha512-+VlKzHzMhaU+GsCIg4AoXF1UdDFjHHwMmMKqMJNDNLlUlejz58FCy4LBqB2YVJskHGYl06BatYWKP2TVdVXE5w==", + "dev": true, + "requires": { + "accepts": "~1.3.4", + "base64id": "1.0.0", + "cookie": "0.3.1", + "debug": "~3.1.0", + "engine.io-parser": "~2.1.0", + "ws": "~3.3.1" + }, + "dependencies": { + "debug": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + } + } + }, + "engine.io-client": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-3.2.1.tgz", + "integrity": "sha512-y5AbkytWeM4jQr7m/koQLc5AxpRKC1hEVUb/s1FUAWEJq5AzJJ4NLvzuKPuxtDi5Mq755WuDvZ6Iv2rXj4PTzw==", + "dev": true, + "requires": { + "component-emitter": "1.2.1", + "component-inherit": "0.0.3", + "debug": "~3.1.0", + "engine.io-parser": "~2.1.1", + "has-cors": "1.1.0", + "indexof": "0.0.1", + "parseqs": "0.0.5", + "parseuri": "0.0.5", + "ws": "~3.3.1", + "xmlhttprequest-ssl": "~1.5.4", + "yeast": "0.1.2" + }, + "dependencies": { + "debug": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + } + } + }, + "engine.io-parser": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-2.1.3.tgz", + "integrity": "sha512-6HXPre2O4Houl7c4g7Ic/XzPnHBvaEmN90vtRO9uLmwtRqQmTOw0QMevL1TOfL2Cpu1VzsaTmMotQgMdkzGkVA==", + "dev": true, + "requires": { + "after": "0.8.2", + "arraybuffer.slice": "~0.0.7", + "base64-arraybuffer": "0.1.5", + "blob": "0.0.5", + "has-binary2": "~1.0.2" + } + }, + "enhanced-resolve": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-4.1.0.tgz", + "integrity": "sha512-F/7vkyTtyc/llOIn8oWclcB25KdRaiPBpZYDgJHgh/UHtpgT2p2eldQgtQnLtUvfMKPKxbRaQM/hHkvLHt1Vng==", + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "memory-fs": "^0.4.0", + "tapable": "^1.0.0" + } + }, + "ent": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/ent/-/ent-2.2.0.tgz", + "integrity": "sha1-6WQhkyWiHQX0RGai9obtbOX13R0=", + "dev": true + }, + "err-code": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/err-code/-/err-code-1.1.2.tgz", + "integrity": "sha1-BuARbTAo9q70gGhJ6w6mp0iuaWA=", + "dev": true + }, + "errno": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/errno/-/errno-0.1.7.tgz", + "integrity": "sha512-MfrRBDWzIWifgq6tJj60gkAwtLNb6sQPlcFrSOflcP1aFmmruKQ2wRnze/8V6kgyz7H3FF8Npzv78mZ7XLLflg==", + "dev": true, + "requires": { + "prr": "~1.0.1" + } + }, + "error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "dev": true, + "requires": { + "is-arrayish": "^0.2.1" + } + }, + "es6-promise": { + "version": "4.2.6", + "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.6.tgz", + "integrity": "sha512-aRVgGdnmW2OiySVPUC9e6m+plolMAJKjZnQlCwNSuK5yQ0JN61DZSO1X1Ufd1foqWRAlig0rhduTCHe7sVtK5Q==", + "dev": true + }, + "es6-promisify": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/es6-promisify/-/es6-promisify-5.0.0.tgz", + "integrity": "sha1-UQnWLz5W6pZ8S2NQWu8IKRyKUgM=", + "dev": true, + "requires": { + "es6-promise": "^4.0.3" + } + }, + "escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=", + "dev": true + }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", + "dev": true + }, + "eslint-scope": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-4.0.3.tgz", + "integrity": "sha512-p7VutNr1O/QrxysMo3E45FjYDTeXBy0iTltPFNSqKAIfjDSXC+4dj+qfyuD8bfAXrW/y6lW3O76VaYNPKfpKrg==", + "dev": true, + "requires": { + "esrecurse": "^4.1.0", + "estraverse": "^4.1.1" + } + }, + "esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true + }, + "esrecurse": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.2.1.tgz", + "integrity": "sha512-64RBB++fIOAXPw3P9cy89qfMlvZEXZkqqJkjqqXIvzP5ezRZjW+lPWjw35UX/3EhUPFYbg5ER4JYgDw4007/DQ==", + "dev": true, + "requires": { + "estraverse": "^4.1.0" + } + }, + "estraverse": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.2.0.tgz", + "integrity": "sha1-De4/7TH81GlhjOc0IJn8GvoL2xM=", + "dev": true + }, + "esutils": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.2.tgz", + "integrity": "sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs=", + "dev": true + }, + "etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=", + "dev": true + }, + "eventemitter3": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-3.1.0.tgz", + "integrity": "sha512-ivIvhpq/Y0uSjcHDcOIccjmYjGLcP09MFGE7ysAwkAvkXfpZlC985pH2/ui64DKazbTW/4kN3yqozUxlXzI6cA==", + "dev": true + }, + "events": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.0.0.tgz", + "integrity": "sha512-Dc381HFWJzEOhQ+d8pkNon++bk9h6cdAoAj4iE6Q4y6xgTzySWXlKn05/TVNpjnfRqi/X0EpJEJohPjNI3zpVA==", + "dev": true + }, + "eventsource": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/eventsource/-/eventsource-1.0.7.tgz", + "integrity": "sha512-4Ln17+vVT0k8aWq+t/bF5arcS3EpT9gYtW66EPacdj/mAFevznsnyoHLPy2BA8gbIQeIHoPsvwmfBftfcG//BQ==", + "dev": true, + "requires": { + "original": "^1.0.0" + } + }, + "evp_bytestokey": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz", + "integrity": "sha512-/f2Go4TognH/KvCISP7OUsHn85hT9nUkxxA9BEWxFn+Oj9o8ZNLm/40hdlgSLyuOimsrTKLUMEorQexp/aPQeA==", + "dev": true, + "requires": { + "md5.js": "^1.3.4", + "safe-buffer": "^5.1.1" + } + }, + "execa": { + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-0.10.0.tgz", + "integrity": "sha512-7XOMnz8Ynx1gGo/3hyV9loYNPWM94jG3+3T3Y8tsfSstFmETmENCMU/A/zj8Lyaj1lkgEepKepvd6240tBRvlw==", + "dev": true, + "requires": { + "cross-spawn": "^6.0.0", + "get-stream": "^3.0.0", + "is-stream": "^1.1.0", + "npm-run-path": "^2.0.0", + "p-finally": "^1.0.0", + "signal-exit": "^3.0.0", + "strip-eof": "^1.0.0" + }, + "dependencies": { + "cross-spawn": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", + "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", + "dev": true, + "requires": { + "nice-try": "^1.0.4", + "path-key": "^2.0.1", + "semver": "^5.5.0", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + } + } + } + }, + "exit": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", + "integrity": "sha1-BjJjj42HfMghB9MKD/8aF8uhzQw=", + "dev": true + }, + "expand-brackets": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz", + "integrity": "sha1-t3c14xXOMPa27/D4OwQVGiJEliI=", + "dev": true, + "requires": { + "debug": "^2.3.3", + "define-property": "^0.2.5", + "extend-shallow": "^2.0.1", + "posix-character-classes": "^0.1.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "requires": { + "is-descriptor": "^0.1.0" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "express": { + "version": "4.16.4", + "resolved": "https://registry.npmjs.org/express/-/express-4.16.4.tgz", + "integrity": "sha512-j12Uuyb4FMrd/qQAm6uCHAkPtO8FDTRJZBDd5D2KOL2eLaz1yUNdUB/NOIyq0iU4q4cFarsUCrnFDPBcnksuOg==", + "dev": true, + "requires": { + "accepts": "~1.3.5", + "array-flatten": "1.1.1", + "body-parser": "1.18.3", + "content-disposition": "0.5.2", + "content-type": "~1.0.4", + "cookie": "0.3.1", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "~1.1.2", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "1.1.1", + "fresh": "0.5.2", + "merge-descriptors": "1.0.1", + "methods": "~1.1.2", + "on-finished": "~2.3.0", + "parseurl": "~1.3.2", + "path-to-regexp": "0.1.7", + "proxy-addr": "~2.0.4", + "qs": "6.5.2", + "range-parser": "~1.2.0", + "safe-buffer": "5.1.2", + "send": "0.16.2", + "serve-static": "1.13.2", + "setprototypeof": "1.1.0", + "statuses": "~1.4.0", + "type-is": "~1.6.16", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "dependencies": { + "array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=", + "dev": true + } + } + }, + "extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "dev": true + }, + "extend-shallow": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", + "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=", + "dev": true, + "requires": { + "assign-symbols": "^1.0.0", + "is-extendable": "^1.0.1" + }, + "dependencies": { + "is-extendable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", + "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", + "dev": true, + "requires": { + "is-plain-object": "^2.0.4" + } + } + } + }, + "external-editor": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.0.3.tgz", + "integrity": "sha512-bn71H9+qWoOQKyZDo25mOMVpSmXROAsTJVVVYzrrtol3d4y+AsKjf4Iwl2Q+IuT0kFSQ1qo166UuIwqYq7mGnA==", + "dev": true, + "requires": { + "chardet": "^0.7.0", + "iconv-lite": "^0.4.24", + "tmp": "^0.0.33" + }, + "dependencies": { + "iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dev": true, + "requires": { + "safer-buffer": ">= 2.1.2 < 3" + } + } + } + }, + "extglob": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz", + "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==", + "dev": true, + "requires": { + "array-unique": "^0.3.2", + "define-property": "^1.0.0", + "expand-brackets": "^2.1.4", + "extend-shallow": "^2.0.1", + "fragment-cache": "^0.2.1", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + }, + "dependencies": { + "define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "dev": true, + "requires": { + "is-descriptor": "^1.0.0" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + }, + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + } + } + } + }, + "extsprintf": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", + "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=", + "dev": true + }, + "fast-deep-equal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz", + "integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=", + "dev": true + }, + "fast-json-stable-stringify": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz", + "integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I=", + "dev": true + }, + "fastparse": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/fastparse/-/fastparse-1.1.2.tgz", + "integrity": "sha512-483XLLxTVIwWK3QTrMGRqUfUpoOs/0hbQrl2oz4J0pAcm3A3bu84wxTFqGqkJzewCLdME38xJLJAxBABfQT8sQ==", + "dev": true + }, + "faye-websocket": { + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.10.0.tgz", + "integrity": "sha1-TkkvjQTftviQA1B/btvy1QHnxvQ=", + "dev": true, + "requires": { + "websocket-driver": ">=0.5.1" + } + }, + "figgy-pudding": { + "version": "3.5.1", + "resolved": "https://registry.npmjs.org/figgy-pudding/-/figgy-pudding-3.5.1.tgz", + "integrity": "sha512-vNKxJHTEKNThjfrdJwHc7brvM6eVevuO5nTj6ez8ZQ1qbXTvGthucRF7S4vf2cr71QVnT70V34v0S1DyQsti0w==", + "dev": true + }, + "figures": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-2.0.0.tgz", + "integrity": "sha1-OrGi0qYsi/tDGgyUy3l6L84nyWI=", + "dev": true, + "requires": { + "escape-string-regexp": "^1.0.5" + } + }, + "file-loader": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/file-loader/-/file-loader-3.0.1.tgz", + "integrity": "sha512-4sNIOXgtH/9WZq4NvlfU3Opn5ynUsqBwSLyM+I7UOwdGigTBYfVVQEwe/msZNX/j4pCJTIM14Fsw66Svo1oVrw==", + "dev": true, + "requires": { + "loader-utils": "^1.0.2", + "schema-utils": "^1.0.0" + } + }, + "fileset": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/fileset/-/fileset-2.0.3.tgz", + "integrity": "sha1-jnVIqW08wjJ+5eZ0FocjozO7oqA=", + "dev": true, + "requires": { + "glob": "^7.0.3", + "minimatch": "^3.0.3" + } + }, + "fill-range": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", + "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", + "dev": true, + "requires": { + "extend-shallow": "^2.0.1", + "is-number": "^3.0.0", + "repeat-string": "^1.6.1", + "to-regex-range": "^2.1.0" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "finalhandler": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.1.tgz", + "integrity": "sha512-Y1GUDo39ez4aHAw7MysnUD5JzYX+WaIj8I57kO3aEPT1fFRL4sr7mjei97FgnwhAyyzRYmQZaTHb2+9uZ1dPtg==", + "dev": true, + "requires": { + "debug": "2.6.9", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "on-finished": "~2.3.0", + "parseurl": "~1.3.2", + "statuses": "~1.4.0", + "unpipe": "~1.0.0" + } + }, + "find-cache-dir": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-1.0.0.tgz", + "integrity": "sha1-kojj6ePMN0hxfTnq3hfPcfww7m8=", + "dev": true, + "requires": { + "commondir": "^1.0.1", + "make-dir": "^1.0.0", + "pkg-dir": "^2.0.0" + } + }, + "find-up": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", + "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=", + "dev": true, + "requires": { + "locate-path": "^2.0.0" + } + }, + "flatted": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-2.0.0.tgz", + "integrity": "sha512-R+H8IZclI8AAkSBRQJLVOsxwAoHd6WC40b4QTNWIjzAa6BXOBfQcM587MXDTVPeYaopFNWHUFLx7eNmHDSxMWg==", + "dev": true + }, + "flush-write-stream": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/flush-write-stream/-/flush-write-stream-1.1.1.tgz", + "integrity": "sha512-3Z4XhFZ3992uIq0XOqb9AreonueSYphE6oYbpt5+3u06JWklbsPkNv3ZKkP9Bz/r+1MWCaMoSQ28P85+1Yc77w==", + "dev": true, + "requires": { + "inherits": "^2.0.3", + "readable-stream": "^2.3.6" + } + }, + "follow-redirects": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.7.0.tgz", + "integrity": "sha512-m/pZQy4Gj287eNy94nivy5wchN3Kp+Q5WgUPNy5lJSZ3sgkVKSYV/ZChMAQVIgx1SqfZ2zBZtPA2YlXIWxxJOQ==", + "dev": true, + "requires": { + "debug": "^3.2.6" + }, + "dependencies": { + "debug": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", + "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "ms": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", + "dev": true + } + } + }, + "for-in": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", + "integrity": "sha1-gQaNKVqBQuwKxybG4iAMMPttXoA=", + "dev": true + }, + "for-own": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/for-own/-/for-own-1.0.0.tgz", + "integrity": "sha1-xjMy9BXO3EsE2/5wz4NklMU8tEs=", + "dev": true, + "requires": { + "for-in": "^1.0.1" + } + }, + "forever-agent": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", + "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=", + "dev": true + }, + "form-data": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", + "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", + "dev": true, + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.6", + "mime-types": "^2.1.12" + } + }, + "forwarded": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz", + "integrity": "sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ=", + "dev": true + }, + "fragment-cache": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/fragment-cache/-/fragment-cache-0.2.1.tgz", + "integrity": "sha1-QpD60n8T6Jvn8zeZxrxaCr//DRk=", + "dev": true, + "requires": { + "map-cache": "^0.2.2" + } + }, + "fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=", + "dev": true + }, + "from2": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/from2/-/from2-2.3.0.tgz", + "integrity": "sha1-i/tVAr3kpNNs/e6gB/zKIdfjgq8=", + "dev": true, + "requires": { + "inherits": "^2.0.1", + "readable-stream": "^2.0.0" + } + }, + "fs-access": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/fs-access/-/fs-access-1.0.1.tgz", + "integrity": "sha1-1qh/JiJxzv6+wwxVNAf7mV2od3o=", + "dev": true, + "requires": { + "null-check": "^1.0.0" + } + }, + "fs-extra": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-7.0.1.tgz", + "integrity": "sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==", + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + } + }, + "fs-minipass": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-1.2.5.tgz", + "integrity": "sha512-JhBl0skXjUPCFH7x6x61gQxrKyXsxB5gcgePLZCwfyCGGsTISMoIeObbrvVeP6Xmyaudw4TT43qV2Gz+iyd2oQ==", + "dev": true, + "requires": { + "minipass": "^2.2.1" + } + }, + "fs-write-stream-atomic": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/fs-write-stream-atomic/-/fs-write-stream-atomic-1.0.10.tgz", + "integrity": "sha1-tH31NJPvkR33VzHnCp3tAYnbQMk=", + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "iferr": "^0.1.5", + "imurmurhash": "^0.1.4", + "readable-stream": "1 || 2" + } + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "dev": true + }, + "fsevents": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.7.tgz", + "integrity": "sha512-Pxm6sI2MeBD7RdD12RYsqaP0nMiwx8eZBXCa6z2L+mRHm2DYrOYwihmhjpkdjUHwQhslWQjRpEgNq4XvBmaAuw==", + "dev": true, + "optional": true, + "requires": { + "nan": "^2.9.2", + "node-pre-gyp": "^0.10.0" + }, + "dependencies": { + "abbrev": { + "version": "1.1.1", + "bundled": true, + "dev": true, + "optional": true + }, + "ansi-regex": { + "version": "2.1.1", + "bundled": true, + "dev": true, + "optional": true + }, + "aproba": { + "version": "1.2.0", + "bundled": true, + "dev": true, + "optional": true + }, + "are-we-there-yet": { + "version": "1.1.5", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "delegates": "^1.0.0", + "readable-stream": "^2.0.6" + } + }, + "balanced-match": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "optional": true + }, + "brace-expansion": { + "version": "1.1.11", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "chownr": { + "version": "1.1.1", + "bundled": true, + "dev": true, + "optional": true + }, + "code-point-at": { + "version": "1.1.0", + "bundled": true, + "dev": true, + "optional": true + }, + "concat-map": { + "version": "0.0.1", + "bundled": true, + "dev": true, + "optional": true + }, + "console-control-strings": { + "version": "1.1.0", + "bundled": true, + "dev": true, + "optional": true + }, + "core-util-is": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "optional": true + }, + "debug": { + "version": "2.6.9", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "ms": "2.0.0" + } + }, + "deep-extend": { + "version": "0.6.0", + "bundled": true, + "dev": true, + "optional": true + }, + "delegates": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "optional": true + }, + "detect-libc": { + "version": "1.0.3", + "bundled": true, + "dev": true, + "optional": true + }, + "fs-minipass": { + "version": "1.2.5", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "minipass": "^2.2.1" + } + }, + "fs.realpath": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "optional": true + }, + "gauge": { + "version": "2.7.4", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "aproba": "^1.0.3", + "console-control-strings": "^1.0.0", + "has-unicode": "^2.0.0", + "object-assign": "^4.1.0", + "signal-exit": "^3.0.0", + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1", + "wide-align": "^1.1.0" + } + }, + "glob": { + "version": "7.1.3", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "has-unicode": { + "version": "2.0.1", + "bundled": true, + "dev": true, + "optional": true + }, + "iconv-lite": { + "version": "0.4.24", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "safer-buffer": ">= 2.1.2 < 3" + } + }, + "ignore-walk": { + "version": "3.0.1", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "minimatch": "^3.0.4" + } + }, + "inflight": { + "version": "1.0.6", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.3", + "bundled": true, + "dev": true, + "optional": true + }, + "ini": { + "version": "1.3.5", + "bundled": true, + "dev": true, + "optional": true + }, + "is-fullwidth-code-point": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "number-is-nan": "^1.0.0" + } + }, + "isarray": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "optional": true + }, + "minimatch": { + "version": "3.0.4", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "minimist": { + "version": "0.0.8", + "bundled": true, + "dev": true, + "optional": true + }, + "minipass": { + "version": "2.3.5", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "safe-buffer": "^5.1.2", + "yallist": "^3.0.0" + } + }, + "minizlib": { + "version": "1.2.1", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "minipass": "^2.2.1" + } + }, + "mkdirp": { + "version": "0.5.1", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "minimist": "0.0.8" + } + }, + "ms": { + "version": "2.0.0", + "bundled": true, + "dev": true, + "optional": true + }, + "needle": { + "version": "2.2.4", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "debug": "^2.1.2", + "iconv-lite": "^0.4.4", + "sax": "^1.2.4" + } + }, + "node-pre-gyp": { + "version": "0.10.3", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "detect-libc": "^1.0.2", + "mkdirp": "^0.5.1", + "needle": "^2.2.1", + "nopt": "^4.0.1", + "npm-packlist": "^1.1.6", + "npmlog": "^4.0.2", + "rc": "^1.2.7", + "rimraf": "^2.6.1", + "semver": "^5.3.0", + "tar": "^4" + } + }, + "nopt": { + "version": "4.0.1", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "abbrev": "1", + "osenv": "^0.1.4" + } + }, + "npm-bundled": { + "version": "1.0.5", + "bundled": true, + "dev": true, + "optional": true + }, + "npm-packlist": { + "version": "1.2.0", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "ignore-walk": "^3.0.1", + "npm-bundled": "^1.0.1" + } + }, + "npmlog": { + "version": "4.1.2", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "are-we-there-yet": "~1.1.2", + "console-control-strings": "~1.1.0", + "gauge": "~2.7.3", + "set-blocking": "~2.0.0" + } + }, + "number-is-nan": { + "version": "1.0.1", + "bundled": true, + "dev": true, + "optional": true + }, + "object-assign": { + "version": "4.1.1", + "bundled": true, + "dev": true, + "optional": true + }, + "once": { + "version": "1.4.0", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "wrappy": "1" + } + }, + "os-homedir": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "optional": true + }, + "os-tmpdir": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "optional": true + }, + "osenv": { + "version": "0.1.5", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "os-homedir": "^1.0.0", + "os-tmpdir": "^1.0.0" + } + }, + "path-is-absolute": { + "version": "1.0.1", + "bundled": true, + "dev": true, + "optional": true + }, + "process-nextick-args": { + "version": "2.0.0", + "bundled": true, + "dev": true, + "optional": true + }, + "rc": { + "version": "1.2.8", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "deep-extend": "^0.6.0", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" + }, + "dependencies": { + "minimist": { + "version": "1.2.0", + "bundled": true, + "dev": true, + "optional": true + } + } + }, + "readable-stream": { + "version": "2.3.6", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "rimraf": { + "version": "2.6.3", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "glob": "^7.1.3" + } + }, + "safe-buffer": { + "version": "5.1.2", + "bundled": true, + "dev": true, + "optional": true + }, + "safer-buffer": { + "version": "2.1.2", + "bundled": true, + "dev": true, + "optional": true + }, + "sax": { + "version": "1.2.4", + "bundled": true, + "dev": true, + "optional": true + }, + "semver": { + "version": "5.6.0", + "bundled": true, + "dev": true, + "optional": true + }, + "set-blocking": { + "version": "2.0.0", + "bundled": true, + "dev": true, + "optional": true + }, + "signal-exit": { + "version": "3.0.2", + "bundled": true, + "dev": true, + "optional": true + }, + "string-width": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" + } + }, + "string_decoder": { + "version": "1.1.1", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "safe-buffer": "~5.1.0" + } + }, + "strip-ansi": { + "version": "3.0.1", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "ansi-regex": "^2.0.0" + } + }, + "strip-json-comments": { + "version": "2.0.1", + "bundled": true, + "dev": true, + "optional": true + }, + "tar": { + "version": "4.4.8", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "chownr": "^1.1.1", + "fs-minipass": "^1.2.5", + "minipass": "^2.3.4", + "minizlib": "^1.1.1", + "mkdirp": "^0.5.0", + "safe-buffer": "^5.1.2", + "yallist": "^3.0.2" + } + }, + "util-deprecate": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "optional": true + }, + "wide-align": { + "version": "1.1.3", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "string-width": "^1.0.2 || 2" + } + }, + "wrappy": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "optional": true + }, + "yallist": { + "version": "3.0.3", + "bundled": true, + "dev": true, + "optional": true + } + } + }, + "fstream": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/fstream/-/fstream-1.0.11.tgz", + "integrity": "sha1-XB+x8RdHcRTwYyoOtLcbPLD9MXE=", + "dev": true, + "optional": true, + "requires": { + "graceful-fs": "^4.1.2", + "inherits": "~2.0.0", + "mkdirp": ">=0.5 0", + "rimraf": "2" + } + }, + "gauge": { + "version": "2.7.4", + "resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz", + "integrity": "sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=", + "dev": true, + "optional": true, + "requires": { + "aproba": "^1.0.3", + "console-control-strings": "^1.0.0", + "has-unicode": "^2.0.0", + "object-assign": "^4.1.0", + "signal-exit": "^3.0.0", + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1", + "wide-align": "^1.1.0" + } + }, + "gaze": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/gaze/-/gaze-1.1.3.tgz", + "integrity": "sha512-BRdNm8hbWzFzWHERTrejLqwHDfS4GibPoq5wjTPIoJHoBtKGPg3xAFfxmM+9ztbXelxcf2hwQcaz1PtmFeue8g==", + "dev": true, + "optional": true, + "requires": { + "globule": "^1.0.0" + } + }, + "genfun": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/genfun/-/genfun-5.0.0.tgz", + "integrity": "sha512-KGDOARWVga7+rnB3z9Sd2Letx515owfk0hSxHGuqjANb1M+x2bGZGqHLiozPsYMdM2OubeMni/Hpwmjq6qIUhA==", + "dev": true + }, + "get-caller-file": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-1.0.3.tgz", + "integrity": "sha512-3t6rVToeoZfYSGd8YoLFR2DJkiQrIiUrGcjvFX2mDw3bn6k2OtwHN0TNCLbBO+w8qTvimhDkv+LSscbJY1vE6w==", + "dev": true + }, + "get-stdin": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-4.0.1.tgz", + "integrity": "sha1-uWjGsKBDhDJJAui/Gl3zJXmkUP4=", + "dev": true, + "optional": true + }, + "get-stream": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz", + "integrity": "sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ=", + "dev": true + }, + "get-value": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz", + "integrity": "sha1-3BXKHGcjh8p2vTesCjlbogQqLCg=", + "dev": true + }, + "getpass": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", + "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", + "dev": true, + "requires": { + "assert-plus": "^1.0.0" + } + }, + "glob": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz", + "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "glob-parent": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz", + "integrity": "sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4=", + "dev": true, + "requires": { + "is-glob": "^3.1.0", + "path-dirname": "^1.0.0" + }, + "dependencies": { + "is-glob": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", + "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=", + "dev": true, + "requires": { + "is-extglob": "^2.1.0" + } + } + } + }, + "globals": { + "version": "9.18.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-9.18.0.tgz", + "integrity": "sha512-S0nG3CLEQiY/ILxqtztTWH/3iRRdyBLw6KMDxnKMchrtbj2OFmehVh0WUCfW3DUrIgx/qFrJPICrq4Z4sTR9UQ==", + "dev": true + }, + "globby": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/globby/-/globby-7.1.1.tgz", + "integrity": "sha1-+yzP+UAfhgCUXfral0QMypcrhoA=", + "dev": true, + "requires": { + "array-union": "^1.0.1", + "dir-glob": "^2.0.0", + "glob": "^7.1.2", + "ignore": "^3.3.5", + "pify": "^3.0.0", + "slash": "^1.0.0" + } + }, + "globule": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/globule/-/globule-1.2.1.tgz", + "integrity": "sha512-g7QtgWF4uYSL5/dn71WxubOrS7JVGCnFPEnoeChJmBnyR9Mw8nGoEwOgJL/RC2Te0WhbsEUCejfH8SZNJ+adYQ==", + "dev": true, + "optional": true, + "requires": { + "glob": "~7.1.1", + "lodash": "~4.17.10", + "minimatch": "~3.0.2" + } + }, + "graceful-fs": { + "version": "4.1.15", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.15.tgz", + "integrity": "sha512-6uHUhOPEBgQ24HM+r6b/QwWfZq+yiFcipKFrOFiBEnWdy5sdzYoi+pJeQaPI5qOLRFqWmAXUPQNsielzdLoecA==", + "dev": true + }, + "handle-thing": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/handle-thing/-/handle-thing-2.0.0.tgz", + "integrity": "sha512-d4sze1JNC454Wdo2fkuyzCr6aHcbL6PGGuFAz0Li/NcOm1tCHGnWDRmJP85dh9IhQErTc2svWFEX5xHIOo//kQ==", + "dev": true + }, + "handlebars": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.1.1.tgz", + "integrity": "sha512-3Zhi6C0euYZL5sM0Zcy7lInLXKQ+YLcF/olbN010mzGQ4XVm50JeyBnMqofHh696GrciGruC7kCcApPDJvVgwA==", + "dev": true, + "requires": { + "neo-async": "^2.6.0", + "optimist": "^0.6.1", + "source-map": "^0.6.1", + "uglify-js": "^3.1.4" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + } + } + }, + "har-schema": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", + "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=", + "dev": true + }, + "har-validator": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.3.tgz", + "integrity": "sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g==", + "dev": true, + "requires": { + "ajv": "^6.5.5", + "har-schema": "^2.0.0" + } + }, + "has-ansi": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", + "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=", + "dev": true, + "requires": { + "ansi-regex": "^2.0.0" + } + }, + "has-binary2": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-binary2/-/has-binary2-1.0.3.tgz", + "integrity": "sha512-G1LWKhDSvhGeAQ8mPVQlqNcOB2sJdwATtZKl2pDKKHfpf/rYj24lkinxf69blJbnsvtqqNU+L3SL50vzZhXOnw==", + "dev": true, + "requires": { + "isarray": "2.0.1" + }, + "dependencies": { + "isarray": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.1.tgz", + "integrity": "sha1-o32U7ZzaLVmGXJ92/llu4fM4dB4=", + "dev": true + } + } + }, + "has-cors": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-cors/-/has-cors-1.1.0.tgz", + "integrity": "sha1-XkdHk/fqmEPRu5nCPu9J/xJv/zk=", + "dev": true + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true + }, + "has-unicode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", + "integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=", + "dev": true, + "optional": true + }, + "has-value": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-value/-/has-value-1.0.0.tgz", + "integrity": "sha1-GLKB2lhbHFxR3vJMkw7SmgvmsXc=", + "dev": true, + "requires": { + "get-value": "^2.0.6", + "has-values": "^1.0.0", + "isobject": "^3.0.0" + } + }, + "has-values": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-values/-/has-values-1.0.0.tgz", + "integrity": "sha1-lbC2P+whRmGab+V/51Yo1aOe/k8=", + "dev": true, + "requires": { + "is-number": "^3.0.0", + "kind-of": "^4.0.0" + }, + "dependencies": { + "kind-of": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-4.0.0.tgz", + "integrity": "sha1-IIE989cSkosgc3hpGkUGb65y3Vc=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "hash-base": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-3.0.4.tgz", + "integrity": "sha1-X8hoaEfs1zSZQDMZprCj8/auSRg=", + "dev": true, + "requires": { + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, + "hash.js": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz", + "integrity": "sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==", + "dev": true, + "requires": { + "inherits": "^2.0.3", + "minimalistic-assert": "^1.0.1" + } + }, + "hmac-drbg": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz", + "integrity": "sha1-0nRXAQJabHdabFRXk+1QL8DGSaE=", + "dev": true, + "requires": { + "hash.js": "^1.0.3", + "minimalistic-assert": "^1.0.0", + "minimalistic-crypto-utils": "^1.0.1" + } + }, + "hosted-git-info": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.7.1.tgz", + "integrity": "sha512-7T/BxH19zbcCTa8XkMlbK5lTo1WtgkFi3GvdWEyNuc4Vex7/9Dqbnpsf4JMydcfj9HCg4zUWFTL3Za6lapg5/w==", + "dev": true + }, + "hpack.js": { + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/hpack.js/-/hpack.js-2.1.6.tgz", + "integrity": "sha1-h3dMCUnlE/QuhFdbPEVoH63ioLI=", + "dev": true, + "requires": { + "inherits": "^2.0.1", + "obuf": "^1.0.0", + "readable-stream": "^2.0.1", + "wbuf": "^1.1.0" + } + }, + "html-entities": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-1.2.1.tgz", + "integrity": "sha1-DfKTUfByEWNRXfueVUPl9u7VFi8=", + "dev": true + }, + "http-cache-semantics": { + "version": "3.8.1", + "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-3.8.1.tgz", + "integrity": "sha512-5ai2iksyV8ZXmnZhHH4rWPoxxistEexSi5936zIQ1bnNTW5VnA85B6P/VpXiRM017IgRvb2kKo1a//y+0wSp3w==", + "dev": true + }, + "http-deceiver": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/http-deceiver/-/http-deceiver-1.2.7.tgz", + "integrity": "sha1-+nFolEq5pRnTN8sL7HKE3D5yPYc=", + "dev": true + }, + "http-errors": { + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", + "integrity": "sha1-i1VoC7S+KDoLW/TqLjhYC+HZMg0=", + "dev": true, + "requires": { + "depd": "~1.1.2", + "inherits": "2.0.3", + "setprototypeof": "1.1.0", + "statuses": ">= 1.4.0 < 2" + } + }, + "http-parser-js": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.0.tgz", + "integrity": "sha512-cZdEF7r4gfRIq7ezX9J0T+kQmJNOub71dWbgAXVHDct80TKP4MCETtZQ31xyv38UwgzkWPYF/Xc0ge55dW9Z9w==", + "dev": true + }, + "http-proxy": { + "version": "1.17.0", + "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.17.0.tgz", + "integrity": "sha512-Taqn+3nNvYRfJ3bGvKfBSRwy1v6eePlm3oc/aWVxZp57DQr5Eq3xhKJi7Z4hZpS8PC3H4qI+Yly5EmFacGuA/g==", + "dev": true, + "requires": { + "eventemitter3": "^3.0.0", + "follow-redirects": "^1.0.0", + "requires-port": "^1.0.0" + } + }, + "http-proxy-agent": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-2.1.0.tgz", + "integrity": "sha512-qwHbBLV7WviBl0rQsOzH6o5lwyOIvwp/BdFnvVxXORldu5TmjFfjzBcWUWS5kWAZhmv+JtiDhSuQCp4sBfbIgg==", + "dev": true, + "requires": { + "agent-base": "4", + "debug": "3.1.0" + }, + "dependencies": { + "debug": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + } + } + }, + "http-proxy-middleware": { + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-0.18.0.tgz", + "integrity": "sha512-Fs25KVMPAIIcgjMZkVHJoKg9VcXcC1C8yb9JUgeDvVXY0S/zgVIhMb+qVswDIgtJe2DfckMSY2d6TuTEutlk6Q==", + "dev": true, + "requires": { + "http-proxy": "^1.16.2", + "is-glob": "^4.0.0", + "lodash": "^4.17.5", + "micromatch": "^3.1.9" + } + }, + "http-signature": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", + "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", + "dev": true, + "requires": { + "assert-plus": "^1.0.0", + "jsprim": "^1.2.2", + "sshpk": "^1.7.0" + } + }, + "https-browserify": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/https-browserify/-/https-browserify-1.0.0.tgz", + "integrity": "sha1-7AbBDgo0wPL68Zn3/X/Hj//QPHM=", + "dev": true + }, + "https-proxy-agent": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-2.2.1.tgz", + "integrity": "sha512-HPCTS1LW51bcyMYbxUIOO4HEOlQ1/1qRaFWcyxvwaqUS9TY88aoEuHUY33kuAh1YhVVaDQhLZsnPd+XNARWZlQ==", + "dev": true, + "requires": { + "agent-base": "^4.1.0", + "debug": "^3.1.0" + }, + "dependencies": { + "debug": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", + "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "ms": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", + "dev": true + } + } + }, + "humanize-ms": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/humanize-ms/-/humanize-ms-1.2.1.tgz", + "integrity": "sha1-xG4xWaKT9riW2ikxbYtv6Lt5u+0=", + "dev": true, + "requires": { + "ms": "^2.0.0" + } + }, + "iconv-lite": { + "version": "0.4.23", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.23.tgz", + "integrity": "sha512-neyTUVFtahjf0mB3dZT77u+8O0QB89jFdnBkd5P1JgYPbPaia3gXXOVL2fq8VyU2gMMD7SaN7QukTB/pmXYvDA==", + "dev": true, + "requires": { + "safer-buffer": ">= 2.1.2 < 3" + } + }, + "ieee754": { + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.13.tgz", + "integrity": "sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg==", + "dev": true + }, + "iferr": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/iferr/-/iferr-0.1.5.tgz", + "integrity": "sha1-xg7taebY/bazEEofy8ocGS3FtQE=", + "dev": true + }, + "ignore": { + "version": "3.3.10", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-3.3.10.tgz", + "integrity": "sha512-Pgs951kaMm5GXP7MOvxERINe3gsaVjUWFm+UZPSq9xYriQAksyhg0csnS0KXSNRD5NmNdapXEpjxG49+AKh/ug==", + "dev": true + }, + "ignore-walk": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/ignore-walk/-/ignore-walk-3.0.1.tgz", + "integrity": "sha512-DTVlMx3IYPe0/JJcYP7Gxg7ttZZu3IInhuEhbchuqneY9wWe5Ojy2mXLBaQFUQmo0AW2r3qG7m1mg86js+gnlQ==", + "dev": true, + "requires": { + "minimatch": "^3.0.4" + } + }, + "image-size": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/image-size/-/image-size-0.5.5.tgz", + "integrity": "sha1-Cd/Uq50g4p6xw+gLiZA3jfnjy5w=", + "dev": true, + "optional": true + }, + "immediate": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz", + "integrity": "sha1-nbHb0Pr43m++D13V5Wu2BigN5ps=", + "dev": true + }, + "import-cwd": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/import-cwd/-/import-cwd-2.1.0.tgz", + "integrity": "sha1-qmzzbnInYShcs3HsZRn1PiQ1sKk=", + "dev": true, + "requires": { + "import-from": "^2.1.0" + } + }, + "import-from": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/import-from/-/import-from-2.1.0.tgz", + "integrity": "sha1-M1238qev/VOqpHHUuAId7ja387E=", + "dev": true, + "requires": { + "resolve-from": "^3.0.0" + } + }, + "import-local": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/import-local/-/import-local-2.0.0.tgz", + "integrity": "sha512-b6s04m3O+s3CGSbqDIyP4R6aAwAeYlVq9+WUWep6iHa8ETRf9yei1U48C5MmfJmV9AiLYYBKPMq/W+/WRpQmCQ==", + "dev": true, + "requires": { + "pkg-dir": "^3.0.0", + "resolve-cwd": "^2.0.0" + }, + "dependencies": { + "find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "dev": true, + "requires": { + "locate-path": "^3.0.0" + } + }, + "locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "dev": true, + "requires": { + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" + } + }, + "p-limit": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.2.0.tgz", + "integrity": "sha512-pZbTJpoUsCzV48Mc9Nh51VbwO0X9cuPFE8gYwx9BTCt9SF8/b7Zljd2fVgOxhIF/HDTKgpVzs+GPhyKfjLLFRQ==", + "dev": true, + "requires": { + "p-try": "^2.0.0" + } + }, + "p-locate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "dev": true, + "requires": { + "p-limit": "^2.0.0" + } + }, + "p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true + }, + "pkg-dir": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-3.0.0.tgz", + "integrity": "sha512-/E57AYkoeQ25qkxMj5PBOVgF8Kiu/h7cYS30Z5+R7WaiCCBfLq58ZI/dSeaEKb9WVJV5n/03QwrN3IeWIFllvw==", + "dev": true, + "requires": { + "find-up": "^3.0.0" + } + } + } + }, + "imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", + "dev": true + }, + "in-publish": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/in-publish/-/in-publish-2.0.0.tgz", + "integrity": "sha1-4g/146KvwmkDILbcVSaCqcf631E=", + "dev": true, + "optional": true + }, + "indent-string": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-2.1.0.tgz", + "integrity": "sha1-ji1INIdCEhtKghi3oTfppSBJ3IA=", + "dev": true, + "optional": true, + "requires": { + "repeating": "^2.0.0" + } + }, + "indexof": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/indexof/-/indexof-0.0.1.tgz", + "integrity": "sha1-gtwzbSMrkGIXnQWrMpOmYFn9Q10=", + "dev": true + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "dev": true, + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", + "dev": true + }, + "ini": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz", + "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==", + "dev": true + }, + "inquirer": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-6.2.1.tgz", + "integrity": "sha512-088kl3DRT2dLU5riVMKKr1DlImd6X7smDhpXUCkJDCKvTEJeRiXh0G132HG9u5a+6Ylw9plFRY7RuTnwohYSpg==", + "dev": true, + "requires": { + "ansi-escapes": "^3.0.0", + "chalk": "^2.0.0", + "cli-cursor": "^2.1.0", + "cli-width": "^2.0.0", + "external-editor": "^3.0.0", + "figures": "^2.0.0", + "lodash": "^4.17.10", + "mute-stream": "0.0.7", + "run-async": "^2.2.0", + "rxjs": "^6.1.0", + "string-width": "^2.1.0", + "strip-ansi": "^5.0.0", + "through": "^2.3.6" + }, + "dependencies": { + "ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "dev": true + }, + "string-width": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", + "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "dev": true, + "requires": { + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^4.0.0" + }, + "dependencies": { + "strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "dev": true, + "requires": { + "ansi-regex": "^3.0.0" + } + } + } + }, + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dev": true, + "requires": { + "ansi-regex": "^4.1.0" + }, + "dependencies": { + "ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "dev": true + } + } + } + } + }, + "internal-ip": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/internal-ip/-/internal-ip-3.0.1.tgz", + "integrity": "sha512-NXXgESC2nNVtU+pqmC9e6R8B1GpKxzsAQhffvh5AL79qKnodd+L7tnEQmTiUAVngqLalPbSqRA7XGIEL5nCd0Q==", + "dev": true, + "requires": { + "default-gateway": "^2.6.0", + "ipaddr.js": "^1.5.2" + } + }, + "interpret": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.2.0.tgz", + "integrity": "sha512-mT34yGKMNceBQUoVn7iCDKDntA7SC6gycMAWzGx1z/CMCTV7b2AAtXlo3nRyHZ1FelRkQbQjprHSYGwzLtkVbw==", + "dev": true + }, + "invariant": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", + "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==", + "dev": true, + "requires": { + "loose-envify": "^1.0.0" + } + }, + "invert-kv": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-1.0.0.tgz", + "integrity": "sha1-EEqOSqym09jNFXqO+L+rLXo//bY=", + "dev": true + }, + "ip": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/ip/-/ip-1.1.5.tgz", + "integrity": "sha1-vd7XARQpCCjAoDnnLvJfWq7ENUo=", + "dev": true + }, + "ip-regex": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/ip-regex/-/ip-regex-2.1.0.tgz", + "integrity": "sha1-+ni/XS5pE8kRzp+BnuUUa7bYROk=", + "dev": true + }, + "ipaddr.js": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.8.0.tgz", + "integrity": "sha1-6qM9bd16zo9/b+DJygRA5wZzix4=", + "dev": true + }, + "is-accessor-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", + "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=", + "dev": true + }, + "is-binary-path": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-1.0.1.tgz", + "integrity": "sha1-dfFmQrSA8YenEcgUFh/TpKdlWJg=", + "dev": true, + "requires": { + "binary-extensions": "^1.0.0" + } + }, + "is-buffer": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", + "dev": true + }, + "is-data-descriptor": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", + "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "is-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", + "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "^0.1.6", + "is-data-descriptor": "^0.1.4", + "kind-of": "^5.0.0" + }, + "dependencies": { + "kind-of": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", + "dev": true + } + } + }, + "is-directory": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/is-directory/-/is-directory-0.3.1.tgz", + "integrity": "sha1-YTObbyR1/Hcv2cnYP1yFddwVSuE=", + "dev": true + }, + "is-extendable": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", + "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=", + "dev": true + }, + "is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", + "dev": true + }, + "is-finite": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-finite/-/is-finite-1.0.2.tgz", + "integrity": "sha1-zGZ3aVYCvlUO8R6LSqYwU0K20Ko=", + "dev": true, + "requires": { + "number-is-nan": "^1.0.0" + } + }, + "is-fullwidth-code-point": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", + "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", + "dev": true, + "requires": { + "number-is-nan": "^1.0.0" + } + }, + "is-glob": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", + "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==", + "dev": true, + "requires": { + "is-extglob": "^2.1.1" + } + }, + "is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "is-path-cwd": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-path-cwd/-/is-path-cwd-1.0.0.tgz", + "integrity": "sha1-0iXsIxMuie3Tj9p2dHLmLmXxEG0=", + "dev": true + }, + "is-path-in-cwd": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-path-in-cwd/-/is-path-in-cwd-1.0.1.tgz", + "integrity": "sha512-FjV1RTW48E7CWM7eE/J2NJvAEEVektecDBVBE5Hh3nM1Jd0kvhHtX68Pr3xsDf857xt3Y4AkwVULK1Vku62aaQ==", + "dev": true, + "requires": { + "is-path-inside": "^1.0.0" + } + }, + "is-path-inside": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-1.0.1.tgz", + "integrity": "sha1-jvW33lBDej/cprToZe96pVy0gDY=", + "dev": true, + "requires": { + "path-is-inside": "^1.0.1" + } + }, + "is-plain-object": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", + "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", + "dev": true, + "requires": { + "isobject": "^3.0.1" + } + }, + "is-promise": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.1.0.tgz", + "integrity": "sha1-eaKp7OfwlugPNtKy87wWwf9L8/o=", + "dev": true + }, + "is-stream": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", + "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=", + "dev": true + }, + "is-typedarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=", + "dev": true + }, + "is-utf8": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-utf8/-/is-utf8-0.2.1.tgz", + "integrity": "sha1-Sw2hRCEE0bM2NA6AeX6GXPOffXI=", + "dev": true, + "optional": true + }, + "is-windows": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", + "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==", + "dev": true + }, + "is-wsl": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-1.1.0.tgz", + "integrity": "sha1-HxbkqiKwTRM2tmGIpmrzxgDDpm0=", + "dev": true + }, + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "dev": true + }, + "isbinaryfile": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/isbinaryfile/-/isbinaryfile-3.0.3.tgz", + "integrity": "sha512-8cJBL5tTd2OS0dM4jz07wQd5g0dCCqIhUxPIGtZfa5L6hWlvV5MHTITy/DBAsF+Oe2LS1X3krBUhNwaGUWpWxw==", + "dev": true, + "requires": { + "buffer-alloc": "^1.2.0" + } + }, + "isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", + "dev": true + }, + "isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", + "dev": true + }, + "isstream": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", + "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=", + "dev": true + }, + "istanbul-api": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/istanbul-api/-/istanbul-api-2.1.1.tgz", + "integrity": "sha512-kVmYrehiwyeBAk/wE71tW6emzLiHGjYIiDrc8sfyty4F8M02/lrgXSm+R1kXysmF20zArvmZXjlE/mg24TVPJw==", + "dev": true, + "requires": { + "async": "^2.6.1", + "compare-versions": "^3.2.1", + "fileset": "^2.0.3", + "istanbul-lib-coverage": "^2.0.3", + "istanbul-lib-hook": "^2.0.3", + "istanbul-lib-instrument": "^3.1.0", + "istanbul-lib-report": "^2.0.4", + "istanbul-lib-source-maps": "^3.0.2", + "istanbul-reports": "^2.1.1", + "js-yaml": "^3.12.0", + "make-dir": "^1.3.0", + "minimatch": "^3.0.4", + "once": "^1.4.0" + }, + "dependencies": { + "istanbul-lib-coverage": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.3.tgz", + "integrity": "sha512-dKWuzRGCs4G+67VfW9pBFFz2Jpi4vSp/k7zBcJ888ofV5Mi1g5CUML5GvMvV6u9Cjybftu+E8Cgp+k0dI1E5lw==", + "dev": true + }, + "istanbul-lib-instrument": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-3.1.0.tgz", + "integrity": "sha512-ooVllVGT38HIk8MxDj/OIHXSYvH+1tq/Vb38s8ixt9GoJadXska4WkGY+0wkmtYCZNYtaARniH/DixUGGLZ0uA==", + "dev": true, + "requires": { + "@babel/generator": "^7.0.0", + "@babel/parser": "^7.0.0", + "@babel/template": "^7.0.0", + "@babel/traverse": "^7.0.0", + "@babel/types": "^7.0.0", + "istanbul-lib-coverage": "^2.0.3", + "semver": "^5.5.0" + } + } + } + }, + "istanbul-instrumenter-loader": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/istanbul-instrumenter-loader/-/istanbul-instrumenter-loader-3.0.1.tgz", + "integrity": "sha512-a5SPObZgS0jB/ixaKSMdn6n/gXSrK2S6q/UfRJBT3e6gQmVjwZROTODQsYW5ZNwOu78hG62Y3fWlebaVOL0C+w==", + "dev": true, + "requires": { + "convert-source-map": "^1.5.0", + "istanbul-lib-instrument": "^1.7.3", + "loader-utils": "^1.1.0", + "schema-utils": "^0.3.0" + }, + "dependencies": { + "ajv": { + "version": "5.5.2", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-5.5.2.tgz", + "integrity": "sha1-c7Xuyj+rZT49P5Qis0GtQiBdyWU=", + "dev": true, + "requires": { + "co": "^4.6.0", + "fast-deep-equal": "^1.0.0", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.3.0" + } + }, + "fast-deep-equal": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-1.1.0.tgz", + "integrity": "sha1-wFNHeBfIa1HaqFPIHgWbcz0CNhQ=", + "dev": true + }, + "json-schema-traverse": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.3.1.tgz", + "integrity": "sha1-NJptRMU6Ud6JtAgFxdXlm0F9M0A=", + "dev": true + }, + "schema-utils": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-0.3.0.tgz", + "integrity": "sha1-9YdyIs4+kx7a4DnxfrNxbnE3+M8=", + "dev": true, + "requires": { + "ajv": "^5.0.0" + } + } + } + }, + "istanbul-lib-coverage": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-1.2.1.tgz", + "integrity": "sha512-PzITeunAgyGbtY1ibVIUiV679EFChHjoMNRibEIobvmrCRaIgwLxNucOSimtNWUhEib/oO7QY2imD75JVgCJWQ==", + "dev": true + }, + "istanbul-lib-hook": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/istanbul-lib-hook/-/istanbul-lib-hook-2.0.3.tgz", + "integrity": "sha512-CLmEqwEhuCYtGcpNVJjLV1DQyVnIqavMLFHV/DP+np/g3qvdxu3gsPqYoJMXm15sN84xOlckFB3VNvRbf5yEgA==", + "dev": true, + "requires": { + "append-transform": "^1.0.0" + } + }, + "istanbul-lib-instrument": { + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-1.10.2.tgz", + "integrity": "sha512-aWHxfxDqvh/ZlxR8BBaEPVSWDPUkGD63VjGQn3jcw8jCp7sHEMKcrj4xfJn/ABzdMEHiQNyvDQhqm5o8+SQg7A==", + "dev": true, + "requires": { + "babel-generator": "^6.18.0", + "babel-template": "^6.16.0", + "babel-traverse": "^6.18.0", + "babel-types": "^6.18.0", + "babylon": "^6.18.0", + "istanbul-lib-coverage": "^1.2.1", + "semver": "^5.3.0" + } + }, + "istanbul-lib-report": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-2.0.4.tgz", + "integrity": "sha512-sOiLZLAWpA0+3b5w5/dq0cjm2rrNdAfHWaGhmn7XEFW6X++IV9Ohn+pnELAl9K3rfpaeBfbmH9JU5sejacdLeA==", + "dev": true, + "requires": { + "istanbul-lib-coverage": "^2.0.3", + "make-dir": "^1.3.0", + "supports-color": "^6.0.0" + }, + "dependencies": { + "istanbul-lib-coverage": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.3.tgz", + "integrity": "sha512-dKWuzRGCs4G+67VfW9pBFFz2Jpi4vSp/k7zBcJ888ofV5Mi1g5CUML5GvMvV6u9Cjybftu+E8Cgp+k0dI1E5lw==", + "dev": true + } + } + }, + "istanbul-lib-source-maps": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-3.0.2.tgz", + "integrity": "sha512-JX4v0CiKTGp9fZPmoxpu9YEkPbEqCqBbO3403VabKjH+NRXo72HafD5UgnjTEqHL2SAjaZK1XDuDOkn6I5QVfQ==", + "dev": true, + "requires": { + "debug": "^4.1.1", + "istanbul-lib-coverage": "^2.0.3", + "make-dir": "^1.3.0", + "rimraf": "^2.6.2", + "source-map": "^0.6.1" + }, + "dependencies": { + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "istanbul-lib-coverage": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.3.tgz", + "integrity": "sha512-dKWuzRGCs4G+67VfW9pBFFz2Jpi4vSp/k7zBcJ888ofV5Mi1g5CUML5GvMvV6u9Cjybftu+E8Cgp+k0dI1E5lw==", + "dev": true + }, + "ms": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", + "dev": true + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + } + } + }, + "istanbul-reports": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-2.1.1.tgz", + "integrity": "sha512-FzNahnidyEPBCI0HcufJoSEoKykesRlFcSzQqjH9x0+LC8tnnE/p/90PBLu8iZTxr8yYZNyTtiAujUqyN+CIxw==", + "dev": true, + "requires": { + "handlebars": "^4.1.0" + } + }, + "jasmine": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/jasmine/-/jasmine-2.8.0.tgz", + "integrity": "sha1-awicChFXax8W3xG4AUbZHU6Lij4=", + "dev": true, + "requires": { + "exit": "^0.1.2", + "glob": "^7.0.6", + "jasmine-core": "~2.8.0" + }, + "dependencies": { + "jasmine-core": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/jasmine-core/-/jasmine-core-2.8.0.tgz", + "integrity": "sha1-vMl5rh+f0FcB5F5S5l06XWPxok4=", + "dev": true + } + } + }, + "jasmine-core": { + "version": "2.99.1", + "resolved": "https://registry.npmjs.org/jasmine-core/-/jasmine-core-2.99.1.tgz", + "integrity": "sha1-5kAN8ea1bhMLYcS80JPap/boyhU=", + "dev": true + }, + "jasmine-spec-reporter": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/jasmine-spec-reporter/-/jasmine-spec-reporter-4.2.1.tgz", + "integrity": "sha512-FZBoZu7VE5nR7Nilzy+Np8KuVIOxF4oXDPDknehCYBDE080EnlPu0afdZNmpGDBRCUBv3mj5qgqCRmk6W/K8vg==", + "dev": true, + "requires": { + "colors": "1.1.2" + } + }, + "jasminewd2": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/jasminewd2/-/jasminewd2-2.2.0.tgz", + "integrity": "sha1-43zwsX8ZnM4jvqcbIDk5Uka07E4=", + "dev": true + }, + "js-base64": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/js-base64/-/js-base64-2.5.1.tgz", + "integrity": "sha512-M7kLczedRMYX4L8Mdh4MzyAMM9O5osx+4FcOQuTvr3A9F2D9S5JXheN0ewNbrvK2UatkTRhL5ejGmGSjNMiZuw==", + "dev": true, + "optional": true + }, + "js-tokens": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.2.tgz", + "integrity": "sha1-mGbfOVECEw449/mWvOtlRDIJwls=", + "dev": true + }, + "js-yaml": { + "version": "3.13.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.0.tgz", + "integrity": "sha512-pZZoSxcCYco+DIKBTimr67J6Hy+EYGZDY/HCWC+iAEA9h1ByhMXAIVUXMcMFpOCxQ/xjXmPI2MkDL5HRm5eFrQ==", + "dev": true, + "requires": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + } + }, + "jsbn": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", + "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=", + "dev": true + }, + "jsesc": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-1.3.0.tgz", + "integrity": "sha1-RsP+yMGJKxKwgz25vHYiF226s0s=", + "dev": true + }, + "json-parse-better-errors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", + "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==", + "dev": true + }, + "json-schema": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", + "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=", + "dev": true + }, + "json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "json-stringify-safe": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=", + "dev": true + }, + "json3": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/json3/-/json3-3.3.2.tgz", + "integrity": "sha1-PAQ0dD35Pi9cQq7nsZvLSDV19OE=", + "dev": true + }, + "json5": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", + "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", + "dev": true, + "requires": { + "minimist": "^1.2.0" + } + }, + "jsonfile": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", + "integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=", + "dev": true, + "requires": { + "graceful-fs": "^4.1.6" + } + }, + "jsonparse": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/jsonparse/-/jsonparse-1.3.1.tgz", + "integrity": "sha1-P02uSpH6wxX3EGL4UhzCOfE2YoA=", + "dev": true + }, + "jsprim": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", + "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=", + "dev": true, + "requires": { + "assert-plus": "1.0.0", + "extsprintf": "1.3.0", + "json-schema": "0.2.3", + "verror": "1.10.0" + } + }, + "jszip": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.2.1.tgz", + "integrity": "sha512-iCMBbo4eE5rb1VCpm5qXOAaUiRKRUKiItn8ah2YQQx9qymmSAY98eyQfioChEYcVQLh0zxJ3wS4A0mh90AVPvw==", + "dev": true, + "requires": { + "lie": "~3.3.0", + "pako": "~1.0.2", + "readable-stream": "~2.3.6", + "set-immediate-shim": "~1.0.1" + } + }, + "karma": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/karma/-/karma-4.0.1.tgz", + "integrity": "sha512-ind+4s03BqIXas7ZmraV3/kc5+mnqwCd+VDX1FndS6jxbt03kQKX2vXrWxNLuCjVYmhMwOZosAEKMM0a2q7w7A==", + "dev": true, + "requires": { + "bluebird": "^3.3.0", + "body-parser": "^1.16.1", + "braces": "^2.3.2", + "chokidar": "^2.0.3", + "colors": "^1.1.0", + "connect": "^3.6.0", + "core-js": "^2.2.0", + "di": "^0.0.1", + "dom-serialize": "^2.2.0", + "flatted": "^2.0.0", + "glob": "^7.1.1", + "graceful-fs": "^4.1.2", + "http-proxy": "^1.13.0", + "isbinaryfile": "^3.0.0", + "lodash": "^4.17.11", + "log4js": "^4.0.0", + "mime": "^2.3.1", + "minimatch": "^3.0.2", + "optimist": "^0.6.1", + "qjobs": "^1.1.4", + "range-parser": "^1.2.0", + "rimraf": "^2.6.0", + "safe-buffer": "^5.0.1", + "socket.io": "2.1.1", + "source-map": "^0.6.1", + "tmp": "0.0.33", + "useragent": "2.3.0" + }, + "dependencies": { + "mime": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-2.4.0.tgz", + "integrity": "sha512-ikBcWwyqXQSHKtciCcctu9YfPbFYZ4+gbHEmE0Q8jzcTYQg5dHCr3g2wwAZjPoJfQVXZq6KXAjpXOTf5/cjT7w==", + "dev": true + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + } + } + }, + "karma-chrome-launcher": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/karma-chrome-launcher/-/karma-chrome-launcher-2.2.0.tgz", + "integrity": "sha512-uf/ZVpAabDBPvdPdveyk1EPgbnloPvFFGgmRhYLTDH7gEB4nZdSBk8yTU47w1g/drLSx5uMOkjKk7IWKfWg/+w==", + "dev": true, + "requires": { + "fs-access": "^1.0.0", + "which": "^1.2.1" + } + }, + "karma-coverage-istanbul-reporter": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/karma-coverage-istanbul-reporter/-/karma-coverage-istanbul-reporter-2.0.5.tgz", + "integrity": "sha512-yPvAlKtY3y+rKKWbOo0CzBMVTvJEeMOgbMXuVv3yWvS8YtYKC98AU9vFF0mVBZ2RP1E9SgS90+PT6Kf14P3S4w==", + "dev": true, + "requires": { + "istanbul-api": "^2.1.1", + "minimatch": "^3.0.4" + } + }, + "karma-jasmine": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/karma-jasmine/-/karma-jasmine-1.1.2.tgz", + "integrity": "sha1-OU8rJf+0pkS5rabyLUQ+L9CIhsM=", + "dev": true + }, + "karma-jasmine-html-reporter": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/karma-jasmine-html-reporter/-/karma-jasmine-html-reporter-0.2.2.tgz", + "integrity": "sha1-SKjl7xiAdhfuK14zwRlMNbQ5Ukw=", + "dev": true, + "requires": { + "karma-jasmine": "^1.0.2" + } + }, + "karma-source-map-support": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/karma-source-map-support/-/karma-source-map-support-1.3.0.tgz", + "integrity": "sha512-HcPqdAusNez/ywa+biN4EphGz62MmQyPggUsDfsHqa7tSe4jdsxgvTKuDfIazjL+IOxpVWyT7Pr4dhAV+sxX5Q==", + "dev": true, + "requires": { + "source-map-support": "^0.5.5" + } + }, + "killable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/killable/-/killable-1.0.1.tgz", + "integrity": "sha512-LzqtLKlUwirEUyl/nicirVmNiPvYs7l5n8wOPP7fyJVpUPkvCnW/vuiXGpylGUlnPDnB7311rARzAt3Mhswpjg==", + "dev": true + }, + "kind-of": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", + "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", + "dev": true + }, + "lcid": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/lcid/-/lcid-1.0.0.tgz", + "integrity": "sha1-MIrMr6C8SDo4Z7S28rlQYlHRuDU=", + "dev": true, + "requires": { + "invert-kv": "^1.0.0" + } + }, + "less": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/less/-/less-3.9.0.tgz", + "integrity": "sha512-31CmtPEZraNUtuUREYjSqRkeETFdyEHSEPAGq4erDlUXtda7pzNmctdljdIagSb589d/qXGWiiP31R5JVf+v0w==", + "dev": true, + "requires": { + "clone": "^2.1.2", + "errno": "^0.1.1", + "graceful-fs": "^4.1.2", + "image-size": "~0.5.0", + "mime": "^1.4.1", + "mkdirp": "^0.5.0", + "promise": "^7.1.1", + "request": "^2.83.0", + "source-map": "~0.6.0" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "optional": true + } + } + }, + "less-loader": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/less-loader/-/less-loader-4.1.0.tgz", + "integrity": "sha512-KNTsgCE9tMOM70+ddxp9yyt9iHqgmSs0yTZc5XH5Wo+g80RWRIYNqE58QJKm/yMud5wZEvz50ugRDuzVIkyahg==", + "dev": true, + "requires": { + "clone": "^2.1.1", + "loader-utils": "^1.1.0", + "pify": "^3.0.0" + } + }, + "license-webpack-plugin": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/license-webpack-plugin/-/license-webpack-plugin-2.1.0.tgz", + "integrity": "sha512-vDiBeMWxjE9n6TabQ9J4FH8urFdsRK0Nvxn1cit9biCiR9aq1zBR0X2BlAkEiIG6qPamLeU0GzvIgLkrFc398A==", + "dev": true, + "requires": { + "@types/webpack-sources": "^0.1.5", + "webpack-sources": "^1.2.0" + } + }, + "lie": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz", + "integrity": "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==", + "dev": true, + "requires": { + "immediate": "~3.0.5" + } + }, + "load-json-file": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz", + "integrity": "sha1-lWkFcI1YtLq0wiYbBPWfMcmTdMA=", + "dev": true, + "optional": true, + "requires": { + "graceful-fs": "^4.1.2", + "parse-json": "^2.2.0", + "pify": "^2.0.0", + "pinkie-promise": "^2.0.0", + "strip-bom": "^2.0.0" + }, + "dependencies": { + "pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", + "dev": true, + "optional": true + } + } + }, + "loader-runner": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-2.4.0.tgz", + "integrity": "sha512-Jsmr89RcXGIwivFY21FcRrisYZfvLMTWx5kOLc+JTxtpBOG6xML0vzbc6SEQG2FO9/4Fc3wW4LVcB5DmGflaRw==", + "dev": true + }, + "loader-utils": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.2.3.tgz", + "integrity": "sha512-fkpz8ejdnEMG3s37wGL07iSBDg99O9D5yflE9RGNH3hRdx9SOwYfnGYdZOUIZitN8E+E2vkq3MUMYMvPYl5ZZA==", + "dev": true, + "requires": { + "big.js": "^5.2.2", + "emojis-list": "^2.0.0", + "json5": "^1.0.1" + } + }, + "locate-path": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", + "integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=", + "dev": true, + "requires": { + "p-locate": "^2.0.0", + "path-exists": "^3.0.0" + } + }, + "lodash": { + "version": "4.17.11", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz", + "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==", + "dev": true + }, + "lodash.assign": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/lodash.assign/-/lodash.assign-4.2.0.tgz", + "integrity": "sha1-DZnzzNem0mHRm9rrkkUAXShYCOc=", + "dev": true, + "optional": true + }, + "lodash.clonedeep": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz", + "integrity": "sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8=", + "dev": true + }, + "lodash.debounce": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", + "integrity": "sha1-gteb/zCmfEAF/9XiUVMArZyk168=", + "dev": true + }, + "lodash.mergewith": { + "version": "4.6.1", + "resolved": "https://registry.npmjs.org/lodash.mergewith/-/lodash.mergewith-4.6.1.tgz", + "integrity": "sha512-eWw5r+PYICtEBgrBE5hhlT6aAa75f411bgDz/ZL2KZqYV03USvucsxcHUIlGTDTECs1eunpI7HOV7U+WLDvNdQ==", + "dev": true, + "optional": true + }, + "lodash.tail": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.tail/-/lodash.tail-4.1.1.tgz", + "integrity": "sha1-0jM6NtnncXyK0vfKyv7HwytERmQ=", + "dev": true + }, + "log4js": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/log4js/-/log4js-4.1.0.tgz", + "integrity": "sha512-eDa+zZPeVEeK6QGJAePyXM6pg4P3n3TO5rX9iZMVY48JshsTyLJZLIL5HipI1kQ2qLsSyOpUqNND/C5H4WhhiA==", + "dev": true, + "requires": { + "date-format": "^2.0.0", + "debug": "^4.1.1", + "flatted": "^2.0.0", + "rfdc": "^1.1.2", + "streamroller": "^1.0.4" + }, + "dependencies": { + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "ms": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", + "dev": true + } + } + }, + "loglevel": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/loglevel/-/loglevel-1.6.1.tgz", + "integrity": "sha1-4PyVEztu8nbNyIh82vJKpvFW+Po=", + "dev": true + }, + "loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "dev": true, + "requires": { + "js-tokens": "^3.0.0 || ^4.0.0" + } + }, + "loud-rejection": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/loud-rejection/-/loud-rejection-1.6.0.tgz", + "integrity": "sha1-W0b4AUft7leIcPCG0Eghz5mOVR8=", + "dev": true, + "optional": true, + "requires": { + "currently-unhandled": "^0.4.1", + "signal-exit": "^3.0.0" + } + }, + "lru-cache": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.5.tgz", + "integrity": "sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==", + "dev": true, + "requires": { + "pseudomap": "^1.0.2", + "yallist": "^2.1.2" + } + }, + "magic-string": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.2.tgz", + "integrity": "sha512-iLs9mPjh9IuTtRsqqhNGYcZXGei0Nh/A4xirrsqW7c+QhKVFL2vm7U09ru6cHRD22azaP/wMDgI+HCqbETMTtg==", + "dev": true, + "requires": { + "sourcemap-codec": "^1.4.4" + } + }, + "make-dir": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-1.3.0.tgz", + "integrity": "sha512-2w31R7SJtieJJnQtGc7RVL2StM2vGYVfqUOvUDxH6bC6aJTxPxTF0GnIgCyu7tjockiUWAYQRbxa7vKn34s5sQ==", + "dev": true, + "requires": { + "pify": "^3.0.0" + } + }, + "make-error": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.5.tgz", + "integrity": "sha512-c3sIjNUow0+8swNwVpqoH4YCShKNFkMaw6oH1mNS2haDZQqkeZFlHS3dhoeEbKKmJB4vXpJucU6oH75aDYeE9g==", + "dev": true + }, + "make-fetch-happen": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-4.0.1.tgz", + "integrity": "sha512-7R5ivfy9ilRJ1EMKIOziwrns9fGeAD4bAha8EB7BIiBBLHm2KeTUGCrICFt2rbHfzheTLynv50GnNTK1zDTrcQ==", + "dev": true, + "requires": { + "agentkeepalive": "^3.4.1", + "cacache": "^11.0.1", + "http-cache-semantics": "^3.8.1", + "http-proxy-agent": "^2.1.0", + "https-proxy-agent": "^2.2.1", + "lru-cache": "^4.1.2", + "mississippi": "^3.0.0", + "node-fetch-npm": "^2.0.2", + "promise-retry": "^1.1.1", + "socks-proxy-agent": "^4.0.0", + "ssri": "^6.0.0" + }, + "dependencies": { + "cacache": { + "version": "11.3.2", + "resolved": "https://registry.npmjs.org/cacache/-/cacache-11.3.2.tgz", + "integrity": "sha512-E0zP4EPGDOaT2chM08Als91eYnf8Z+eH1awwwVsngUmgppfM5jjJ8l3z5vO5p5w/I3LsiXawb1sW0VY65pQABg==", + "dev": true, + "requires": { + "bluebird": "^3.5.3", + "chownr": "^1.1.1", + "figgy-pudding": "^3.5.1", + "glob": "^7.1.3", + "graceful-fs": "^4.1.15", + "lru-cache": "^5.1.1", + "mississippi": "^3.0.0", + "mkdirp": "^0.5.1", + "move-concurrently": "^1.0.1", + "promise-inflight": "^1.0.1", + "rimraf": "^2.6.2", + "ssri": "^6.0.1", + "unique-filename": "^1.1.1", + "y18n": "^4.0.0" + }, + "dependencies": { + "lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "requires": { + "yallist": "^3.0.2" + } + } + } + }, + "mississippi": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/mississippi/-/mississippi-3.0.0.tgz", + "integrity": "sha512-x471SsVjUtBRtcvd4BzKE9kFC+/2TeWgKCgw0bZcw1b9l2X3QX5vCWgF+KaZaYm87Ss//rHnWryupDrgLvmSkA==", + "dev": true, + "requires": { + "concat-stream": "^1.5.0", + "duplexify": "^3.4.2", + "end-of-stream": "^1.1.0", + "flush-write-stream": "^1.0.0", + "from2": "^2.1.0", + "parallel-transform": "^1.1.0", + "pump": "^3.0.0", + "pumpify": "^1.3.3", + "stream-each": "^1.1.0", + "through2": "^2.0.0" + } + }, + "pump": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "dev": true, + "requires": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "ssri": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/ssri/-/ssri-6.0.1.tgz", + "integrity": "sha512-3Wge10hNcT1Kur4PDFwEieXSCMCJs/7WvSACcrMYrNp+b8kDL1/0wJch5Ni2WrtwEa2IO8OsVfeKIciKCDx/QA==", + "dev": true, + "requires": { + "figgy-pudding": "^3.5.1" + } + }, + "yallist": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.0.3.tgz", + "integrity": "sha512-S+Zk8DEWE6oKpV+vI3qWkaK+jSbIK86pCwe2IF/xwIpQ8jEuxpw9NyaGjmp9+BoJv5FV2piqCDcoCtStppiq2A==", + "dev": true + } + } + }, + "map-age-cleaner": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/map-age-cleaner/-/map-age-cleaner-0.1.3.tgz", + "integrity": "sha512-bJzx6nMoP6PDLPBFmg7+xRKeFZvFboMrGlxmNj9ClvX53KrmvM5bXFXEWjbz4cz1AFn+jWJ9z/DJSz7hrs0w3w==", + "dev": true, + "requires": { + "p-defer": "^1.0.0" + } + }, + "map-cache": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/map-cache/-/map-cache-0.2.2.tgz", + "integrity": "sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8=", + "dev": true + }, + "map-obj": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-1.0.1.tgz", + "integrity": "sha1-2TPOuSBdgr3PSIb2dCvcK03qFG0=", + "dev": true, + "optional": true + }, + "map-visit": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/map-visit/-/map-visit-1.0.0.tgz", + "integrity": "sha1-7Nyo8TFE5mDxtb1B8S80edmN+48=", + "dev": true, + "requires": { + "object-visit": "^1.0.0" + } + }, + "md5.js": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz", + "integrity": "sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg==", + "dev": true, + "requires": { + "hash-base": "^3.0.0", + "inherits": "^2.0.1", + "safe-buffer": "^5.1.2" + } + }, + "media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=", + "dev": true + }, + "mem": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/mem/-/mem-4.3.0.tgz", + "integrity": "sha512-qX2bG48pTqYRVmDB37rn/6PT7LcR8T7oAX3bf99u1Tt1nzxYfxkgqDwUwolPlXweM0XzBOBFzSx4kfp7KP1s/w==", + "dev": true, + "requires": { + "map-age-cleaner": "^0.1.1", + "mimic-fn": "^2.0.0", + "p-is-promise": "^2.0.0" + } + }, + "memory-fs": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/memory-fs/-/memory-fs-0.4.1.tgz", + "integrity": "sha1-OpoguEYlI+RHz7x+i7gO1me/xVI=", + "dev": true, + "requires": { + "errno": "^0.1.3", + "readable-stream": "^2.0.1" + } + }, + "meow": { + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/meow/-/meow-3.7.0.tgz", + "integrity": "sha1-cstmi0JSKCkKu/qFaJJYcwioAfs=", + "dev": true, + "optional": true, + "requires": { + "camelcase-keys": "^2.0.0", + "decamelize": "^1.1.2", + "loud-rejection": "^1.0.0", + "map-obj": "^1.0.1", + "minimist": "^1.1.3", + "normalize-package-data": "^2.3.4", + "object-assign": "^4.0.1", + "read-pkg-up": "^1.0.1", + "redent": "^1.0.0", + "trim-newlines": "^1.0.0" + } + }, + "merge-descriptors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", + "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=", + "dev": true + }, + "methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=", + "dev": true + }, + "micromatch": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", + "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", + "dev": true, + "requires": { + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "braces": "^2.3.1", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "extglob": "^2.0.4", + "fragment-cache": "^0.2.1", + "kind-of": "^6.0.2", + "nanomatch": "^1.2.9", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.2" + } + }, + "miller-rabin": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/miller-rabin/-/miller-rabin-4.0.1.tgz", + "integrity": "sha512-115fLhvZVqWwHPbClyntxEVfVDfl9DLLTuJvq3g2O/Oxi8AiNouAHvDSzHS0viUJc+V5vm3eq91Xwqn9dp4jRA==", + "dev": true, + "requires": { + "bn.js": "^4.0.0", + "brorand": "^1.0.1" + } + }, + "mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "dev": true, + "optional": true + }, + "mime-db": { + "version": "1.38.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.38.0.tgz", + "integrity": "sha512-bqVioMFFzc2awcdJZIzR3HjZFX20QhilVS7hytkKrv7xFAn8bM1gzc/FOX2awLISvWe0PV8ptFKcon+wZ5qYkg==", + "dev": true + }, + "mime-types": { + "version": "2.1.22", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.22.tgz", + "integrity": "sha512-aGl6TZGnhm/li6F7yx82bJiBZwgiEa4Hf6CNr8YO+r5UHr53tSTYZb102zyU50DOWWKeOv0uQLRL0/9EiKWCog==", + "dev": true, + "requires": { + "mime-db": "~1.38.0" + } + }, + "mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true + }, + "mini-css-extract-plugin": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-0.5.0.tgz", + "integrity": "sha512-IuaLjruM0vMKhUUT51fQdQzBYTX49dLj8w68ALEAe2A4iYNpIC4eMac67mt3NzycvjOlf07/kYxJDc0RTl1Wqw==", + "dev": true, + "requires": { + "loader-utils": "^1.1.0", + "schema-utils": "^1.0.0", + "webpack-sources": "^1.1.0" + } + }, + "minimalistic-assert": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", + "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==", + "dev": true + }, + "minimalistic-crypto-utils": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz", + "integrity": "sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo=", + "dev": true + }, + "minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "minimist": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", + "dev": true + }, + "minipass": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-2.3.5.tgz", + "integrity": "sha512-Gi1W4k059gyRbyVUZQ4mEqLm0YIUiGYfvxhF6SIlk3ui1WVxMTGfGdQ2SInh3PDrRTVvPKgULkpJtT4RH10+VA==", + "dev": true, + "requires": { + "safe-buffer": "^5.1.2", + "yallist": "^3.0.0" + }, + "dependencies": { + "yallist": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.0.3.tgz", + "integrity": "sha512-S+Zk8DEWE6oKpV+vI3qWkaK+jSbIK86pCwe2IF/xwIpQ8jEuxpw9NyaGjmp9+BoJv5FV2piqCDcoCtStppiq2A==", + "dev": true + } + } + }, + "minizlib": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-1.2.1.tgz", + "integrity": "sha512-7+4oTUOWKg7AuL3vloEWekXY2/D20cevzsrNT2kGWm+39J9hGTCBv8VI5Pm5lXZ/o3/mdR4f8rflAPhnQb8mPA==", + "dev": true, + "requires": { + "minipass": "^2.2.1" + } + }, + "mississippi": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mississippi/-/mississippi-2.0.0.tgz", + "integrity": "sha512-zHo8v+otD1J10j/tC+VNoGK9keCuByhKovAvdn74dmxJl9+mWHnx6EMsDN4lgRoMI/eYo2nchAxniIbUPb5onw==", + "dev": true, + "requires": { + "concat-stream": "^1.5.0", + "duplexify": "^3.4.2", + "end-of-stream": "^1.1.0", + "flush-write-stream": "^1.0.0", + "from2": "^2.1.0", + "parallel-transform": "^1.1.0", + "pump": "^2.0.1", + "pumpify": "^1.3.3", + "stream-each": "^1.1.0", + "through2": "^2.0.0" + } + }, + "mixin-deep": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.1.tgz", + "integrity": "sha512-8ZItLHeEgaqEvd5lYBXfm4EZSFCX29Jb9K+lAHhDKzReKBQKj3R+7NOF6tjqYi9t4oI8VUfaWITJQm86wnXGNQ==", + "dev": true, + "requires": { + "for-in": "^1.0.2", + "is-extendable": "^1.0.1" + }, + "dependencies": { + "is-extendable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", + "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", + "dev": true, + "requires": { + "is-plain-object": "^2.0.4" + } + } + } + }, + "mixin-object": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mixin-object/-/mixin-object-2.0.1.tgz", + "integrity": "sha1-T7lJRB2rGCVA8f4DW6YOGUel5X4=", + "dev": true, + "requires": { + "for-in": "^0.1.3", + "is-extendable": "^0.1.1" + }, + "dependencies": { + "for-in": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/for-in/-/for-in-0.1.8.tgz", + "integrity": "sha1-2Hc5COMSVhCZUrH9ubP6hn0ndeE=", + "dev": true + } + } + }, + "mkdirp": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", + "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", + "dev": true, + "requires": { + "minimist": "0.0.8" + }, + "dependencies": { + "minimist": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", + "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", + "dev": true + } + } + }, + "move-concurrently": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/move-concurrently/-/move-concurrently-1.0.1.tgz", + "integrity": "sha1-viwAX9oy4LKa8fBdfEszIUxwH5I=", + "dev": true, + "requires": { + "aproba": "^1.1.1", + "copy-concurrently": "^1.0.0", + "fs-write-stream-atomic": "^1.0.8", + "mkdirp": "^0.5.1", + "rimraf": "^2.5.4", + "run-queue": "^1.0.3" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + }, + "multicast-dns": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/multicast-dns/-/multicast-dns-6.2.3.tgz", + "integrity": "sha512-ji6J5enbMyGRHIAkAOu3WdV8nggqviKCEKtXcOqfphZZtQrmHKycfynJ2V7eVPUA4NhJ6V7Wf4TmGbTwKE9B6g==", + "dev": true, + "requires": { + "dns-packet": "^1.3.1", + "thunky": "^1.0.2" + } + }, + "multicast-dns-service-types": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/multicast-dns-service-types/-/multicast-dns-service-types-1.1.0.tgz", + "integrity": "sha1-iZ8R2WhuXgXLkbNdXw5jt3PPyQE=", + "dev": true + }, + "mute-stream": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.7.tgz", + "integrity": "sha1-MHXOk7whuPq0PhvE2n6BFe0ee6s=", + "dev": true + }, + "nan": { + "version": "2.13.2", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.13.2.tgz", + "integrity": "sha512-TghvYc72wlMGMVMluVo9WRJc0mB8KxxF/gZ4YYFy7V2ZQX9l7rgbPg7vjS9mt6U5HXODVFVI2bOduCzwOMv/lw==", + "dev": true, + "optional": true + }, + "nanomatch": { + "version": "1.2.13", + "resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.13.tgz", + "integrity": "sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA==", + "dev": true, + "requires": { + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "fragment-cache": "^0.2.1", + "is-windows": "^1.0.2", + "kind-of": "^6.0.2", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + } + }, + "negotiator": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.1.tgz", + "integrity": "sha1-KzJxhOiZIQEXeyhWP7XnECrNDKk=", + "dev": true + }, + "neo-async": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.0.tgz", + "integrity": "sha512-MFh0d/Wa7vkKO3Y3LlacqAEeHK0mckVqzDieUKTT+KGxi+zIpeVsFxymkIiRpbpDziHc290Xr9A1O4Om7otoRA==", + "dev": true + }, + "nice-try": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", + "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==", + "dev": true + }, + "node-fetch-npm": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/node-fetch-npm/-/node-fetch-npm-2.0.2.tgz", + "integrity": "sha512-nJIxm1QmAj4v3nfCvEeCrYSoVwXyxLnaPBK5W1W5DGEJwjlKuC2VEUycGw5oxk+4zZahRrB84PUJJgEmhFTDFw==", + "dev": true, + "requires": { + "encoding": "^0.1.11", + "json-parse-better-errors": "^1.0.0", + "safe-buffer": "^5.1.1" + } + }, + "node-forge": { + "version": "0.7.5", + "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.7.5.tgz", + "integrity": "sha512-MmbQJ2MTESTjt3Gi/3yG1wGpIMhUfcIypUCGtTizFR9IiccFwxSpfp0vtIZlkFclEqERemxfnSdZEMR9VqqEFQ==", + "dev": true + }, + "node-gyp": { + "version": "3.8.0", + "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-3.8.0.tgz", + "integrity": "sha512-3g8lYefrRRzvGeSowdJKAKyks8oUpLEd/DyPV4eMhVlhJ0aNaZqIrNUIPuEWWTAoPqyFkfGrM67MC69baqn6vA==", + "dev": true, + "optional": true, + "requires": { + "fstream": "^1.0.0", + "glob": "^7.0.3", + "graceful-fs": "^4.1.2", + "mkdirp": "^0.5.0", + "nopt": "2 || 3", + "npmlog": "0 || 1 || 2 || 3 || 4", + "osenv": "0", + "request": "^2.87.0", + "rimraf": "2", + "semver": "~5.3.0", + "tar": "^2.0.0", + "which": "1" + }, + "dependencies": { + "semver": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.3.0.tgz", + "integrity": "sha1-myzl094C0XxgEq0yaqa00M9U+U8=", + "dev": true, + "optional": true + } + } + }, + "node-libs-browser": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/node-libs-browser/-/node-libs-browser-2.2.0.tgz", + "integrity": "sha512-5MQunG/oyOaBdttrL40dA7bUfPORLRWMUJLQtMg7nluxUvk5XwnLdL9twQHFAjRx/y7mIMkLKT9++qPbbk6BZA==", + "dev": true, + "requires": { + "assert": "^1.1.1", + "browserify-zlib": "^0.2.0", + "buffer": "^4.3.0", + "console-browserify": "^1.1.0", + "constants-browserify": "^1.0.0", + "crypto-browserify": "^3.11.0", + "domain-browser": "^1.1.1", + "events": "^3.0.0", + "https-browserify": "^1.0.0", + "os-browserify": "^0.3.0", + "path-browserify": "0.0.0", + "process": "^0.11.10", + "punycode": "^1.2.4", + "querystring-es3": "^0.2.0", + "readable-stream": "^2.3.3", + "stream-browserify": "^2.0.1", + "stream-http": "^2.7.2", + "string_decoder": "^1.0.0", + "timers-browserify": "^2.0.4", + "tty-browserify": "0.0.0", + "url": "^0.11.0", + "util": "^0.11.0", + "vm-browserify": "0.0.4" + }, + "dependencies": { + "punycode": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", + "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=", + "dev": true + } + } + }, + "node-releases": { + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.13.tgz", + "integrity": "sha512-fKZGviSXR6YvVPyc011NHuJDSD8gFQvLPmc2d2V3BS4gr52ycyQ1Xzs7a8B+Ax3Ni/W+5h1h4SqmzeoA8WZRmA==", + "dev": true, + "requires": { + "semver": "^5.3.0" + } + }, + "node-sass": { + "version": "4.11.0", + "resolved": "https://registry.npmjs.org/node-sass/-/node-sass-4.11.0.tgz", + "integrity": "sha512-bHUdHTphgQJZaF1LASx0kAviPH7sGlcyNhWade4eVIpFp6tsn7SV8xNMTbsQFpEV9VXpnwTTnNYlfsZXgGgmkA==", + "dev": true, + "optional": true, + "requires": { + "async-foreach": "^0.1.3", + "chalk": "^1.1.1", + "cross-spawn": "^3.0.0", + "gaze": "^1.0.0", + "get-stdin": "^4.0.1", + "glob": "^7.0.3", + "in-publish": "^2.0.0", + "lodash.assign": "^4.2.0", + "lodash.clonedeep": "^4.3.2", + "lodash.mergewith": "^4.6.0", + "meow": "^3.7.0", + "mkdirp": "^0.5.1", + "nan": "^2.10.0", + "node-gyp": "^3.8.0", + "npmlog": "^4.0.0", + "request": "^2.88.0", + "sass-graph": "^2.2.4", + "stdout-stream": "^1.4.0", + "true-case-path": "^1.0.2" + }, + "dependencies": { + "ansi-styles": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", + "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", + "dev": true, + "optional": true + }, + "chalk": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "dev": true, + "optional": true, + "requires": { + "ansi-styles": "^2.2.1", + "escape-string-regexp": "^1.0.2", + "has-ansi": "^2.0.0", + "strip-ansi": "^3.0.0", + "supports-color": "^2.0.0" + } + }, + "supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", + "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", + "dev": true, + "optional": true + } + } + }, + "nopt": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-3.0.6.tgz", + "integrity": "sha1-xkZdvwirzU2zWTF/eaxopkayj/k=", + "dev": true, + "optional": true, + "requires": { + "abbrev": "1" + } + }, + "normalize-package-data": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", + "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", + "dev": true, + "requires": { + "hosted-git-info": "^2.1.4", + "resolve": "^1.10.0", + "semver": "2 || 3 || 4 || 5", + "validate-npm-package-license": "^3.0.1" + } + }, + "normalize-path": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", + "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", + "dev": true, + "requires": { + "remove-trailing-separator": "^1.0.1" + } + }, + "normalize-range": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz", + "integrity": "sha1-LRDAa9/TEuqXd2laTShDlFa3WUI=", + "dev": true + }, + "npm-bundled": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/npm-bundled/-/npm-bundled-1.0.6.tgz", + "integrity": "sha512-8/JCaftHwbd//k6y2rEWp6k1wxVfpFzB6t1p825+cUb7Ym2XQfhwIC5KwhrvzZRJu+LtDE585zVaS32+CGtf0g==", + "dev": true + }, + "npm-package-arg": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/npm-package-arg/-/npm-package-arg-6.1.0.tgz", + "integrity": "sha512-zYbhP2k9DbJhA0Z3HKUePUgdB1x7MfIfKssC+WLPFMKTBZKpZh5m13PgexJjCq6KW7j17r0jHWcCpxEqnnncSA==", + "dev": true, + "requires": { + "hosted-git-info": "^2.6.0", + "osenv": "^0.1.5", + "semver": "^5.5.0", + "validate-npm-package-name": "^3.0.0" + } + }, + "npm-packlist": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/npm-packlist/-/npm-packlist-1.4.1.tgz", + "integrity": "sha512-+TcdO7HJJ8peiiYhvPxsEDhF3PJFGUGRcFsGve3vxvxdcpO2Z4Z7rkosRM0kWj6LfbK/P0gu3dzk5RU1ffvFcw==", + "dev": true, + "requires": { + "ignore-walk": "^3.0.1", + "npm-bundled": "^1.0.1" + } + }, + "npm-pick-manifest": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/npm-pick-manifest/-/npm-pick-manifest-2.2.3.tgz", + "integrity": "sha512-+IluBC5K201+gRU85vFlUwX3PFShZAbAgDNp2ewJdWMVSppdo/Zih0ul2Ecky/X7b51J7LrrUAP+XOmOCvYZqA==", + "dev": true, + "requires": { + "figgy-pudding": "^3.5.1", + "npm-package-arg": "^6.0.0", + "semver": "^5.4.1" + } + }, + "npm-registry-fetch": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/npm-registry-fetch/-/npm-registry-fetch-3.9.0.tgz", + "integrity": "sha512-srwmt8YhNajAoSAaDWndmZgx89lJwIZ1GWxOuckH4Coek4uHv5S+o/l9FLQe/awA+JwTnj4FJHldxhlXdZEBmw==", + "dev": true, + "requires": { + "JSONStream": "^1.3.4", + "bluebird": "^3.5.1", + "figgy-pudding": "^3.4.1", + "lru-cache": "^4.1.3", + "make-fetch-happen": "^4.0.1", + "npm-package-arg": "^6.1.0" + } + }, + "npm-run-path": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", + "integrity": "sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8=", + "dev": true, + "requires": { + "path-key": "^2.0.0" + } + }, + "npmlog": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz", + "integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==", + "dev": true, + "optional": true, + "requires": { + "are-we-there-yet": "~1.1.2", + "console-control-strings": "~1.1.0", + "gauge": "~2.7.3", + "set-blocking": "~2.0.0" + } + }, + "null-check": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/null-check/-/null-check-1.0.0.tgz", + "integrity": "sha1-l33/1xdgErnsMNKjnbXPcqBDnt0=", + "dev": true + }, + "num2fraction": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/num2fraction/-/num2fraction-1.2.2.tgz", + "integrity": "sha1-b2gragJ6Tp3fpFZM0lidHU5mnt4=", + "dev": true + }, + "number-is-nan": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", + "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=", + "dev": true + }, + "oauth-sign": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", + "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==", + "dev": true + }, + "object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", + "dev": true + }, + "object-component": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/object-component/-/object-component-0.0.3.tgz", + "integrity": "sha1-8MaapQ78lbhmwYb0AKM3acsvEpE=", + "dev": true + }, + "object-copy": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/object-copy/-/object-copy-0.1.0.tgz", + "integrity": "sha1-fn2Fi3gb18mRpBupde04EnVOmYw=", + "dev": true, + "requires": { + "copy-descriptor": "^0.1.0", + "define-property": "^0.2.5", + "kind-of": "^3.0.3" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "requires": { + "is-descriptor": "^0.1.0" + } + }, + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "object-visit": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/object-visit/-/object-visit-1.0.1.tgz", + "integrity": "sha1-95xEk68MU3e1n+OdOV5BBC3QRbs=", + "dev": true, + "requires": { + "isobject": "^3.0.0" + } + }, + "object.pick": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/object.pick/-/object.pick-1.3.0.tgz", + "integrity": "sha1-h6EKxMFpS9Lhy/U1kaZhQftd10c=", + "dev": true, + "requires": { + "isobject": "^3.0.1" + } + }, + "obuf": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/obuf/-/obuf-1.1.2.tgz", + "integrity": "sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg==", + "dev": true + }, + "on-finished": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", + "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", + "dev": true, + "requires": { + "ee-first": "1.1.1" + } + }, + "on-headers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz", + "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==", + "dev": true + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dev": true, + "requires": { + "wrappy": "1" + } + }, + "onetime": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-2.0.1.tgz", + "integrity": "sha1-BnQoIw/WdEOyeUsiu6UotoZ5YtQ=", + "dev": true, + "requires": { + "mimic-fn": "^1.0.0" + }, + "dependencies": { + "mimic-fn": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.2.0.tgz", + "integrity": "sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ==", + "dev": true + } + } + }, + "open": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/open/-/open-6.0.0.tgz", + "integrity": "sha512-/yb5mVZBz7mHLySMiSj2DcLtMBbFPJk5JBKEkHVZFxZAPzeg3L026O0T+lbdz1B2nyDnkClRSwRQJdeVUIF7zw==", + "dev": true, + "requires": { + "is-wsl": "^1.1.0" + } + }, + "opn": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/opn/-/opn-5.4.0.tgz", + "integrity": "sha512-YF9MNdVy/0qvJvDtunAOzFw9iasOQHpVthTCvGzxt61Il64AYSGdK+rYwld7NAfk9qJ7dt+hymBNSc9LNYS+Sw==", + "dev": true, + "requires": { + "is-wsl": "^1.1.0" + } + }, + "optimist": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/optimist/-/optimist-0.6.1.tgz", + "integrity": "sha1-2j6nRob6IaGaERwybpDrFaAZZoY=", + "dev": true, + "requires": { + "minimist": "~0.0.1", + "wordwrap": "~0.0.2" + }, + "dependencies": { + "minimist": { + "version": "0.0.10", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.10.tgz", + "integrity": "sha1-3j+YVD2/lggr5IrRoMfNqDYwHc8=", + "dev": true + } + } + }, + "original": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/original/-/original-1.0.2.tgz", + "integrity": "sha512-hyBVl6iqqUOJ8FqRe+l/gS8H+kKYjrEndd5Pm1MfBtsEKA038HkkdbAl/72EAXGyonD/PFsvmVG+EvcIpliMBg==", + "dev": true, + "requires": { + "url-parse": "^1.4.3" + } + }, + "os-browserify": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/os-browserify/-/os-browserify-0.3.0.tgz", + "integrity": "sha1-hUNzx/XCMVkU/Jv8a9gjj92h7Cc=", + "dev": true + }, + "os-homedir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", + "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=", + "dev": true + }, + "os-locale": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-1.4.0.tgz", + "integrity": "sha1-IPnxeuKe00XoveWDsT0gCYA8FNk=", + "dev": true, + "optional": true, + "requires": { + "lcid": "^1.0.0" + } + }, + "os-tmpdir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", + "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=", + "dev": true + }, + "osenv": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/osenv/-/osenv-0.1.5.tgz", + "integrity": "sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g==", + "dev": true, + "requires": { + "os-homedir": "^1.0.0", + "os-tmpdir": "^1.0.0" + } + }, + "p-defer": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-defer/-/p-defer-1.0.0.tgz", + "integrity": "sha1-n26xgvbJqozXQwBKfU+WsZaw+ww=", + "dev": true + }, + "p-finally": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", + "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=", + "dev": true + }, + "p-is-promise": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/p-is-promise/-/p-is-promise-2.1.0.tgz", + "integrity": "sha512-Y3W0wlRPK8ZMRbNq97l4M5otioeA5lm1z7bkNkxCka8HSPjR0xRWmpCmc9utiaLP9Jb1eD8BgeIxTW4AIF45Pg==", + "dev": true + }, + "p-limit": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", + "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", + "dev": true, + "requires": { + "p-try": "^1.0.0" + } + }, + "p-locate": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", + "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=", + "dev": true, + "requires": { + "p-limit": "^1.1.0" + } + }, + "p-map": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-1.2.0.tgz", + "integrity": "sha512-r6zKACMNhjPJMTl8KcFH4li//gkrXWfbD6feV8l6doRHlzljFWGJ2AP6iKaCJXyZmAUMOPtvbW7EXkbWO/pLEA==", + "dev": true + }, + "p-try": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", + "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=", + "dev": true + }, + "pacote": { + "version": "9.4.0", + "resolved": "https://registry.npmjs.org/pacote/-/pacote-9.4.0.tgz", + "integrity": "sha512-WQ1KL/phGMkedYEQx9ODsjj7xvwLSpdFJJdEXrLyw5SILMxcTNt5DTxT2Z93fXuLFYJBlZJdnwdalrQdB/rX5w==", + "dev": true, + "requires": { + "bluebird": "^3.5.3", + "cacache": "^11.3.2", + "figgy-pudding": "^3.5.1", + "get-stream": "^4.1.0", + "glob": "^7.1.3", + "lru-cache": "^5.1.1", + "make-fetch-happen": "^4.0.1", + "minimatch": "^3.0.4", + "minipass": "^2.3.5", + "mississippi": "^3.0.0", + "mkdirp": "^0.5.1", + "normalize-package-data": "^2.4.0", + "npm-package-arg": "^6.1.0", + "npm-packlist": "^1.1.12", + "npm-pick-manifest": "^2.2.3", + "npm-registry-fetch": "^3.8.0", + "osenv": "^0.1.5", + "promise-inflight": "^1.0.1", + "promise-retry": "^1.1.1", + "protoduck": "^5.0.1", + "rimraf": "^2.6.2", + "safe-buffer": "^5.1.2", + "semver": "^5.6.0", + "ssri": "^6.0.1", + "tar": "^4.4.8", + "unique-filename": "^1.1.1", + "which": "^1.3.1" + }, + "dependencies": { + "cacache": { + "version": "11.3.2", + "resolved": "https://registry.npmjs.org/cacache/-/cacache-11.3.2.tgz", + "integrity": "sha512-E0zP4EPGDOaT2chM08Als91eYnf8Z+eH1awwwVsngUmgppfM5jjJ8l3z5vO5p5w/I3LsiXawb1sW0VY65pQABg==", + "dev": true, + "requires": { + "bluebird": "^3.5.3", + "chownr": "^1.1.1", + "figgy-pudding": "^3.5.1", + "glob": "^7.1.3", + "graceful-fs": "^4.1.15", + "lru-cache": "^5.1.1", + "mississippi": "^3.0.0", + "mkdirp": "^0.5.1", + "move-concurrently": "^1.0.1", + "promise-inflight": "^1.0.1", + "rimraf": "^2.6.2", + "ssri": "^6.0.1", + "unique-filename": "^1.1.1", + "y18n": "^4.0.0" + } + }, + "get-stream": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", + "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", + "dev": true, + "requires": { + "pump": "^3.0.0" + } + }, + "lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "requires": { + "yallist": "^3.0.2" + } + }, + "mississippi": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/mississippi/-/mississippi-3.0.0.tgz", + "integrity": "sha512-x471SsVjUtBRtcvd4BzKE9kFC+/2TeWgKCgw0bZcw1b9l2X3QX5vCWgF+KaZaYm87Ss//rHnWryupDrgLvmSkA==", + "dev": true, + "requires": { + "concat-stream": "^1.5.0", + "duplexify": "^3.4.2", + "end-of-stream": "^1.1.0", + "flush-write-stream": "^1.0.0", + "from2": "^2.1.0", + "parallel-transform": "^1.1.0", + "pump": "^3.0.0", + "pumpify": "^1.3.3", + "stream-each": "^1.1.0", + "through2": "^2.0.0" + } + }, + "pump": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "dev": true, + "requires": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "ssri": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/ssri/-/ssri-6.0.1.tgz", + "integrity": "sha512-3Wge10hNcT1Kur4PDFwEieXSCMCJs/7WvSACcrMYrNp+b8kDL1/0wJch5Ni2WrtwEa2IO8OsVfeKIciKCDx/QA==", + "dev": true, + "requires": { + "figgy-pudding": "^3.5.1" + } + }, + "tar": { + "version": "4.4.8", + "resolved": "https://registry.npmjs.org/tar/-/tar-4.4.8.tgz", + "integrity": "sha512-LzHF64s5chPQQS0IYBn9IN5h3i98c12bo4NCO7e0sGM2llXQ3p2FGC5sdENN4cTW48O915Sh+x+EXx7XW96xYQ==", + "dev": true, + "requires": { + "chownr": "^1.1.1", + "fs-minipass": "^1.2.5", + "minipass": "^2.3.4", + "minizlib": "^1.1.1", + "mkdirp": "^0.5.0", + "safe-buffer": "^5.1.2", + "yallist": "^3.0.2" + } + }, + "yallist": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.0.3.tgz", + "integrity": "sha512-S+Zk8DEWE6oKpV+vI3qWkaK+jSbIK86pCwe2IF/xwIpQ8jEuxpw9NyaGjmp9+BoJv5FV2piqCDcoCtStppiq2A==", + "dev": true + } + } + }, + "pako": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.10.tgz", + "integrity": "sha512-0DTvPVU3ed8+HNXOu5Bs+o//Mbdj9VNQMUOe9oKCwh8l0GNwpTDMKCWbRjgtD291AWnkAgkqA/LOnQS8AmS1tw==", + "dev": true + }, + "parallel-transform": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/parallel-transform/-/parallel-transform-1.1.0.tgz", + "integrity": "sha1-1BDwZbBdojCB/NEPKIVMKb2jOwY=", + "dev": true, + "requires": { + "cyclist": "~0.2.2", + "inherits": "^2.0.3", + "readable-stream": "^2.1.5" + } + }, + "parse-asn1": { + "version": "5.1.4", + "resolved": "https://registry.npmjs.org/parse-asn1/-/parse-asn1-5.1.4.tgz", + "integrity": "sha512-Qs5duJcuvNExRfFZ99HDD3z4mAi3r9Wl/FOjEOijlxwCZs7E7mW2vjTpgQ4J8LpTF8x5v+1Vn5UQFejmWT11aw==", + "dev": true, + "requires": { + "asn1.js": "^4.0.0", + "browserify-aes": "^1.0.0", + "create-hash": "^1.1.0", + "evp_bytestokey": "^1.0.0", + "pbkdf2": "^3.0.3", + "safe-buffer": "^5.1.1" + } + }, + "parse-json": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz", + "integrity": "sha1-9ID0BDTvgHQfhGkJn43qGPVaTck=", + "dev": true, + "requires": { + "error-ex": "^1.2.0" + } + }, + "parse5": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-4.0.0.tgz", + "integrity": "sha512-VrZ7eOd3T1Fk4XWNXMgiGBK/z0MG48BWG2uQNU4I72fkQuKUTZpl+u9k+CxEG0twMVzSmXEEz12z5Fnw1jIQFA==", + "dev": true + }, + "parseqs": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/parseqs/-/parseqs-0.0.5.tgz", + "integrity": "sha1-1SCKNzjkZ2bikbouoXNoSSGouJ0=", + "dev": true, + "requires": { + "better-assert": "~1.0.0" + } + }, + "parseuri": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/parseuri/-/parseuri-0.0.5.tgz", + "integrity": "sha1-gCBKUNTbt3m/3G6+J3jZDkvOMgo=", + "dev": true, + "requires": { + "better-assert": "~1.0.0" + } + }, + "parseurl": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.2.tgz", + "integrity": "sha1-/CidTtiZMRlGDBViUyYs3I3mW/M=", + "dev": true + }, + "pascalcase": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/pascalcase/-/pascalcase-0.1.1.tgz", + "integrity": "sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ=", + "dev": true + }, + "path-browserify": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-0.0.0.tgz", + "integrity": "sha1-oLhwcpquIUAFt9UDLsLLuw+0RRo=", + "dev": true + }, + "path-dirname": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/path-dirname/-/path-dirname-1.0.2.tgz", + "integrity": "sha1-zDPSTVJeCZpTiMAzbG4yuRYGCeA=", + "dev": true + }, + "path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", + "dev": true + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "dev": true + }, + "path-is-inside": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.2.tgz", + "integrity": "sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM=", + "dev": true + }, + "path-key": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", + "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=", + "dev": true + }, + "path-parse": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz", + "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==", + "dev": true + }, + "path-to-regexp": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", + "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=", + "dev": true + }, + "path-type": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-3.0.0.tgz", + "integrity": "sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==", + "dev": true, + "requires": { + "pify": "^3.0.0" + } + }, + "pbkdf2": { + "version": "3.0.17", + "resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.0.17.tgz", + "integrity": "sha512-U/il5MsrZp7mGg3mSQfn742na2T+1/vHDCG5/iTI3X9MKUuYUZVLQhyRsg06mCgDBTd57TxzgZt7P+fYfjRLtA==", + "dev": true, + "requires": { + "create-hash": "^1.1.2", + "create-hmac": "^1.1.4", + "ripemd160": "^2.0.1", + "safe-buffer": "^5.0.1", + "sha.js": "^2.4.8" + } + }, + "performance-now": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", + "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=", + "dev": true + }, + "pify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", + "dev": true + }, + "pinkie": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", + "integrity": "sha1-clVrgM+g1IqXToDnckjoDtT3+HA=", + "dev": true + }, + "pinkie-promise": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz", + "integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=", + "dev": true, + "requires": { + "pinkie": "^2.0.0" + } + }, + "pkg-dir": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-2.0.0.tgz", + "integrity": "sha1-9tXREJ4Z1j7fQo4L1X4Sd3YVM0s=", + "dev": true, + "requires": { + "find-up": "^2.1.0" + } + }, + "portfinder": { + "version": "1.0.20", + "resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.20.tgz", + "integrity": "sha512-Yxe4mTyDzTd59PZJY4ojZR8F+E5e97iq2ZOHPz3HDgSvYC5siNad2tLooQ5y5QHyQhc3xVqvyk/eNA3wuoa7Sw==", + "dev": true, + "requires": { + "async": "^1.5.2", + "debug": "^2.2.0", + "mkdirp": "0.5.x" + }, + "dependencies": { + "async": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz", + "integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=", + "dev": true + } + } + }, + "posix-character-classes": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz", + "integrity": "sha1-AerA/jta9xoqbAL+q7jB/vfgDqs=", + "dev": true + }, + "postcss": { + "version": "7.0.14", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.14.tgz", + "integrity": "sha512-NsbD6XUUMZvBxtQAJuWDJeeC4QFsmWsfozWxCJPWf3M55K9iu2iMDaKqyoOdTJ1R4usBXuxlVFAIo8rZPQD4Bg==", + "dev": true, + "requires": { + "chalk": "^2.4.2", + "source-map": "^0.6.1", + "supports-color": "^6.1.0" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + } + } + }, + "postcss-import": { + "version": "12.0.1", + "resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-12.0.1.tgz", + "integrity": "sha512-3Gti33dmCjyKBgimqGxL3vcV8w9+bsHwO5UrBawp796+jdardbcFl4RP5w/76BwNL7aGzpKstIfF9I+kdE8pTw==", + "dev": true, + "requires": { + "postcss": "^7.0.1", + "postcss-value-parser": "^3.2.3", + "read-cache": "^1.0.0", + "resolve": "^1.1.7" + } + }, + "postcss-load-config": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-2.0.0.tgz", + "integrity": "sha512-V5JBLzw406BB8UIfsAWSK2KSwIJ5yoEIVFb4gVkXci0QdKgA24jLmHZ/ghe/GgX0lJ0/D1uUK1ejhzEY94MChQ==", + "dev": true, + "requires": { + "cosmiconfig": "^4.0.0", + "import-cwd": "^2.0.0" + } + }, + "postcss-loader": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/postcss-loader/-/postcss-loader-3.0.0.tgz", + "integrity": "sha512-cLWoDEY5OwHcAjDnkyRQzAXfs2jrKjXpO/HQFcc5b5u/r7aa471wdmChmwfnv7x2u840iat/wi0lQ5nbRgSkUA==", + "dev": true, + "requires": { + "loader-utils": "^1.1.0", + "postcss": "^7.0.0", + "postcss-load-config": "^2.0.0", + "schema-utils": "^1.0.0" + } + }, + "postcss-value-parser": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", + "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==", + "dev": true + }, + "primeflex": { + "version": "1.0.0-rc.1", + "resolved": "https://registry.npmjs.org/primeflex/-/primeflex-1.0.0-rc.1.tgz", + "integrity": "sha512-JVrQE0ulyP0UJ3Rn8UI1RjmpHU/f4CIm7BI18xoMLlUriAViiykFMmtDTvUC0AZWHl4YhgEvHJIZ7EERxER2cg==" + }, + "primeicons": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/primeicons/-/primeicons-1.0.0.tgz", + "integrity": "sha512-p/hzIjUVccW4eJPhuORHI3AUkDpqfvCQVrjxbFEejnTEdWY4C8fomVfjiaA9jCu83fSQnBHuRIGB96iAR8R6uA==" + }, + "primeng": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/primeng/-/primeng-7.1.0.tgz", + "integrity": "sha512-uUkiKtGjCJN4Hz5fKliiw8/UvkOC7RV4oWOmoDY1el2Y2NDCyQIUI9t4QbyoPeyZzL1CggBbFsVPkqnQg+oVxA==" + }, + "process": { + "version": "0.11.10", + "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", + "integrity": "sha1-czIwDoQBYb2j5podHZGn1LwW8YI=", + "dev": true + }, + "process-nextick-args": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz", + "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==", + "dev": true + }, + "promise": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/promise/-/promise-7.3.1.tgz", + "integrity": "sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg==", + "dev": true, + "optional": true, + "requires": { + "asap": "~2.0.3" + } + }, + "promise-inflight": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz", + "integrity": "sha1-mEcocL8igTL8vdhoEputEsPAKeM=", + "dev": true + }, + "promise-retry": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/promise-retry/-/promise-retry-1.1.1.tgz", + "integrity": "sha1-ZznpaOMFHaIM5kl/srUPaRHfPW0=", + "dev": true, + "requires": { + "err-code": "^1.0.0", + "retry": "^0.10.0" + } + }, + "protoduck": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/protoduck/-/protoduck-5.0.1.tgz", + "integrity": "sha512-WxoCeDCoCBY55BMvj4cAEjdVUFGRWed9ZxPlqTKYyw1nDDTQ4pqmnIMAGfJlg7Dx35uB/M+PHJPTmGOvaCaPTg==", + "dev": true, + "requires": { + "genfun": "^5.0.0" + } + }, + "protractor": { + "version": "5.4.2", + "resolved": "https://registry.npmjs.org/protractor/-/protractor-5.4.2.tgz", + "integrity": "sha512-zlIj64Cr6IOWP7RwxVeD8O4UskLYPoyIcg0HboWJL9T79F1F0VWtKkGTr/9GN6BKL+/Q/GmM7C9kFVCfDbP5sA==", + "dev": true, + "requires": { + "@types/q": "^0.0.32", + "@types/selenium-webdriver": "^3.0.0", + "blocking-proxy": "^1.0.0", + "browserstack": "^1.5.1", + "chalk": "^1.1.3", + "glob": "^7.0.3", + "jasmine": "2.8.0", + "jasminewd2": "^2.1.0", + "optimist": "~0.6.0", + "q": "1.4.1", + "saucelabs": "^1.5.0", + "selenium-webdriver": "3.6.0", + "source-map-support": "~0.4.0", + "webdriver-js-extender": "2.1.0", + "webdriver-manager": "^12.0.6" + }, + "dependencies": { + "ansi-styles": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", + "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", + "dev": true + }, + "chalk": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "dev": true, + "requires": { + "ansi-styles": "^2.2.1", + "escape-string-regexp": "^1.0.2", + "has-ansi": "^2.0.0", + "strip-ansi": "^3.0.0", + "supports-color": "^2.0.0" + } + }, + "del": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/del/-/del-2.2.2.tgz", + "integrity": "sha1-wSyYHQZ4RshLyvhiz/kw2Qf/0ag=", + "dev": true, + "requires": { + "globby": "^5.0.0", + "is-path-cwd": "^1.0.0", + "is-path-in-cwd": "^1.0.0", + "object-assign": "^4.0.1", + "pify": "^2.0.0", + "pinkie-promise": "^2.0.0", + "rimraf": "^2.2.8" + } + }, + "globby": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-5.0.0.tgz", + "integrity": "sha1-69hGZ8oNuzMLmbz8aOrCvFQ3Dg0=", + "dev": true, + "requires": { + "array-union": "^1.0.1", + "arrify": "^1.0.0", + "glob": "^7.0.3", + "object-assign": "^4.0.1", + "pify": "^2.0.0", + "pinkie-promise": "^2.0.0" + } + }, + "pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", + "dev": true + }, + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true + }, + "source-map-support": { + "version": "0.4.18", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.4.18.tgz", + "integrity": "sha512-try0/JqxPLF9nOjvSta7tVondkP5dwgyLDjVoyMDlmjugT2lRZ1OfsrYTkCd2hkDnJTKRbO/Rl3orm8vlsUzbA==", + "dev": true, + "requires": { + "source-map": "^0.5.6" + } + }, + "supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", + "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", + "dev": true + }, + "webdriver-manager": { + "version": "12.1.1", + "resolved": "https://registry.npmjs.org/webdriver-manager/-/webdriver-manager-12.1.1.tgz", + "integrity": "sha512-L9TEQmZs6JbMMRQI1w60mfps265/NCr0toYJl7p/R2OAk6oXAfwI6jqYP7EWae+d7Ad2S2Aj4+rzxoSjqk3ZuA==", + "dev": true, + "requires": { + "adm-zip": "^0.4.9", + "chalk": "^1.1.1", + "del": "^2.2.0", + "glob": "^7.0.3", + "ini": "^1.3.4", + "minimist": "^1.2.0", + "q": "^1.4.1", + "request": "^2.87.0", + "rimraf": "^2.5.2", + "semver": "^5.3.0", + "xml2js": "^0.4.17" + } + } + } + }, + "proxy-addr": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.4.tgz", + "integrity": "sha512-5erio2h9jp5CHGwcybmxmVqHmnCBZeewlfJ0pex+UW7Qny7OOZXTtH56TGNyBizkgiOwhJtMKrVzDTeKcySZwA==", + "dev": true, + "requires": { + "forwarded": "~0.1.2", + "ipaddr.js": "1.8.0" + } + }, + "prr": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz", + "integrity": "sha1-0/wRS6BplaRexok/SEzrHXj19HY=", + "dev": true + }, + "pseudomap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", + "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=", + "dev": true + }, + "psl": { + "version": "1.1.31", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.1.31.tgz", + "integrity": "sha512-/6pt4+C+T+wZUieKR620OpzN/LlnNKuWjy1iFLQ/UG35JqHlR/89MP1d96dUfkf6Dne3TuLQzOYEYshJ+Hx8mw==", + "dev": true + }, + "public-encrypt": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/public-encrypt/-/public-encrypt-4.0.3.tgz", + "integrity": "sha512-zVpa8oKZSz5bTMTFClc1fQOnyyEzpl5ozpi1B5YcvBrdohMjH2rfsBtyXcuNuwjsDIXmBYlF2N5FlJYhR29t8Q==", + "dev": true, + "requires": { + "bn.js": "^4.1.0", + "browserify-rsa": "^4.0.0", + "create-hash": "^1.1.0", + "parse-asn1": "^5.0.0", + "randombytes": "^2.0.1", + "safe-buffer": "^5.1.2" + } + }, + "pump": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pump/-/pump-2.0.1.tgz", + "integrity": "sha512-ruPMNRkN3MHP1cWJc9OWr+T/xDP0jhXYCLfJcBuX54hhfIBnaQmAUMfDcG4DM5UMWByBbJY69QSphm3jtDKIkA==", + "dev": true, + "requires": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "pumpify": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/pumpify/-/pumpify-1.5.1.tgz", + "integrity": "sha512-oClZI37HvuUJJxSKKrC17bZ9Cu0ZYhEAGPsPUy9KlMUmv9dKX2o77RUmq7f3XjIxbwyGwYzbzQ1L2Ks8sIradQ==", + "dev": true, + "requires": { + "duplexify": "^3.6.0", + "inherits": "^2.0.3", + "pump": "^2.0.0" + } + }, + "punycode": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", + "dev": true + }, + "q": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/q/-/q-1.4.1.tgz", + "integrity": "sha1-VXBbzZPF82c1MMLCy8DCs63cKG4=", + "dev": true + }, + "qjobs": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/qjobs/-/qjobs-1.2.0.tgz", + "integrity": "sha512-8YOJEHtxpySA3fFDyCRxA+UUV+fA+rTWnuWvylOK/NCjhY+b4ocCtmu8TtsWb+mYeU+GCHf/S66KZF/AsteKHg==", + "dev": true + }, + "qs": { + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", + "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==", + "dev": true + }, + "querystring": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz", + "integrity": "sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=", + "dev": true + }, + "querystring-es3": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/querystring-es3/-/querystring-es3-0.2.1.tgz", + "integrity": "sha1-nsYfeQSYdXB9aUFFlv2Qek1xHnM=", + "dev": true + }, + "querystringify": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.1.1.tgz", + "integrity": "sha512-w7fLxIRCRT7U8Qu53jQnJyPkYZIaR4n5151KMfcJlO/A9397Wxb1amJvROTK6TOnp7PfoAmg/qXiNHI+08jRfA==", + "dev": true + }, + "randombytes": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "dev": true, + "requires": { + "safe-buffer": "^5.1.0" + } + }, + "randomfill": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/randomfill/-/randomfill-1.0.4.tgz", + "integrity": "sha512-87lcbR8+MhcWcUiQ+9e+Rwx8MyR2P7qnt15ynUlbm3TU/fjbgz4GsvfSUDTemtCCtVCqb4ZcEFlyPNTh9bBTLw==", + "dev": true, + "requires": { + "randombytes": "^2.0.5", + "safe-buffer": "^5.1.0" + } + }, + "range-parser": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.0.tgz", + "integrity": "sha1-9JvmtIeJTdxA3MlKMi9hEJLgDV4=", + "dev": true + }, + "raw-body": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.3.3.tgz", + "integrity": "sha512-9esiElv1BrZoI3rCDuOuKCBRbuApGGaDPQfjSflGxdy4oyzqghxu6klEkkVIvBje+FF0BX9coEv8KqW6X/7njw==", + "dev": true, + "requires": { + "bytes": "3.0.0", + "http-errors": "1.6.3", + "iconv-lite": "0.4.23", + "unpipe": "1.0.0" + } + }, + "raw-loader": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/raw-loader/-/raw-loader-1.0.0.tgz", + "integrity": "sha512-Uqy5AqELpytJTRxYT4fhltcKPj0TyaEpzJDcGz7DFJi+pQOOi3GjR/DOdxTkTsF+NzhnldIoG6TORaBlInUuqA==", + "dev": true, + "requires": { + "loader-utils": "^1.1.0", + "schema-utils": "^1.0.0" + } + }, + "read-cache": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", + "integrity": "sha1-5mTvMRYRZsl1HNvo28+GtftY93Q=", + "dev": true, + "requires": { + "pify": "^2.3.0" + }, + "dependencies": { + "pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", + "dev": true + } + } + }, + "read-pkg": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-1.1.0.tgz", + "integrity": "sha1-9f+qXs0pyzHAR0vKfXVra7KePyg=", + "dev": true, + "optional": true, + "requires": { + "load-json-file": "^1.0.0", + "normalize-package-data": "^2.3.2", + "path-type": "^1.0.0" + }, + "dependencies": { + "path-type": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-1.1.0.tgz", + "integrity": "sha1-WcRPfuSR2nBNpBXaWkBwuk+P5EE=", + "dev": true, + "optional": true, + "requires": { + "graceful-fs": "^4.1.2", + "pify": "^2.0.0", + "pinkie-promise": "^2.0.0" + } + }, + "pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", + "dev": true, + "optional": true + } + } + }, + "read-pkg-up": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-1.0.1.tgz", + "integrity": "sha1-nWPBMnbAZZGNV/ACpX9AobZD+wI=", + "dev": true, + "optional": true, + "requires": { + "find-up": "^1.0.0", + "read-pkg": "^1.0.0" + }, + "dependencies": { + "find-up": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-1.1.2.tgz", + "integrity": "sha1-ay6YIrGizgpgq2TWEOzK1TyyTQ8=", + "dev": true, + "optional": true, + "requires": { + "path-exists": "^2.0.0", + "pinkie-promise": "^2.0.0" + } + }, + "path-exists": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-2.1.0.tgz", + "integrity": "sha1-D+tsZPD8UY2adU3V77YscCJ2H0s=", + "dev": true, + "optional": true, + "requires": { + "pinkie-promise": "^2.0.0" + } + } + } + }, + "readable-stream": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "readdirp": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-2.2.1.tgz", + "integrity": "sha512-1JU/8q+VgFZyxwrJ+SVIOsh+KywWGpds3NTqikiKpDMZWScmAYyKIgqkO+ARvNWJfXeXR1zxz7aHF4u4CyH6vQ==", + "dev": true, + "requires": { + "graceful-fs": "^4.1.11", + "micromatch": "^3.1.10", + "readable-stream": "^2.0.2" + } + }, + "rechoir": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.6.2.tgz", + "integrity": "sha1-hSBLVNuoLVdC4oyWdW70OvUOM4Q=", + "dev": true, + "requires": { + "resolve": "^1.1.6" + } + }, + "redent": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/redent/-/redent-1.0.0.tgz", + "integrity": "sha1-z5Fqsf1fHxbfsggi3W7H9zDCr94=", + "dev": true, + "optional": true, + "requires": { + "indent-string": "^2.1.0", + "strip-indent": "^1.0.1" + } + }, + "reflect-metadata": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.1.13.tgz", + "integrity": "sha512-Ts1Y/anZELhSsjMcU605fU9RE4Oi3p5ORujwbIKXfWa+0Zxs510Qrmrce5/Jowq3cHSZSJqBjypxmHarc+vEWg==", + "dev": true + }, + "regenerate": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.0.tgz", + "integrity": "sha512-1G6jJVDWrt0rK99kBjvEtziZNCICAuvIPkSiUFIQxVP06RCVpq3dmDo2oi6ABpYaDYaTRr67BEhL8r1wgEZZKg==", + "dev": true + }, + "regenerator-runtime": { + "version": "0.11.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz", + "integrity": "sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==", + "dev": true + }, + "regex-not": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/regex-not/-/regex-not-1.0.2.tgz", + "integrity": "sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A==", + "dev": true, + "requires": { + "extend-shallow": "^3.0.2", + "safe-regex": "^1.1.0" + } + }, + "regexpu-core": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-1.0.0.tgz", + "integrity": "sha1-hqdj9Y7k18L2sQLkdkBQ3n7ZDGs=", + "dev": true, + "requires": { + "regenerate": "^1.2.1", + "regjsgen": "^0.2.0", + "regjsparser": "^0.1.4" + } + }, + "regjsgen": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.2.0.tgz", + "integrity": "sha1-bAFq3qxVT3WCP+N6wFuS1aTtsfc=", + "dev": true + }, + "regjsparser": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.1.5.tgz", + "integrity": "sha1-fuj4Tcb6eS0/0K4ijSS9lJ6tIFw=", + "dev": true, + "requires": { + "jsesc": "~0.5.0" + }, + "dependencies": { + "jsesc": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz", + "integrity": "sha1-597mbjXW/Bb3EP6R1c9p9w8IkR0=", + "dev": true + } + } + }, + "remove-trailing-separator": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz", + "integrity": "sha1-wkvOKig62tW8P1jg1IJJuSN52O8=", + "dev": true + }, + "repeat-element": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.3.tgz", + "integrity": "sha512-ahGq0ZnV5m5XtZLMb+vP76kcAM5nkLqk0lpqAuojSKGgQtn4eRi4ZZGm2olo2zKFH+sMsWaqOCW1dqAnOru72g==", + "dev": true + }, + "repeat-string": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", + "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=", + "dev": true + }, + "repeating": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/repeating/-/repeating-2.0.1.tgz", + "integrity": "sha1-UhTFOpJtNVJwdSf7q0FdvAjQbdo=", + "dev": true, + "requires": { + "is-finite": "^1.0.0" + } + }, + "request": { + "version": "2.88.0", + "resolved": "https://registry.npmjs.org/request/-/request-2.88.0.tgz", + "integrity": "sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg==", + "dev": true, + "requires": { + "aws-sign2": "~0.7.0", + "aws4": "^1.8.0", + "caseless": "~0.12.0", + "combined-stream": "~1.0.6", + "extend": "~3.0.2", + "forever-agent": "~0.6.1", + "form-data": "~2.3.2", + "har-validator": "~5.1.0", + "http-signature": "~1.2.0", + "is-typedarray": "~1.0.0", + "isstream": "~0.1.2", + "json-stringify-safe": "~5.0.1", + "mime-types": "~2.1.19", + "oauth-sign": "~0.9.0", + "performance-now": "^2.1.0", + "qs": "~6.5.2", + "safe-buffer": "^5.1.2", + "tough-cookie": "~2.4.3", + "tunnel-agent": "^0.6.0", + "uuid": "^3.3.2" + } + }, + "require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", + "dev": true + }, + "require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "dev": true + }, + "require-main-filename": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-1.0.1.tgz", + "integrity": "sha1-l/cXtp1IeE9fUmpsWqj/3aBVpNE=", + "dev": true + }, + "requires-port": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", + "integrity": "sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=", + "dev": true + }, + "resolve": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.10.0.tgz", + "integrity": "sha512-3sUr9aq5OfSg2S9pNtPA9hL1FVEAjvfOC4leW0SNf/mpnaakz2a9femSd6LqAww2RaFctwyf1lCqnTHuF1rxDg==", + "dev": true, + "requires": { + "path-parse": "^1.0.6" + } + }, + "resolve-cwd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-2.0.0.tgz", + "integrity": "sha1-AKn3OHVW4nA46uIyyqNypqWbZlo=", + "dev": true, + "requires": { + "resolve-from": "^3.0.0" + } + }, + "resolve-from": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-3.0.0.tgz", + "integrity": "sha1-six699nWiBvItuZTM17rywoYh0g=", + "dev": true + }, + "resolve-url": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz", + "integrity": "sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo=", + "dev": true + }, + "restore-cursor": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-2.0.0.tgz", + "integrity": "sha1-n37ih/gv0ybU/RYpI9YhKe7g368=", + "dev": true, + "requires": { + "onetime": "^2.0.0", + "signal-exit": "^3.0.2" + } + }, + "ret": { + "version": "0.1.15", + "resolved": "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz", + "integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==", + "dev": true + }, + "retry": { + "version": "0.10.1", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.10.1.tgz", + "integrity": "sha1-52OI0heZLCUnUCQdPTlW/tmNj/Q=", + "dev": true + }, + "rfdc": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.1.2.tgz", + "integrity": "sha512-92ktAgvZhBzYTIK0Mja9uen5q5J3NRVMoDkJL2VMwq6SXjVCgqvQeVP2XAaUY6HT+XpQYeLSjb3UoitBryKmdA==", + "dev": true + }, + "rimraf": { + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz", + "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==", + "dev": true, + "requires": { + "glob": "^7.1.3" + } + }, + "ripemd160": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/ripemd160/-/ripemd160-2.0.2.tgz", + "integrity": "sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA==", + "dev": true, + "requires": { + "hash-base": "^3.0.0", + "inherits": "^2.0.1" + } + }, + "run-async": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.3.0.tgz", + "integrity": "sha1-A3GrSuC91yDUFm19/aZP96RFpsA=", + "dev": true, + "requires": { + "is-promise": "^2.1.0" + } + }, + "run-queue": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/run-queue/-/run-queue-1.0.3.tgz", + "integrity": "sha1-6Eg5bwV9Ij8kOGkkYY4laUFh7Ec=", + "dev": true, + "requires": { + "aproba": "^1.1.1" + } + }, + "rxjs": { + "version": "6.3.3", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.3.3.tgz", + "integrity": "sha512-JTWmoY9tWCs7zvIk/CvRjhjGaOd+OVBM987mxFo+OW66cGpdKjZcpmc74ES1sB//7Kl/PAe8+wEakuhG4pcgOw==", + "requires": { + "tslib": "^1.9.0" + } + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + }, + "safe-regex": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz", + "integrity": "sha1-QKNmnzsHfR6UPURinhV91IAjvy4=", + "dev": true, + "requires": { + "ret": "~0.1.10" + } + }, + "safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true + }, + "sass-graph": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/sass-graph/-/sass-graph-2.2.4.tgz", + "integrity": "sha1-E/vWPNHK8JCLn9k0dq1DpR0eC0k=", + "dev": true, + "optional": true, + "requires": { + "glob": "^7.0.0", + "lodash": "^4.0.0", + "scss-tokenizer": "^0.2.3", + "yargs": "^7.0.0" + } + }, + "sass-loader": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/sass-loader/-/sass-loader-7.1.0.tgz", + "integrity": "sha512-+G+BKGglmZM2GUSfT9TLuEp6tzehHPjAMoRRItOojWIqIGPloVCMhNIQuG639eJ+y033PaGTSjLaTHts8Kw79w==", + "dev": true, + "requires": { + "clone-deep": "^2.0.1", + "loader-utils": "^1.0.1", + "lodash.tail": "^4.1.1", + "neo-async": "^2.5.0", + "pify": "^3.0.0", + "semver": "^5.5.0" + } + }, + "saucelabs": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/saucelabs/-/saucelabs-1.5.0.tgz", + "integrity": "sha512-jlX3FGdWvYf4Q3LFfFWS1QvPg3IGCGWxIc8QBFdPTbpTJnt/v17FHXYVAn7C8sHf1yUXo2c7yIM0isDryfYtHQ==", + "dev": true, + "requires": { + "https-proxy-agent": "^2.2.1" + } + }, + "sax": { + "version": "0.5.8", + "resolved": "https://registry.npmjs.org/sax/-/sax-0.5.8.tgz", + "integrity": "sha1-1HLbIo6zMcJQaw6MFVJK25OdEsE=", + "dev": true + }, + "schema-utils": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-1.0.0.tgz", + "integrity": "sha512-i27Mic4KovM/lnGsy8whRCHhc7VicJajAjTrYg11K9zfZXnYIt4k5F+kZkwjnrhKzLic/HLU4j11mjsz2G/75g==", + "dev": true, + "requires": { + "ajv": "^6.1.0", + "ajv-errors": "^1.0.0", + "ajv-keywords": "^3.1.0" + } + }, + "scss-tokenizer": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/scss-tokenizer/-/scss-tokenizer-0.2.3.tgz", + "integrity": "sha1-jrBtualyMzOCTT9VMGQRSYR85dE=", + "dev": true, + "optional": true, + "requires": { + "js-base64": "^2.1.8", + "source-map": "^0.4.2" + }, + "dependencies": { + "source-map": { + "version": "0.4.4", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.4.4.tgz", + "integrity": "sha1-66T12pwNyZneaAMti092FzZSA2s=", + "dev": true, + "optional": true, + "requires": { + "amdefine": ">=0.0.4" + } + } + } + }, + "select-hose": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/select-hose/-/select-hose-2.0.0.tgz", + "integrity": "sha1-Yl2GWPhlr0Psliv8N2o3NZpJlMo=", + "dev": true + }, + "selenium-webdriver": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/selenium-webdriver/-/selenium-webdriver-3.6.0.tgz", + "integrity": "sha512-WH7Aldse+2P5bbFBO4Gle/nuQOdVwpHMTL6raL3uuBj/vPG07k6uzt3aiahu352ONBr5xXh0hDlM3LhtXPOC4Q==", + "dev": true, + "requires": { + "jszip": "^3.1.3", + "rimraf": "^2.5.4", + "tmp": "0.0.30", + "xml2js": "^0.4.17" + }, + "dependencies": { + "tmp": { + "version": "0.0.30", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.30.tgz", + "integrity": "sha1-ckGdSovn1s51FI/YsyTlk6cRwu0=", + "dev": true, + "requires": { + "os-tmpdir": "~1.0.1" + } + } + } + }, + "selfsigned": { + "version": "1.10.4", + "resolved": "https://registry.npmjs.org/selfsigned/-/selfsigned-1.10.4.tgz", + "integrity": "sha512-9AukTiDmHXGXWtWjembZ5NDmVvP2695EtpgbCsxCa68w3c88B+alqbmZ4O3hZ4VWGXeGWzEVdvqgAJD8DQPCDw==", + "dev": true, + "requires": { + "node-forge": "0.7.5" + } + }, + "semver": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.6.0.tgz", + "integrity": "sha512-RS9R6R35NYgQn++fkDWaOmqGoj4Ek9gGs+DPxNUZKuwE183xjJroKvyo1IzVFeXvUrvmALy6FWD5xrdJT25gMg==", + "dev": true + }, + "semver-dsl": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/semver-dsl/-/semver-dsl-1.0.1.tgz", + "integrity": "sha1-02eN5VVeimH2Ke7QJTZq5fJzQKA=", + "dev": true, + "requires": { + "semver": "^5.3.0" + } + }, + "semver-intersect": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/semver-intersect/-/semver-intersect-1.4.0.tgz", + "integrity": "sha512-d8fvGg5ycKAq0+I6nfWeCx6ffaWJCsBYU0H2Rq56+/zFePYfT8mXkB3tWBSjR5BerkHNZ5eTPIk1/LBYas35xQ==", + "dev": true, + "requires": { + "semver": "^5.0.0" + } + }, + "send": { + "version": "0.16.2", + "resolved": "https://registry.npmjs.org/send/-/send-0.16.2.tgz", + "integrity": "sha512-E64YFPUssFHEFBvpbbjr44NCLtI1AohxQ8ZSiJjQLskAdKuriYEP6VyGEsRDH8ScozGpkaX1BGvhanqCwkcEZw==", + "dev": true, + "requires": { + "debug": "2.6.9", + "depd": "~1.1.2", + "destroy": "~1.0.4", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "~1.6.2", + "mime": "1.4.1", + "ms": "2.0.0", + "on-finished": "~2.3.0", + "range-parser": "~1.2.0", + "statuses": "~1.4.0" + }, + "dependencies": { + "mime": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.4.1.tgz", + "integrity": "sha512-KI1+qOZu5DcW6wayYHSzR/tXKCDC5Om4s1z2QJjDULzLcmf3DvzS7oluY4HCTrc+9FiKmWUgeNLg7W3uIQvxtQ==", + "dev": true + } + } + }, + "serialize-javascript": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-1.6.1.tgz", + "integrity": "sha512-A5MOagrPFga4YaKQSWHryl7AXvbQkEqpw4NNYMTNYUNV51bA8ABHgYFpqKx+YFFrw59xMV1qGH1R4AgoNIVgCw==", + "dev": true + }, + "serve-index": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/serve-index/-/serve-index-1.9.1.tgz", + "integrity": "sha1-03aNabHn2C5c4FD/9bRTvqEqkjk=", + "dev": true, + "requires": { + "accepts": "~1.3.4", + "batch": "0.6.1", + "debug": "2.6.9", + "escape-html": "~1.0.3", + "http-errors": "~1.6.2", + "mime-types": "~2.1.17", + "parseurl": "~1.3.2" + } + }, + "serve-static": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.13.2.tgz", + "integrity": "sha512-p/tdJrO4U387R9oMjb1oj7qSMaMfmOyd4j9hOFoxZe2baQszgHcSWjuya/CiT5kgZZKRudHNOA0pYXOl8rQ5nw==", + "dev": true, + "requires": { + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "parseurl": "~1.3.2", + "send": "0.16.2" + } + }, + "set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", + "dev": true + }, + "set-immediate-shim": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/set-immediate-shim/-/set-immediate-shim-1.0.1.tgz", + "integrity": "sha1-SysbJ+uAip+NzEgaWOXlb1mfP2E=", + "dev": true + }, + "set-value": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.0.tgz", + "integrity": "sha512-hw0yxk9GT/Hr5yJEYnHNKYXkIA8mVJgd9ditYZCe16ZczcaELYYcfvaXesNACk2O8O0nTiPQcQhGUQj8JLzeeg==", + "dev": true, + "requires": { + "extend-shallow": "^2.0.1", + "is-extendable": "^0.1.1", + "is-plain-object": "^2.0.3", + "split-string": "^3.0.1" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "setimmediate": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", + "integrity": "sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU=", + "dev": true + }, + "setprototypeof": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", + "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==", + "dev": true + }, + "sha.js": { + "version": "2.4.11", + "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", + "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==", + "dev": true, + "requires": { + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, + "shallow-clone": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-1.0.0.tgz", + "integrity": "sha512-oeXreoKR/SyNJtRJMAKPDSvd28OqEwG4eR/xc856cRGBII7gX9lvAqDxusPm0846z/w/hWYjI1NpKwJ00NHzRA==", + "dev": true, + "requires": { + "is-extendable": "^0.1.1", + "kind-of": "^5.0.0", + "mixin-object": "^2.0.1" + }, + "dependencies": { + "kind-of": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", + "dev": true + } + } + }, + "shebang-command": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", + "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", + "dev": true, + "requires": { + "shebang-regex": "^1.0.0" + } + }, + "shebang-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", + "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=", + "dev": true + }, + "shelljs": { + "version": "0.8.3", + "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.8.3.tgz", + "integrity": "sha512-fc0BKlAWiLpwZljmOvAOTE/gXawtCoNrP5oaY7KIaQbbyHeQVg01pSEuEGvGh3HEdBU4baCD7wQBwADmM/7f7A==", + "dev": true, + "requires": { + "glob": "^7.0.0", + "interpret": "^1.0.0", + "rechoir": "^0.6.2" + } + }, + "signal-exit": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", + "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=", + "dev": true + }, + "slash": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-1.0.0.tgz", + "integrity": "sha1-xB8vbDn8FtHNF61LXYlhFK5HDVU=", + "dev": true + }, + "smart-buffer": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.0.2.tgz", + "integrity": "sha512-JDhEpTKzXusOqXZ0BUIdH+CjFdO/CR3tLlf5CN34IypI+xMmXW1uB16OOY8z3cICbJlDAVJzNbwBhNO0wt9OAw==", + "dev": true + }, + "snapdragon": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz", + "integrity": "sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg==", + "dev": true, + "requires": { + "base": "^0.11.1", + "debug": "^2.2.0", + "define-property": "^0.2.5", + "extend-shallow": "^2.0.1", + "map-cache": "^0.2.2", + "source-map": "^0.5.6", + "source-map-resolve": "^0.5.0", + "use": "^3.1.0" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "requires": { + "is-descriptor": "^0.1.0" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + }, + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true + } + } + }, + "snapdragon-node": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/snapdragon-node/-/snapdragon-node-2.1.1.tgz", + "integrity": "sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw==", + "dev": true, + "requires": { + "define-property": "^1.0.0", + "isobject": "^3.0.0", + "snapdragon-util": "^3.0.1" + }, + "dependencies": { + "define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "dev": true, + "requires": { + "is-descriptor": "^1.0.0" + } + }, + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + } + } + } + }, + "snapdragon-util": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/snapdragon-util/-/snapdragon-util-3.0.1.tgz", + "integrity": "sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ==", + "dev": true, + "requires": { + "kind-of": "^3.2.0" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "socket.io": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-2.1.1.tgz", + "integrity": "sha512-rORqq9c+7W0DAK3cleWNSyfv/qKXV99hV4tZe+gGLfBECw3XEhBy7x85F3wypA9688LKjtwO9pX9L33/xQI8yA==", + "dev": true, + "requires": { + "debug": "~3.1.0", + "engine.io": "~3.2.0", + "has-binary2": "~1.0.2", + "socket.io-adapter": "~1.1.0", + "socket.io-client": "2.1.1", + "socket.io-parser": "~3.2.0" + }, + "dependencies": { + "debug": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + } + } + }, + "socket.io-adapter": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-1.1.1.tgz", + "integrity": "sha1-KoBeihTWNyEk3ZFZrUUC+MsH8Gs=", + "dev": true + }, + "socket.io-client": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-2.1.1.tgz", + "integrity": "sha512-jxnFyhAuFxYfjqIgduQlhzqTcOEQSn+OHKVfAxWaNWa7ecP7xSNk2Dx/3UEsDcY7NcFafxvNvKPmmO7HTwTxGQ==", + "dev": true, + "requires": { + "backo2": "1.0.2", + "base64-arraybuffer": "0.1.5", + "component-bind": "1.0.0", + "component-emitter": "1.2.1", + "debug": "~3.1.0", + "engine.io-client": "~3.2.0", + "has-binary2": "~1.0.2", + "has-cors": "1.1.0", + "indexof": "0.0.1", + "object-component": "0.0.3", + "parseqs": "0.0.5", + "parseuri": "0.0.5", + "socket.io-parser": "~3.2.0", + "to-array": "0.1.4" + }, + "dependencies": { + "debug": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + } + } + }, + "socket.io-parser": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-3.2.0.tgz", + "integrity": "sha512-FYiBx7rc/KORMJlgsXysflWx/RIvtqZbyGLlHZvjfmPTPeuD/I8MaW7cfFrj5tRltICJdgwflhfZ3NVVbVLFQA==", + "dev": true, + "requires": { + "component-emitter": "1.2.1", + "debug": "~3.1.0", + "isarray": "2.0.1" + }, + "dependencies": { + "debug": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "isarray": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.1.tgz", + "integrity": "sha1-o32U7ZzaLVmGXJ92/llu4fM4dB4=", + "dev": true + } + } + }, + "sockjs": { + "version": "0.3.19", + "resolved": "https://registry.npmjs.org/sockjs/-/sockjs-0.3.19.tgz", + "integrity": "sha512-V48klKZl8T6MzatbLlzzRNhMepEys9Y4oGFpypBFFn1gLI/QQ9HtLLyWJNbPlwGLelOVOEijUbTTJeLLI59jLw==", + "dev": true, + "requires": { + "faye-websocket": "^0.10.0", + "uuid": "^3.0.1" + } + }, + "sockjs-client": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/sockjs-client/-/sockjs-client-1.3.0.tgz", + "integrity": "sha512-R9jxEzhnnrdxLCNln0xg5uGHqMnkhPSTzUZH2eXcR03S/On9Yvoq2wyUZILRUhZCNVu2PmwWVoyuiPz8th8zbg==", + "dev": true, + "requires": { + "debug": "^3.2.5", + "eventsource": "^1.0.7", + "faye-websocket": "~0.11.1", + "inherits": "^2.0.3", + "json3": "^3.3.2", + "url-parse": "^1.4.3" + }, + "dependencies": { + "debug": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", + "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "faye-websocket": { + "version": "0.11.1", + "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.1.tgz", + "integrity": "sha1-8O/hjE9W5PQK/H4Gxxn9XuYYjzg=", + "dev": true, + "requires": { + "websocket-driver": ">=0.5.1" + } + }, + "ms": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", + "dev": true + } + } + }, + "socks": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/socks/-/socks-2.3.2.tgz", + "integrity": "sha512-pCpjxQgOByDHLlNqlnh/mNSAxIUkyBBuwwhTcV+enZGbDaClPvHdvm6uvOwZfFJkam7cGhBNbb4JxiP8UZkRvQ==", + "dev": true, + "requires": { + "ip": "^1.1.5", + "smart-buffer": "4.0.2" + } + }, + "socks-proxy-agent": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-4.0.2.tgz", + "integrity": "sha512-NT6syHhI9LmuEMSK6Kd2V7gNv5KFZoLE7V5udWmn0de+3Mkj3UMA/AJPLyeNUVmElCurSHtUdM3ETpR3z770Wg==", + "dev": true, + "requires": { + "agent-base": "~4.2.1", + "socks": "~2.3.2" + } + }, + "source-list-map": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/source-list-map/-/source-list-map-2.0.1.tgz", + "integrity": "sha512-qnQ7gVMxGNxsiL4lEuJwe/To8UnK7fAnmbGEEH8RpLouuKbeEm0lhbQVFIrNSuB+G7tVrAlVsZgETT5nljf+Iw==", + "dev": true + }, + "source-map": { + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz", + "integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==", + "dev": true + }, + "source-map-loader": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/source-map-loader/-/source-map-loader-0.2.4.tgz", + "integrity": "sha512-OU6UJUty+i2JDpTItnizPrlpOIBLmQbWMuBg9q5bVtnHACqw1tn9nNwqJLbv0/00JjnJb/Ee5g5WS5vrRv7zIQ==", + "dev": true, + "requires": { + "async": "^2.5.0", + "loader-utils": "^1.1.0" + } + }, + "source-map-resolve": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.2.tgz", + "integrity": "sha512-MjqsvNwyz1s0k81Goz/9vRBe9SZdB09Bdw+/zYyO+3CuPk6fouTaxscHkgtE8jKvf01kVfl8riHzERQ/kefaSA==", + "dev": true, + "requires": { + "atob": "^2.1.1", + "decode-uri-component": "^0.2.0", + "resolve-url": "^0.2.1", + "source-map-url": "^0.4.0", + "urix": "^0.1.0" + } + }, + "source-map-support": { + "version": "0.5.10", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.10.tgz", + "integrity": "sha512-YfQ3tQFTK/yzlGJuX8pTwa4tifQj4QS2Mj7UegOu8jAz59MqIiMGPXxQhVQiIMNzayuUSF/jEuVnfFF5JqybmQ==", + "dev": true, + "requires": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + } + } + }, + "source-map-url": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/source-map-url/-/source-map-url-0.4.0.tgz", + "integrity": "sha1-PpNdfd1zYxuXZZlW1VEo6HtQhKM=", + "dev": true + }, + "sourcemap-codec": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.4.tgz", + "integrity": "sha512-CYAPYdBu34781kLHkaW3m6b/uUSyMOC2R61gcYMWooeuaGtjof86ZA/8T+qVPPt7np1085CR9hmMGrySwEc8Xg==", + "dev": true + }, + "spdx-correct": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.0.tgz", + "integrity": "sha512-lr2EZCctC2BNR7j7WzJ2FpDznxky1sjfxvvYEyzxNyb6lZXHODmEoJeFu4JupYlkfha1KZpJyoqiJ7pgA1qq8Q==", + "dev": true, + "requires": { + "spdx-expression-parse": "^3.0.0", + "spdx-license-ids": "^3.0.0" + } + }, + "spdx-exceptions": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.2.0.tgz", + "integrity": "sha512-2XQACfElKi9SlVb1CYadKDXvoajPgBVPn/gOQLrTvHdElaVhr7ZEbqJaRnJLVNeaI4cMEAgVCeBMKF6MWRDCRA==", + "dev": true + }, + "spdx-expression-parse": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.0.tgz", + "integrity": "sha512-Yg6D3XpRD4kkOmTpdgbUiEJFKghJH03fiC1OPll5h/0sO6neh2jqRDVHOQ4o/LMea0tgCkbMgea5ip/e+MkWyg==", + "dev": true, + "requires": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "spdx-license-ids": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.3.tgz", + "integrity": "sha512-uBIcIl3Ih6Phe3XHK1NqboJLdGfwr1UN3k6wSD1dZpmPsIkb8AGNbZYJ1fOBk834+Gxy8rpfDxrS6XLEMZMY2g==", + "dev": true + }, + "spdy": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/spdy/-/spdy-4.0.0.tgz", + "integrity": "sha512-ot0oEGT/PGUpzf/6uk4AWLqkq+irlqHXkrdbk51oWONh3bxQmBuljxPNl66zlRRcIJStWq0QkLUCPOPjgjvU0Q==", + "dev": true, + "requires": { + "debug": "^4.1.0", + "handle-thing": "^2.0.0", + "http-deceiver": "^1.2.7", + "select-hose": "^2.0.0", + "spdy-transport": "^3.0.0" + }, + "dependencies": { + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "ms": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", + "dev": true + } + } + }, + "spdy-transport": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/spdy-transport/-/spdy-transport-3.0.0.tgz", + "integrity": "sha512-hsLVFE5SjA6TCisWeJXFKniGGOpBgMLmerfO2aCyCU5s7nJ/rpAepqmFifv/GCbSbueEeAJJnmSQ2rKC/g8Fcw==", + "dev": true, + "requires": { + "debug": "^4.1.0", + "detect-node": "^2.0.4", + "hpack.js": "^2.1.6", + "obuf": "^1.1.2", + "readable-stream": "^3.0.6", + "wbuf": "^1.7.3" + }, + "dependencies": { + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "ms": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", + "dev": true + }, + "readable-stream": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.3.0.tgz", + "integrity": "sha512-EsI+s3k3XsW+fU8fQACLN59ky34AZ14LoeVZpYwmZvldCFo0r0gnelwF2TcMjLor/BTL5aDJVBMkss0dthToPw==", + "dev": true, + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + } + } + }, + "speed-measure-webpack-plugin": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/speed-measure-webpack-plugin/-/speed-measure-webpack-plugin-1.3.1.tgz", + "integrity": "sha512-qVIkJvbtS9j/UeZumbdfz0vg+QfG/zxonAjzefZrqzkr7xOncLVXkeGbTpzd1gjCBM4PmVNkWlkeTVhgskAGSQ==", + "dev": true, + "requires": { + "chalk": "^2.0.1" + } + }, + "split-string": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz", + "integrity": "sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw==", + "dev": true, + "requires": { + "extend-shallow": "^3.0.0" + } + }, + "sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", + "dev": true + }, + "sshpk": { + "version": "1.16.1", + "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.16.1.tgz", + "integrity": "sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg==", + "dev": true, + "requires": { + "asn1": "~0.2.3", + "assert-plus": "^1.0.0", + "bcrypt-pbkdf": "^1.0.0", + "dashdash": "^1.12.0", + "ecc-jsbn": "~0.1.1", + "getpass": "^0.1.1", + "jsbn": "~0.1.0", + "safer-buffer": "^2.0.2", + "tweetnacl": "~0.14.0" + } + }, + "ssri": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/ssri/-/ssri-5.3.0.tgz", + "integrity": "sha512-XRSIPqLij52MtgoQavH/x/dU1qVKtWUAAZeOHsR9c2Ddi4XerFy3mc1alf+dLJKl9EUIm/Ht+EowFkTUOA6GAQ==", + "dev": true, + "requires": { + "safe-buffer": "^5.1.1" + } + }, + "static-extend": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/static-extend/-/static-extend-0.1.2.tgz", + "integrity": "sha1-YICcOcv/VTNyJv1eC1IPNB8ftcY=", + "dev": true, + "requires": { + "define-property": "^0.2.5", + "object-copy": "^0.1.0" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "requires": { + "is-descriptor": "^0.1.0" + } + } + } + }, + "stats-webpack-plugin": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/stats-webpack-plugin/-/stats-webpack-plugin-0.7.0.tgz", + "integrity": "sha512-NT0YGhwuQ0EOX+uPhhUcI6/+1Sq/pMzNuSCBVT4GbFl/ac6I/JZefBcjlECNfAb1t3GOx5dEj1Z7x0cAxeeVLQ==", + "dev": true, + "requires": { + "lodash": "^4.17.4" + } + }, + "statuses": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.4.0.tgz", + "integrity": "sha512-zhSCtt8v2NDrRlPQpCNtw/heZLtfUDqxBM1udqikb/Hbk52LK4nQSwr10u77iopCW5LsyHpuXS0GnEc48mLeew==", + "dev": true + }, + "stdout-stream": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/stdout-stream/-/stdout-stream-1.4.1.tgz", + "integrity": "sha512-j4emi03KXqJWcIeF8eIXkjMFN1Cmb8gUlDYGeBALLPo5qdyTfA9bOtl8m33lRoC+vFMkP3gl0WsDr6+gzxbbTA==", + "dev": true, + "optional": true, + "requires": { + "readable-stream": "^2.0.1" + } + }, + "stream-browserify": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/stream-browserify/-/stream-browserify-2.0.2.tgz", + "integrity": "sha512-nX6hmklHs/gr2FuxYDltq8fJA1GDlxKQCz8O/IM4atRqBH8OORmBNgfvW5gG10GT/qQ9u0CzIvr2X5Pkt6ntqg==", + "dev": true, + "requires": { + "inherits": "~2.0.1", + "readable-stream": "^2.0.2" + } + }, + "stream-each": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/stream-each/-/stream-each-1.2.3.tgz", + "integrity": "sha512-vlMC2f8I2u/bZGqkdfLQW/13Zihpej/7PmSiMQsbYddxuTsJp8vRe2x2FvVExZg7FaOds43ROAuFJwPR4MTZLw==", + "dev": true, + "requires": { + "end-of-stream": "^1.1.0", + "stream-shift": "^1.0.0" + } + }, + "stream-http": { + "version": "2.8.3", + "resolved": "https://registry.npmjs.org/stream-http/-/stream-http-2.8.3.tgz", + "integrity": "sha512-+TSkfINHDo4J+ZobQLWiMouQYB+UVYFttRA94FpEzzJ7ZdqcL4uUUQ7WkdkI4DSozGmgBUE/a47L+38PenXhUw==", + "dev": true, + "requires": { + "builtin-status-codes": "^3.0.0", + "inherits": "^2.0.1", + "readable-stream": "^2.3.6", + "to-arraybuffer": "^1.0.0", + "xtend": "^4.0.0" + } + }, + "stream-shift": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.0.tgz", + "integrity": "sha1-1cdSgl5TZ+eG944Y5EXqIjoVWVI=", + "dev": true + }, + "streamroller": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/streamroller/-/streamroller-1.0.4.tgz", + "integrity": "sha512-Wc2Gm5ygjSX8ZpW9J7Y9FwiSzTlKSvcl0FTTMd3rn7RoxDXpBW+xD9TY5sWL2n0UR61COB0LG1BQvN6nTUQbLQ==", + "dev": true, + "requires": { + "async": "^2.6.1", + "date-format": "^2.0.0", + "debug": "^3.1.0", + "fs-extra": "^7.0.0", + "lodash": "^4.17.10" + }, + "dependencies": { + "debug": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", + "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "ms": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", + "dev": true + } + } + }, + "string-width": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", + "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", + "dev": true, + "requires": { + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" + } + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "requires": { + "safe-buffer": "~5.1.0" + } + }, + "strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "dev": true, + "requires": { + "ansi-regex": "^2.0.0" + } + }, + "strip-bom": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz", + "integrity": "sha1-YhmoVhZSBJHzV4i9vxRHqZx+aw4=", + "dev": true, + "optional": true, + "requires": { + "is-utf8": "^0.2.0" + } + }, + "strip-eof": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz", + "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=", + "dev": true + }, + "strip-indent": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-1.0.1.tgz", + "integrity": "sha1-DHlipq3vp7vUrDZkYKY4VSrhoKI=", + "dev": true, + "optional": true, + "requires": { + "get-stdin": "^4.0.1" + } + }, + "style-loader": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/style-loader/-/style-loader-0.23.1.tgz", + "integrity": "sha512-XK+uv9kWwhZMZ1y7mysB+zoihsEj4wneFWAS5qoiLwzW0WzSqMrrsIy+a3zkQJq0ipFtBpX5W3MqyRIBF/WFGg==", + "dev": true, + "requires": { + "loader-utils": "^1.1.0", + "schema-utils": "^1.0.0" + } + }, + "stylus": { + "version": "0.54.5", + "resolved": "https://registry.npmjs.org/stylus/-/stylus-0.54.5.tgz", + "integrity": "sha1-QrlWCTHKcJDOhRWnmLqeaqPW3Hk=", + "dev": true, + "requires": { + "css-parse": "1.7.x", + "debug": "*", + "glob": "7.0.x", + "mkdirp": "0.5.x", + "sax": "0.5.x", + "source-map": "0.1.x" + }, + "dependencies": { + "glob": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.0.6.tgz", + "integrity": "sha1-IRuvr0nlJbjNkyYNFKsTYVKz9Xo=", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.2", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "source-map": { + "version": "0.1.43", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.1.43.tgz", + "integrity": "sha1-wkvBRspRfBRx9drL4lcbK3+eM0Y=", + "dev": true, + "requires": { + "amdefine": ">=0.0.4" + } + } + } + }, + "stylus-loader": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/stylus-loader/-/stylus-loader-3.0.2.tgz", + "integrity": "sha512-+VomPdZ6a0razP+zinir61yZgpw2NfljeSsdUF5kJuEzlo3khXhY19Fn6l8QQz1GRJGtMCo8nG5C04ePyV7SUA==", + "dev": true, + "requires": { + "loader-utils": "^1.0.2", + "lodash.clonedeep": "^4.5.0", + "when": "~3.6.x" + } + }, + "supports-color": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", + "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + }, + "symbol-observable": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-1.2.0.tgz", + "integrity": "sha512-e900nM8RRtGhlV36KGEU9k65K3mPb1WV70OdjfxlG2EAuM1noi/E/BaW/uMhL7bPEssK8QV57vN3esixjUvcXQ==", + "dev": true + }, + "tapable": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-1.1.1.tgz", + "integrity": "sha512-9I2ydhj8Z9veORCw5PRm4u9uebCn0mcCa6scWoNcbZ6dAtoo2618u9UUzxgmsCOreJpqDDuv61LvwofW7hLcBA==", + "dev": true + }, + "tar": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/tar/-/tar-2.2.1.tgz", + "integrity": "sha1-jk0qJWwOIYXGsYrWlK7JaLg8sdE=", + "dev": true, + "optional": true, + "requires": { + "block-stream": "*", + "fstream": "^1.0.2", + "inherits": "2" + } + }, + "terser": { + "version": "3.17.0", + "resolved": "https://registry.npmjs.org/terser/-/terser-3.17.0.tgz", + "integrity": "sha512-/FQzzPJmCpjAH9Xvk2paiWrFq+5M6aVOf+2KRbwhByISDX/EujxsK+BAvrhb6H+2rtrLCHK9N01wO014vrIwVQ==", + "dev": true, + "requires": { + "commander": "^2.19.0", + "source-map": "~0.6.1", + "source-map-support": "~0.5.10" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + } + } + }, + "terser-webpack-plugin": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-1.2.2.tgz", + "integrity": "sha512-1DMkTk286BzmfylAvLXwpJrI7dWa5BnFmscV/2dCr8+c56egFcbaeFAl7+sujAjdmpLam21XRdhA4oifLyiWWg==", + "dev": true, + "requires": { + "cacache": "^11.0.2", + "find-cache-dir": "^2.0.0", + "schema-utils": "^1.0.0", + "serialize-javascript": "^1.4.0", + "source-map": "^0.6.1", + "terser": "^3.16.1", + "webpack-sources": "^1.1.0", + "worker-farm": "^1.5.2" + }, + "dependencies": { + "cacache": { + "version": "11.3.2", + "resolved": "https://registry.npmjs.org/cacache/-/cacache-11.3.2.tgz", + "integrity": "sha512-E0zP4EPGDOaT2chM08Als91eYnf8Z+eH1awwwVsngUmgppfM5jjJ8l3z5vO5p5w/I3LsiXawb1sW0VY65pQABg==", + "dev": true, + "requires": { + "bluebird": "^3.5.3", + "chownr": "^1.1.1", + "figgy-pudding": "^3.5.1", + "glob": "^7.1.3", + "graceful-fs": "^4.1.15", + "lru-cache": "^5.1.1", + "mississippi": "^3.0.0", + "mkdirp": "^0.5.1", + "move-concurrently": "^1.0.1", + "promise-inflight": "^1.0.1", + "rimraf": "^2.6.2", + "ssri": "^6.0.1", + "unique-filename": "^1.1.1", + "y18n": "^4.0.0" + } + }, + "find-cache-dir": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-2.1.0.tgz", + "integrity": "sha512-Tq6PixE0w/VMFfCgbONnkiQIVol/JJL7nRMi20fqzA4NRs9AfeqMGeRdPi3wIhYkxjeBaWh2rxwapn5Tu3IqOQ==", + "dev": true, + "requires": { + "commondir": "^1.0.1", + "make-dir": "^2.0.0", + "pkg-dir": "^3.0.0" + } + }, + "find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "dev": true, + "requires": { + "locate-path": "^3.0.0" + } + }, + "locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "dev": true, + "requires": { + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" + } + }, + "lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "requires": { + "yallist": "^3.0.2" + } + }, + "make-dir": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz", + "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==", + "dev": true, + "requires": { + "pify": "^4.0.1", + "semver": "^5.6.0" + } + }, + "mississippi": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/mississippi/-/mississippi-3.0.0.tgz", + "integrity": "sha512-x471SsVjUtBRtcvd4BzKE9kFC+/2TeWgKCgw0bZcw1b9l2X3QX5vCWgF+KaZaYm87Ss//rHnWryupDrgLvmSkA==", + "dev": true, + "requires": { + "concat-stream": "^1.5.0", + "duplexify": "^3.4.2", + "end-of-stream": "^1.1.0", + "flush-write-stream": "^1.0.0", + "from2": "^2.1.0", + "parallel-transform": "^1.1.0", + "pump": "^3.0.0", + "pumpify": "^1.3.3", + "stream-each": "^1.1.0", + "through2": "^2.0.0" + } + }, + "p-limit": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.2.0.tgz", + "integrity": "sha512-pZbTJpoUsCzV48Mc9Nh51VbwO0X9cuPFE8gYwx9BTCt9SF8/b7Zljd2fVgOxhIF/HDTKgpVzs+GPhyKfjLLFRQ==", + "dev": true, + "requires": { + "p-try": "^2.0.0" + } + }, + "p-locate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "dev": true, + "requires": { + "p-limit": "^2.0.0" + } + }, + "p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true + }, + "pify": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", + "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", + "dev": true + }, + "pkg-dir": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-3.0.0.tgz", + "integrity": "sha512-/E57AYkoeQ25qkxMj5PBOVgF8Kiu/h7cYS30Z5+R7WaiCCBfLq58ZI/dSeaEKb9WVJV5n/03QwrN3IeWIFllvw==", + "dev": true, + "requires": { + "find-up": "^3.0.0" + } + }, + "pump": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "dev": true, + "requires": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + }, + "ssri": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/ssri/-/ssri-6.0.1.tgz", + "integrity": "sha512-3Wge10hNcT1Kur4PDFwEieXSCMCJs/7WvSACcrMYrNp+b8kDL1/0wJch5Ni2WrtwEa2IO8OsVfeKIciKCDx/QA==", + "dev": true, + "requires": { + "figgy-pudding": "^3.5.1" + } + }, + "yallist": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.0.3.tgz", + "integrity": "sha512-S+Zk8DEWE6oKpV+vI3qWkaK+jSbIK86pCwe2IF/xwIpQ8jEuxpw9NyaGjmp9+BoJv5FV2piqCDcoCtStppiq2A==", + "dev": true + } + } + }, + "through": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", + "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=", + "dev": true + }, + "through2": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", + "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", + "dev": true, + "requires": { + "readable-stream": "~2.3.6", + "xtend": "~4.0.1" + } + }, + "thunky": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/thunky/-/thunky-1.0.3.tgz", + "integrity": "sha512-YwT8pjmNcAXBZqrubu22P4FYsh2D4dxRmnWBOL8Jk8bUcRUtc5326kx32tuTmFDAZtLOGEVNl8POAR8j896Iow==", + "dev": true + }, + "timers-browserify": { + "version": "2.0.10", + "resolved": "https://registry.npmjs.org/timers-browserify/-/timers-browserify-2.0.10.tgz", + "integrity": "sha512-YvC1SV1XdOUaL6gx5CoGroT3Gu49pK9+TZ38ErPldOWW4j49GI1HKs9DV+KGq/w6y+LZ72W1c8cKz2vzY+qpzg==", + "dev": true, + "requires": { + "setimmediate": "^1.0.4" + } + }, + "tmp": { + "version": "0.0.33", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", + "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", + "dev": true, + "requires": { + "os-tmpdir": "~1.0.2" + } + }, + "to-array": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/to-array/-/to-array-0.1.4.tgz", + "integrity": "sha1-F+bBH3PdTz10zaek/zI46a2b+JA=", + "dev": true + }, + "to-arraybuffer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/to-arraybuffer/-/to-arraybuffer-1.0.1.tgz", + "integrity": "sha1-fSKbH8xjfkZsoIEYCDanqr/4P0M=", + "dev": true + }, + "to-fast-properties": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-1.0.3.tgz", + "integrity": "sha1-uDVx+k2MJbguIxsG46MFXeTKGkc=", + "dev": true + }, + "to-object-path": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/to-object-path/-/to-object-path-0.3.0.tgz", + "integrity": "sha1-KXWIt7Dn4KwI4E5nL4XB9JmeF68=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "to-regex": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/to-regex/-/to-regex-3.0.2.tgz", + "integrity": "sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw==", + "dev": true, + "requires": { + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "regex-not": "^1.0.2", + "safe-regex": "^1.1.0" + } + }, + "to-regex-range": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", + "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", + "dev": true, + "requires": { + "is-number": "^3.0.0", + "repeat-string": "^1.6.1" + } + }, + "tough-cookie": { + "version": "2.4.3", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.4.3.tgz", + "integrity": "sha512-Q5srk/4vDM54WJsJio3XNn6K2sCG+CQ8G5Wz6bZhRZoAe/+TxjWB/GlFAnYEbkYVlON9FMk/fE3h2RLpPXo4lQ==", + "dev": true, + "requires": { + "psl": "^1.1.24", + "punycode": "^1.4.1" + }, + "dependencies": { + "punycode": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", + "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=", + "dev": true + } + } + }, + "tree-kill": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.1.tgz", + "integrity": "sha512-4hjqbObwlh2dLyW4tcz0Ymw0ggoaVDMveUB9w8kFSQScdRLo0gxO9J7WFcUBo+W3C1TLdFIEwNOWebgZZ0RH9Q==", + "dev": true + }, + "trim-newlines": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-1.0.0.tgz", + "integrity": "sha1-WIeWa7WCpFA6QetST301ARgVphM=", + "dev": true, + "optional": true + }, + "trim-right": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/trim-right/-/trim-right-1.0.1.tgz", + "integrity": "sha1-yy4SAwZ+DI3h9hQJS5/kVwTqYAM=", + "dev": true + }, + "true-case-path": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/true-case-path/-/true-case-path-1.0.3.tgz", + "integrity": "sha512-m6s2OdQe5wgpFMC+pAJ+q9djG82O2jcHPOI6RNg1yy9rCYR+WD6Nbpl32fDpfC56nirdRy+opFa/Vk7HYhqaew==", + "dev": true, + "optional": true, + "requires": { + "glob": "^7.1.2" + } + }, + "ts-node": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-7.0.1.tgz", + "integrity": "sha512-BVwVbPJRspzNh2yfslyT1PSbl5uIk03EZlb493RKHN4qej/D06n1cEhjlOJG69oFsE7OT8XjpTUcYf6pKTLMhw==", + "dev": true, + "requires": { + "arrify": "^1.0.0", + "buffer-from": "^1.1.0", + "diff": "^3.1.0", + "make-error": "^1.1.1", + "minimist": "^1.2.0", + "mkdirp": "^0.5.1", + "source-map-support": "^0.5.6", + "yn": "^2.0.0" + } + }, + "tslib": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.9.3.tgz", + "integrity": "sha512-4krF8scpejhaOgqzBEcGM7yDIEfi0/8+8zDRZhNZZ2kjmHJ4hv3zCbQWxoJGz1iw5U0Jl0nma13xzHXcncMavQ==" + }, + "tslint": { + "version": "5.11.0", + "resolved": "https://registry.npmjs.org/tslint/-/tslint-5.11.0.tgz", + "integrity": "sha1-mPMMAurjzecAYgHkwzywi0hYHu0=", + "dev": true, + "requires": { + "babel-code-frame": "^6.22.0", + "builtin-modules": "^1.1.1", + "chalk": "^2.3.0", + "commander": "^2.12.1", + "diff": "^3.2.0", + "glob": "^7.1.1", + "js-yaml": "^3.7.0", + "minimatch": "^3.0.4", + "resolve": "^1.3.2", + "semver": "^5.3.0", + "tslib": "^1.8.0", + "tsutils": "^2.27.2" + } + }, + "tsutils": { + "version": "2.29.0", + "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-2.29.0.tgz", + "integrity": "sha512-g5JVHCIJwzfISaXpXE1qvNalca5Jwob6FjI4AoPlqMusJ6ftFE7IkkFoMhVLRgK+4Kx3gkzb8UZK5t5yTTvEmA==", + "dev": true, + "requires": { + "tslib": "^1.8.1" + } + }, + "tty-browserify": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/tty-browserify/-/tty-browserify-0.0.0.tgz", + "integrity": "sha1-oVe6QC2iTpv5V/mqadUk7tQpAaY=", + "dev": true + }, + "tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", + "dev": true, + "requires": { + "safe-buffer": "^5.0.1" + } + }, + "tweetnacl": { + "version": "0.14.5", + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", + "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=", + "dev": true + }, + "type-is": { + "version": "1.6.16", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.16.tgz", + "integrity": "sha512-HRkVv/5qY2G6I8iab9cI7v1bOIdhm94dVjQCPFElW9W+3GeDOSHmy2EBYe4VTApuzolPcmgFTN3ftVJRKR2J9Q==", + "dev": true, + "requires": { + "media-typer": "0.3.0", + "mime-types": "~2.1.18" + } + }, + "typedarray": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", + "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=", + "dev": true + }, + "typescript": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.2.4.tgz", + "integrity": "sha512-0RNDbSdEokBeEAkgNbxJ+BLwSManFy9TeXz8uW+48j/xhEXv1ePME60olyzw2XzUqUBNAYFeJadIqAgNqIACwg==", + "dev": true + }, + "uglify-js": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.5.2.tgz", + "integrity": "sha512-imog1WIsi9Yb56yRt5TfYVxGmnWs3WSGU73ieSOlMVFwhJCA9W8fqFFMMj4kgDqiS/80LGdsYnWL7O9UcjEBlg==", + "dev": true, + "optional": true, + "requires": { + "commander": "~2.19.0", + "source-map": "~0.6.1" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "optional": true + } + } + }, + "ultron": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ultron/-/ultron-1.1.1.tgz", + "integrity": "sha512-UIEXBNeYmKptWH6z8ZnqTeS8fV74zG0/eRU9VGkpzz+LIJNs8W/zM/L+7ctCkRrgbNnnR0xxw4bKOr0cW0N0Og==", + "dev": true + }, + "union-value": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.0.tgz", + "integrity": "sha1-XHHDTLW61dzr4+oM0IIHulqhrqQ=", + "dev": true, + "requires": { + "arr-union": "^3.1.0", + "get-value": "^2.0.6", + "is-extendable": "^0.1.1", + "set-value": "^0.4.3" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + }, + "set-value": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/set-value/-/set-value-0.4.3.tgz", + "integrity": "sha1-fbCPnT0i3H945Trzw79GZuzfzPE=", + "dev": true, + "requires": { + "extend-shallow": "^2.0.1", + "is-extendable": "^0.1.1", + "is-plain-object": "^2.0.1", + "to-object-path": "^0.3.0" + } + } + } + }, + "unique-filename": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-1.1.1.tgz", + "integrity": "sha512-Vmp0jIp2ln35UTXuryvjzkjGdRyf9b2lTXuSYUiPmzRcl3FDtYqAwOnTJkAngD9SWhnoJzDbTKwaOrZ+STtxNQ==", + "dev": true, + "requires": { + "unique-slug": "^2.0.0" + } + }, + "unique-slug": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-2.0.1.tgz", + "integrity": "sha512-n9cU6+gITaVu7VGj1Z8feKMmfAjEAQGhwD9fE3zvpRRa0wEIx8ODYkVGfSc94M2OX00tUFV8wH3zYbm1I8mxFg==", + "dev": true, + "requires": { + "imurmurhash": "^0.1.4" + } + }, + "universalify": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", + "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", + "dev": true + }, + "unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=", + "dev": true + }, + "unset-value": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unset-value/-/unset-value-1.0.0.tgz", + "integrity": "sha1-g3aHP30jNRef+x5vw6jtDfyKtVk=", + "dev": true, + "requires": { + "has-value": "^0.3.1", + "isobject": "^3.0.0" + }, + "dependencies": { + "has-value": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/has-value/-/has-value-0.3.1.tgz", + "integrity": "sha1-ex9YutpiyoJ+wKIHgCVlSEWZXh8=", + "dev": true, + "requires": { + "get-value": "^2.0.3", + "has-values": "^0.1.4", + "isobject": "^2.0.0" + }, + "dependencies": { + "isobject": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz", + "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=", + "dev": true, + "requires": { + "isarray": "1.0.0" + } + } + } + }, + "has-values": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/has-values/-/has-values-0.1.4.tgz", + "integrity": "sha1-bWHeldkd/Km5oCCJrThL/49it3E=", + "dev": true + } + } + }, + "upath": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/upath/-/upath-1.1.2.tgz", + "integrity": "sha512-kXpym8nmDmlCBr7nKdIx8P2jNBa+pBpIUFRnKJ4dr8htyYGJFokkr2ZvERRtUN+9SY+JqXouNgUPtv6JQva/2Q==", + "dev": true + }, + "uri-js": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz", + "integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==", + "dev": true, + "requires": { + "punycode": "^2.1.0" + } + }, + "urix": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/urix/-/urix-0.1.0.tgz", + "integrity": "sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI=", + "dev": true + }, + "url": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/url/-/url-0.11.0.tgz", + "integrity": "sha1-ODjpfPxgUh63PFJajlW/3Z4uKPE=", + "dev": true, + "requires": { + "punycode": "1.3.2", + "querystring": "0.2.0" + }, + "dependencies": { + "punycode": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz", + "integrity": "sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0=", + "dev": true + } + } + }, + "url-parse": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.4.4.tgz", + "integrity": "sha512-/92DTTorg4JjktLNLe6GPS2/RvAd/RGr6LuktmWSMLEOa6rjnlrFXNgSbSmkNvCoL2T028A0a1JaJLzRMlFoHg==", + "dev": true, + "requires": { + "querystringify": "^2.0.0", + "requires-port": "^1.0.0" + } + }, + "use": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz", + "integrity": "sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==", + "dev": true + }, + "useragent": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/useragent/-/useragent-2.3.0.tgz", + "integrity": "sha512-4AoH4pxuSvHCjqLO04sU6U/uE65BYza8l/KKBS0b0hnUPWi+cQ2BpeTEwejCSx9SPV5/U03nniDTrWx5NrmKdw==", + "dev": true, + "requires": { + "lru-cache": "4.1.x", + "tmp": "0.0.x" + } + }, + "util": { + "version": "0.11.1", + "resolved": "https://registry.npmjs.org/util/-/util-0.11.1.tgz", + "integrity": "sha512-HShAsny+zS2TZfaXxD9tYj4HQGlBezXZMZuM/S5PKLLoZkShZiGk9o5CzukI1LVHZvjdvZ2Sj1aW/Ndn2NB/HQ==", + "dev": true, + "requires": { + "inherits": "2.0.3" + } + }, + "util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", + "dev": true + }, + "utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=", + "dev": true + }, + "uuid": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz", + "integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==", + "dev": true + }, + "validate-npm-package-license": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", + "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", + "dev": true, + "requires": { + "spdx-correct": "^3.0.0", + "spdx-expression-parse": "^3.0.0" + } + }, + "validate-npm-package-name": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/validate-npm-package-name/-/validate-npm-package-name-3.0.0.tgz", + "integrity": "sha1-X6kS2B630MdK/BQN5zF/DKffQ34=", + "dev": true, + "requires": { + "builtins": "^1.0.3" + } + }, + "vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=", + "dev": true + }, + "verror": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", + "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", + "dev": true, + "requires": { + "assert-plus": "^1.0.0", + "core-util-is": "1.0.2", + "extsprintf": "^1.2.0" + } + }, + "vm-browserify": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/vm-browserify/-/vm-browserify-0.0.4.tgz", + "integrity": "sha1-XX6kW7755Kb/ZflUOOCofDV9WnM=", + "dev": true, + "requires": { + "indexof": "0.0.1" + } + }, + "void-elements": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/void-elements/-/void-elements-2.0.1.tgz", + "integrity": "sha1-wGavtYK7HLQSjWDqkjkulNXp2+w=", + "dev": true + }, + "watchpack": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-1.6.0.tgz", + "integrity": "sha512-i6dHe3EyLjMmDlU1/bGQpEw25XSjkJULPuAVKCbNRefQVq48yXKUpwg538F7AZTf9kyr57zj++pQFltUa5H7yA==", + "dev": true, + "requires": { + "chokidar": "^2.0.2", + "graceful-fs": "^4.1.2", + "neo-async": "^2.5.0" + } + }, + "wbuf": { + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/wbuf/-/wbuf-1.7.3.tgz", + "integrity": "sha512-O84QOnr0icsbFGLS0O3bI5FswxzRr8/gHwWkDlQFskhSPryQXvrTMxjxGP4+iWYoauLoBvfDpkrOauZ+0iZpDA==", + "dev": true, + "requires": { + "minimalistic-assert": "^1.0.0" + } + }, + "webdriver-js-extender": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/webdriver-js-extender/-/webdriver-js-extender-2.1.0.tgz", + "integrity": "sha512-lcUKrjbBfCK6MNsh7xaY2UAUmZwe+/ib03AjVOpFobX4O7+83BUveSrLfU0Qsyb1DaKJdQRbuU+kM9aZ6QUhiQ==", + "dev": true, + "requires": { + "@types/selenium-webdriver": "^3.0.0", + "selenium-webdriver": "^3.0.1" + } + }, + "webpack": { + "version": "4.29.0", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-4.29.0.tgz", + "integrity": "sha512-pxdGG0keDBtamE1mNvT5zyBdx+7wkh6mh7uzMOo/uRQ/fhsdj5FXkh/j5mapzs060forql1oXqXN9HJGju+y7w==", + "dev": true, + "requires": { + "@webassemblyjs/ast": "1.7.11", + "@webassemblyjs/helper-module-context": "1.7.11", + "@webassemblyjs/wasm-edit": "1.7.11", + "@webassemblyjs/wasm-parser": "1.7.11", + "acorn": "^6.0.5", + "acorn-dynamic-import": "^4.0.0", + "ajv": "^6.1.0", + "ajv-keywords": "^3.1.0", + "chrome-trace-event": "^1.0.0", + "enhanced-resolve": "^4.1.0", + "eslint-scope": "^4.0.0", + "json-parse-better-errors": "^1.0.2", + "loader-runner": "^2.3.0", + "loader-utils": "^1.1.0", + "memory-fs": "~0.4.1", + "micromatch": "^3.1.8", + "mkdirp": "~0.5.0", + "neo-async": "^2.5.0", + "node-libs-browser": "^2.0.0", + "schema-utils": "^0.4.4", + "tapable": "^1.1.0", + "terser-webpack-plugin": "^1.1.0", + "watchpack": "^1.5.0", + "webpack-sources": "^1.3.0" + }, + "dependencies": { + "schema-utils": { + "version": "0.4.7", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-0.4.7.tgz", + "integrity": "sha512-v/iwU6wvwGK8HbU9yi3/nhGzP0yGSuhQMzL6ySiec1FSrZZDkhm4noOSWzrNFo/jEc+SJY6jRTwuwbSXJPDUnQ==", + "dev": true, + "requires": { + "ajv": "^6.1.0", + "ajv-keywords": "^3.1.0" + } + } + } + }, + "webpack-core": { + "version": "0.6.9", + "resolved": "https://registry.npmjs.org/webpack-core/-/webpack-core-0.6.9.tgz", + "integrity": "sha1-/FcViMhVjad76e+23r3Fo7FyvcI=", + "dev": true, + "requires": { + "source-list-map": "~0.1.7", + "source-map": "~0.4.1" + }, + "dependencies": { + "source-list-map": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/source-list-map/-/source-list-map-0.1.8.tgz", + "integrity": "sha1-xVCyq1Qn9rPyH1r+rYjE9Vh7IQY=", + "dev": true + }, + "source-map": { + "version": "0.4.4", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.4.4.tgz", + "integrity": "sha1-66T12pwNyZneaAMti092FzZSA2s=", + "dev": true, + "requires": { + "amdefine": ">=0.0.4" + } + } + } + }, + "webpack-dev-middleware": { + "version": "3.5.1", + "resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-3.5.1.tgz", + "integrity": "sha512-4dwCh/AyMOYAybggUr8fiCkRnjVDp+Cqlr9c+aaNB3GJYgRGYQWJ1YX/WAKUNA9dPNHZ6QSN2lYDKqjKSI8Vqw==", + "dev": true, + "requires": { + "memory-fs": "~0.4.1", + "mime": "^2.3.1", + "range-parser": "^1.0.3", + "webpack-log": "^2.0.0" + }, + "dependencies": { + "mime": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/mime/-/mime-2.4.2.tgz", + "integrity": "sha512-zJBfZDkwRu+j3Pdd2aHsR5GfH2jIWhmL1ZzBoc+X+3JEti2hbArWcyJ+1laC1D2/U/W1a/+Cegj0/OnEU2ybjg==", + "dev": true + } + } + }, + "webpack-dev-server": { + "version": "3.1.14", + "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-3.1.14.tgz", + "integrity": "sha512-mGXDgz5SlTxcF3hUpfC8hrQ11yhAttuUQWf1Wmb+6zo3x6rb7b9mIfuQvAPLdfDRCGRGvakBWHdHOa0I9p/EVQ==", + "dev": true, + "requires": { + "ansi-html": "0.0.7", + "bonjour": "^3.5.0", + "chokidar": "^2.0.0", + "compression": "^1.5.2", + "connect-history-api-fallback": "^1.3.0", + "debug": "^3.1.0", + "del": "^3.0.0", + "express": "^4.16.2", + "html-entities": "^1.2.0", + "http-proxy-middleware": "~0.18.0", + "import-local": "^2.0.0", + "internal-ip": "^3.0.1", + "ip": "^1.1.5", + "killable": "^1.0.0", + "loglevel": "^1.4.1", + "opn": "^5.1.0", + "portfinder": "^1.0.9", + "schema-utils": "^1.0.0", + "selfsigned": "^1.9.1", + "semver": "^5.6.0", + "serve-index": "^1.7.2", + "sockjs": "0.3.19", + "sockjs-client": "1.3.0", + "spdy": "^4.0.0", + "strip-ansi": "^3.0.0", + "supports-color": "^5.1.0", + "url": "^0.11.0", + "webpack-dev-middleware": "3.4.0", + "webpack-log": "^2.0.0", + "yargs": "12.0.2" + }, + "dependencies": { + "ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "dev": true + }, + "camelcase": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-4.1.0.tgz", + "integrity": "sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0=", + "dev": true + }, + "cliui": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-4.1.0.tgz", + "integrity": "sha512-4FG+RSG9DL7uEwRUZXZn3SS34DiDPfzP0VOiEwtUWlE+AR2EIg+hSyvrIgUUfhdgR/UkAeW2QHgeP+hWrXs7jQ==", + "dev": true, + "requires": { + "string-width": "^2.1.1", + "strip-ansi": "^4.0.0", + "wrap-ansi": "^2.0.0" + }, + "dependencies": { + "strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "dev": true, + "requires": { + "ansi-regex": "^3.0.0" + } + } + } + }, + "cross-spawn": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", + "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", + "dev": true, + "requires": { + "nice-try": "^1.0.4", + "path-key": "^2.0.1", + "semver": "^5.5.0", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + } + }, + "debug": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", + "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "decamelize": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-2.0.0.tgz", + "integrity": "sha512-Ikpp5scV3MSYxY39ymh45ZLEecsTdv/Xj2CaQfI8RLMuwi7XvjX9H/fhraiSuU+C5w5NTDu4ZU72xNiZnurBPg==", + "dev": true, + "requires": { + "xregexp": "4.0.0" + } + }, + "execa": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz", + "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==", + "dev": true, + "requires": { + "cross-spawn": "^6.0.0", + "get-stream": "^4.0.0", + "is-stream": "^1.1.0", + "npm-run-path": "^2.0.0", + "p-finally": "^1.0.0", + "signal-exit": "^3.0.0", + "strip-eof": "^1.0.0" + } + }, + "find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "dev": true, + "requires": { + "locate-path": "^3.0.0" + } + }, + "get-stream": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", + "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", + "dev": true, + "requires": { + "pump": "^3.0.0" + } + }, + "invert-kv": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-2.0.0.tgz", + "integrity": "sha512-wPVv/y/QQ/Uiirj/vh3oP+1Ww+AWehmi1g5fFWGPF6IpCBCDVrhgHRMvrLfdYcwDh3QJbGXDW4JAuzxElLSqKA==", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "dev": true + }, + "lcid": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/lcid/-/lcid-2.0.0.tgz", + "integrity": "sha512-avPEb8P8EGnwXKClwsNUgryVjllcRqtMYa49NTsbQagYuT1DcXnl1915oxWjoyGrXR6zH/Y0Zc96xWsPcoDKeA==", + "dev": true, + "requires": { + "invert-kv": "^2.0.0" + } + }, + "locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "dev": true, + "requires": { + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" + } + }, + "mime": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/mime/-/mime-2.4.2.tgz", + "integrity": "sha512-zJBfZDkwRu+j3Pdd2aHsR5GfH2jIWhmL1ZzBoc+X+3JEti2hbArWcyJ+1laC1D2/U/W1a/+Cegj0/OnEU2ybjg==", + "dev": true + }, + "ms": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", + "dev": true + }, + "os-locale": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-3.1.0.tgz", + "integrity": "sha512-Z8l3R4wYWM40/52Z+S265okfFj8Kt2cC2MKY+xNi3kFs+XGI7WXu/I309QQQYbRW4ijiZ+yxs9pqEhJh0DqW3Q==", + "dev": true, + "requires": { + "execa": "^1.0.0", + "lcid": "^2.0.0", + "mem": "^4.0.0" + } + }, + "p-limit": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.2.0.tgz", + "integrity": "sha512-pZbTJpoUsCzV48Mc9Nh51VbwO0X9cuPFE8gYwx9BTCt9SF8/b7Zljd2fVgOxhIF/HDTKgpVzs+GPhyKfjLLFRQ==", + "dev": true, + "requires": { + "p-try": "^2.0.0" + } + }, + "p-locate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "dev": true, + "requires": { + "p-limit": "^2.0.0" + } + }, + "p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true + }, + "pump": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "dev": true, + "requires": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "string-width": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", + "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "dev": true, + "requires": { + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^4.0.0" + }, + "dependencies": { + "strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "dev": true, + "requires": { + "ansi-regex": "^3.0.0" + } + } + } + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + }, + "webpack-dev-middleware": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-3.4.0.tgz", + "integrity": "sha512-Q9Iyc0X9dP9bAsYskAVJ/hmIZZQwf/3Sy4xCAZgL5cUkjZmUZLt4l5HpbST/Pdgjn3u6pE7u5OdGd1apgzRujA==", + "dev": true, + "requires": { + "memory-fs": "~0.4.1", + "mime": "^2.3.1", + "range-parser": "^1.0.3", + "webpack-log": "^2.0.0" + } + }, + "which-module": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", + "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=", + "dev": true + }, + "yargs": { + "version": "12.0.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-12.0.2.tgz", + "integrity": "sha512-e7SkEx6N6SIZ5c5H22RTZae61qtn3PYUE8JYbBFlK9sYmh3DMQ6E5ygtaG/2BW0JZi4WGgTR2IV5ChqlqrDGVQ==", + "dev": true, + "requires": { + "cliui": "^4.0.0", + "decamelize": "^2.0.0", + "find-up": "^3.0.0", + "get-caller-file": "^1.0.1", + "os-locale": "^3.0.0", + "require-directory": "^2.1.1", + "require-main-filename": "^1.0.1", + "set-blocking": "^2.0.0", + "string-width": "^2.0.0", + "which-module": "^2.0.0", + "y18n": "^3.2.1 || ^4.0.0", + "yargs-parser": "^10.1.0" + } + }, + "yargs-parser": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-10.1.0.tgz", + "integrity": "sha512-VCIyR1wJoEBZUqk5PA+oOBF6ypbwh5aNB3I50guxAL/quggdfs4TtNHQrSazFA3fYZ+tEqfs0zIGlv0c/rgjbQ==", + "dev": true, + "requires": { + "camelcase": "^4.1.0" + } + } + } + }, + "webpack-log": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/webpack-log/-/webpack-log-2.0.0.tgz", + "integrity": "sha512-cX8G2vR/85UYG59FgkoMamwHUIkSSlV3bBMRsbxVXVUk2j6NleCKjQ/WE9eYg9WY4w25O9w8wKP4rzNZFmUcUg==", + "dev": true, + "requires": { + "ansi-colors": "^3.0.0", + "uuid": "^3.3.2" + } + }, + "webpack-merge": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-4.2.1.tgz", + "integrity": "sha512-4p8WQyS98bUJcCvFMbdGZyZmsKuWjWVnVHnAS3FFg0HDaRVrPbkivx2RYCre8UiemD67RsiFFLfn4JhLAin8Vw==", + "dev": true, + "requires": { + "lodash": "^4.17.5" + } + }, + "webpack-sources": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-1.3.0.tgz", + "integrity": "sha512-OiVgSrbGu7NEnEvQJJgdSFPl2qWKkWq5lHMhgiToIiN9w34EBnjYzSYs+VbL5KoYiLNtFFa7BZIKxRED3I32pA==", + "dev": true, + "requires": { + "source-list-map": "^2.0.0", + "source-map": "~0.6.1" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + } + } + }, + "webpack-subresource-integrity": { + "version": "1.1.0-rc.6", + "resolved": "https://registry.npmjs.org/webpack-subresource-integrity/-/webpack-subresource-integrity-1.1.0-rc.6.tgz", + "integrity": "sha512-Az7y8xTniNhaA0620AV1KPwWOqawurVVDzQSpPAeR5RwNbL91GoBSJAAo9cfd+GiFHwsS5bbHepBw1e6Hzxy4w==", + "dev": true, + "requires": { + "webpack-core": "^0.6.8" + } + }, + "websocket-driver": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.0.tgz", + "integrity": "sha1-DK+dLXVdk67gSdS90NP+LMoqJOs=", + "dev": true, + "requires": { + "http-parser-js": ">=0.4.0", + "websocket-extensions": ">=0.1.1" + } + }, + "websocket-extensions": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.3.tgz", + "integrity": "sha512-nqHUnMXmBzT0w570r2JpJxfiSD1IzoI+HGVdd3aZ0yNi3ngvQ4jv1dtHt5VGxfI2yj5yqImPhOK4vmIh2xMbGg==", + "dev": true + }, + "when": { + "version": "3.6.4", + "resolved": "https://registry.npmjs.org/when/-/when-3.6.4.tgz", + "integrity": "sha1-RztRfsFZ4rhQBUl6E5g/CVQS404=", + "dev": true + }, + "which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + }, + "which-module": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/which-module/-/which-module-1.0.0.tgz", + "integrity": "sha1-u6Y8qGGUiZT/MHc2CJ47lgJsKk8=", + "dev": true, + "optional": true + }, + "wide-align": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz", + "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==", + "dev": true, + "optional": true, + "requires": { + "string-width": "^1.0.2 || 2" + } + }, + "wordwrap": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.3.tgz", + "integrity": "sha1-o9XabNXAvAAI03I0u68b7WMFkQc=", + "dev": true + }, + "worker-farm": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/worker-farm/-/worker-farm-1.6.0.tgz", + "integrity": "sha512-6w+3tHbM87WnSWnENBUvA2pxJPLhQUg5LKwUQHq3r+XPhIM+Gh2R5ycbwPCyuGbNg+lPgdcnQUhuC02kJCvffQ==", + "dev": true, + "requires": { + "errno": "~0.1.7" + } + }, + "wrap-ansi": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz", + "integrity": "sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=", + "dev": true, + "requires": { + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1" + } + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "dev": true + }, + "ws": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-3.3.3.tgz", + "integrity": "sha512-nnWLa/NwZSt4KQJu51MYlCcSQ5g7INpOrOMt4XV8j4dqTXdmlUmSHQ8/oLC069ckre0fRsgfvsKwbTdtKLCDkA==", + "dev": true, + "requires": { + "async-limiter": "~1.0.0", + "safe-buffer": "~5.1.0", + "ultron": "~1.1.0" + } + }, + "xml2js": { + "version": "0.4.19", + "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.19.tgz", + "integrity": "sha512-esZnJZJOiJR9wWKMyuvSE1y6Dq5LCuJanqhxslH2bxM6duahNZ+HMpCLhBQGZkbX6xRf8x1Y2eJlgt2q3qo49Q==", + "dev": true, + "requires": { + "sax": ">=0.6.0", + "xmlbuilder": "~9.0.1" + }, + "dependencies": { + "sax": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", + "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==", + "dev": true + } + } + }, + "xmlbuilder": { + "version": "9.0.7", + "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-9.0.7.tgz", + "integrity": "sha1-Ey7mPS7FVlxVfiD0wi35rKaGsQ0=", + "dev": true + }, + "xmlhttprequest-ssl": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-1.5.5.tgz", + "integrity": "sha1-wodrBhaKrcQOV9l+gRkayPQ5iz4=", + "dev": true + }, + "xregexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/xregexp/-/xregexp-4.0.0.tgz", + "integrity": "sha512-PHyM+sQouu7xspQQwELlGwwd05mXUFqwFYfqPO0cC7x4fxyHnnuetmQr6CjJiafIDoH4MogHb9dOoJzR/Y4rFg==", + "dev": true + }, + "xtend": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz", + "integrity": "sha1-pcbVMr5lbiPbgg77lDofBJmNY68=", + "dev": true + }, + "y18n": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.0.tgz", + "integrity": "sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w==", + "dev": true + }, + "yallist": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", + "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=", + "dev": true + }, + "yargs": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-7.1.0.tgz", + "integrity": "sha1-a6MY6xaWFyf10oT46gA+jWFU0Mg=", + "dev": true, + "optional": true, + "requires": { + "camelcase": "^3.0.0", + "cliui": "^3.2.0", + "decamelize": "^1.1.1", + "get-caller-file": "^1.0.1", + "os-locale": "^1.4.0", + "read-pkg-up": "^1.0.1", + "require-directory": "^2.1.1", + "require-main-filename": "^1.0.1", + "set-blocking": "^2.0.0", + "string-width": "^1.0.2", + "which-module": "^1.0.0", + "y18n": "^3.2.1", + "yargs-parser": "^5.0.0" + }, + "dependencies": { + "camelcase": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-3.0.0.tgz", + "integrity": "sha1-MvxLn82vhF/N9+c7uXysImHwqwo=", + "dev": true, + "optional": true + }, + "y18n": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-3.2.1.tgz", + "integrity": "sha1-bRX7qITAhnnA136I53WegR4H+kE=", + "dev": true, + "optional": true + } + } + }, + "yargs-parser": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-5.0.0.tgz", + "integrity": "sha1-J17PDX/+Bcd+ZOfIbkzZS/DhIoo=", + "dev": true, + "optional": true, + "requires": { + "camelcase": "^3.0.0" + }, + "dependencies": { + "camelcase": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-3.0.0.tgz", + "integrity": "sha1-MvxLn82vhF/N9+c7uXysImHwqwo=", + "dev": true, + "optional": true + } + } + }, + "yeast": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/yeast/-/yeast-0.1.2.tgz", + "integrity": "sha1-AI4G2AlDIMNy28L47XagymyKxBk=", + "dev": true + }, + "yn": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/yn/-/yn-2.0.0.tgz", + "integrity": "sha1-5a2ryKz0CPY4X8dklWhMiOavaJo=", + "dev": true + }, + "zone.js": { + "version": "0.8.29", + "resolved": "https://registry.npmjs.org/zone.js/-/zone.js-0.8.29.tgz", + "integrity": "sha512-mla2acNCMkWXBD+c+yeUrBUrzOxYMNFdQ6FGfigGGtEVBPJx07BQeJekjt9DmH1FtZek4E9rE1eRR9qQpxACOQ==" + } + } +} diff --git a/sut-gestaoHospital/hospital-ui/package.json b/sut-gestaoHospital/hospital-ui/package.json new file mode 100644 index 0000000..0c6745b --- /dev/null +++ b/sut-gestaoHospital/hospital-ui/package.json @@ -0,0 +1,52 @@ +{ + "name": "hospital-ui", + "version": "0.0.0", + "scripts": { + "ng": "ng", + "start": "ng serve", + "build": "ng build", + "test": "ng test", + "lint": "ng lint", + "e2e": "ng e2e" + }, + "private": true, + "dependencies": { + "@angular/animations": "^7.2.11", + "@angular/cdk": "^7.3.6", + "@angular/common": "~7.2.0", + "@angular/compiler": "~7.2.0", + "@angular/core": "~7.2.0", + "@angular/forms": "~7.2.0", + "@angular/platform-browser": "~7.2.0", + "@angular/platform-browser-dynamic": "~7.2.0", + "@angular/router": "~7.2.0", + "core-js": "^2.5.4", + "primeflex": "^1.0.0-rc.1", + "primeicons": "^1.0.0", + "primeng": "^7.1.0", + "rxjs": "~6.3.3", + "tslib": "^1.9.0", + "zone.js": "~0.8.26" + }, + "devDependencies": { + "@angular-devkit/build-angular": "^0.13.8", + "@angular/cli": "~7.3.7", + "@angular/compiler-cli": "~7.2.0", + "@angular/language-service": "~7.2.0", + "@types/jasmine": "~2.8.8", + "@types/jasminewd2": "~2.0.3", + "@types/node": "~8.9.4", + "codelyzer": "~4.5.0", + "jasmine-core": "~2.99.1", + "jasmine-spec-reporter": "~4.2.1", + "karma": "~4.0.0", + "karma-chrome-launcher": "~2.2.0", + "karma-coverage-istanbul-reporter": "~2.0.1", + "karma-jasmine": "~1.1.2", + "karma-jasmine-html-reporter": "^0.2.2", + "protractor": "~5.4.0", + "ts-node": "~7.0.0", + "tslint": "~5.11.0", + "typescript": "~3.2.2" + } +} diff --git a/sut-gestaoHospital/hospital-ui/src/app/app.component.css b/sut-gestaoHospital/hospital-ui/src/app/app.component.css new file mode 100644 index 0000000..e69de29 diff --git a/sut-gestaoHospital/hospital-ui/src/app/app.component.html b/sut-gestaoHospital/hospital-ui/src/app/app.component.html new file mode 100644 index 0000000..5c4eba3 --- /dev/null +++ b/sut-gestaoHospital/hospital-ui/src/app/app.component.html @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/sut-gestaoHospital/hospital-ui/src/app/app.component.spec.ts b/sut-gestaoHospital/hospital-ui/src/app/app.component.spec.ts new file mode 100644 index 0000000..5e7173c --- /dev/null +++ b/sut-gestaoHospital/hospital-ui/src/app/app.component.spec.ts @@ -0,0 +1,31 @@ +import { TestBed, async } from '@angular/core/testing'; +import { AppComponent } from './app.component'; + +describe('AppComponent', () => { + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ + AppComponent + ], + }).compileComponents(); + })); + + it('should create the app', () => { + const fixture = TestBed.createComponent(AppComponent); + const app = fixture.debugElement.componentInstance; + expect(app).toBeTruthy(); + }); + + it(`should have as title 'Sistema de Gestão Hospitalar'`, () => { + const fixture = TestBed.createComponent(AppComponent); + const app = fixture.debugElement.componentInstance; + expect(app.title).toEqual('Sistema de Gestão Hospitalar'); + }); + + it('should render title in a h1 tag', () => { + const fixture = TestBed.createComponent(AppComponent); + fixture.detectChanges(); + const compiled = fixture.debugElement.nativeElement; + expect(compiled.querySelector('h1').textContent).toContain('Sistema de Gestão Hospitalar'); + }); +}); diff --git a/sut-gestaoHospital/hospital-ui/src/app/app.component.ts b/sut-gestaoHospital/hospital-ui/src/app/app.component.ts new file mode 100644 index 0000000..68478a8 --- /dev/null +++ b/sut-gestaoHospital/hospital-ui/src/app/app.component.ts @@ -0,0 +1,9 @@ +import { Component } from '@angular/core'; + +@Component({ + selector: 'app-root', + templateUrl: './app.component.html', + styleUrls: ['./app.component.css'] +}) +export class AppComponent { +} diff --git a/sut-gestaoHospital/hospital-ui/src/app/app.module.ts b/sut-gestaoHospital/hospital-ui/src/app/app.module.ts new file mode 100644 index 0000000..f05f5ff --- /dev/null +++ b/sut-gestaoHospital/hospital-ui/src/app/app.module.ts @@ -0,0 +1,40 @@ +import { BrowserModule } from '@angular/platform-browser'; +import { NgModule } from '@angular/core'; +import { FormsModule } from '@angular/forms'; +import { HttpClientModule } from '@angular/common/http'; +import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; +import { MultiSelectModule } from 'primeng/multiselect'; +import { PanelModule } from 'primeng/panel'; +import { InputTextModule } from 'primeng/inputtext'; +import { TableModule } from 'primeng/table'; +import { ButtonModule } from 'primeng/button'; +import { AppComponent } from './app.component'; +import { ToastModule } from 'primeng/toast'; +import { MessageService } from 'primeng/api'; +import { CalendarModule } from 'primeng/calendar'; +import { GestaoHospitalComponent } from './gestao-hospital/gestao-hospital.component'; + +@NgModule({ + declarations: [ + AppComponent, + GestaoHospitalComponent + ], + imports: [ + BrowserModule, + BrowserAnimationsModule, + FormsModule, + HttpClientModule, + TableModule, + PanelModule, + InputTextModule, + ButtonModule, + ToastModule, + CalendarModule, + MultiSelectModule + ], + providers: [MessageService], + bootstrap: [AppComponent] +}) +export class AppModule { + value: Date; +} diff --git a/sut-gestaoHospital/hospital-ui/src/app/gestao-hospital/gestao-hospital.component.css b/sut-gestaoHospital/hospital-ui/src/app/gestao-hospital/gestao-hospital.component.css new file mode 100644 index 0000000..b48b908 --- /dev/null +++ b/sut-gestaoHospital/hospital-ui/src/app/gestao-hospital/gestao-hospital.component.css @@ -0,0 +1,25 @@ +.color{ + color: red; +} +#title{ + padding-top: 20px; +} +#button{ + background-color:#660066; + border-radius: 8px; + border: 0px; +} +#button:hover{ + background-color: #7800FF; +} +a{ + color: white; + text-decoration: none; +} +.top{ + margin-left: 2%; + border-radius: 8px; + padding: 5px; + color: white; + background-color: #7800FF; +} \ No newline at end of file diff --git a/sut-gestaoHospital/hospital-ui/src/app/gestao-hospital/gestao-hospital.component.html b/sut-gestaoHospital/hospital-ui/src/app/gestao-hospital/gestao-hospital.component.html new file mode 100644 index 0000000..f427c11 --- /dev/null +++ b/sut-gestaoHospital/hospital-ui/src/app/gestao-hospital/gestao-hospital.component.html @@ -0,0 +1,174 @@ +
+

{{ title }}

+ +
+ +

Hospitais

+
+ +
+

*Necessário preencher todos os campos!!


+
+
+
Nome do Hospital
+
+ +
+
Endereço
+
+ +
+
Leitos
+
+ +
+
Leitos Disponíveis
+
+ +
+
+ +
+
+
+
+ + + + Hospital + Endereço + Leitos + Leitos Disponíveis + + + + + {{ hospital.name }} + {{ hospital.address }} + {{ hospital.beds }} + {{ hospital.availableBeds }} + + + +
+ +

Pacientes

+
+ +
+

*Necessário preencher todos os campos!!


+
+
+
Nome do Paciente
+
+ +
+
CPF
+
+ +
+
Nascimento
+
+ +
+
Gênero
+
+ +
+
Data de Entrada
+
+ +
+
+ +
+
+
+
+ + + + Nome do Paciente + CPF + Nascimento + Gênero + Data de Entrada + + + + + {{ paciente.name }} + {{ paciente.cpf }} + {{ paciente.birthDate | date:'dd/MM/yyyy' }} + {{ paciente.gender }} + {{ paciente.entryDate | date:'dd/MM/yyyy' }} + + + +
+ +

Estoque

+
+ +
+

*Necessário preencher todos os campos!!


+
+
+
Produto
+
+ +
+
Descrição
+
+ +
+
Tipo
+
+ +
+
Quantidade
+
+ +
+
+ +
+
+
+
+ + + + Descrição + Tipo + Quantidade + + + + + {{ produto.description }} + {{ produto.productType }} + {{ produto.quantity }} + + + +
+ +Topo + diff --git a/sut-gestaoHospital/hospital-ui/src/app/gestao-hospital/gestao-hospital.component.ts b/sut-gestaoHospital/hospital-ui/src/app/gestao-hospital/gestao-hospital.component.ts new file mode 100644 index 0000000..8de6bd0 --- /dev/null +++ b/sut-gestaoHospital/hospital-ui/src/app/gestao-hospital/gestao-hospital.component.ts @@ -0,0 +1,105 @@ +import { Component, OnInit } from '@angular/core'; +import { HospitalService } from '../hospital.service'; +import { MessageService } from 'primeng/api'; + +@Component({ + selector: 'app-gestao-hospital', + templateUrl: './gestao-hospital.component.html', + styleUrls: ['./gestao-hospital.component.css'] +}) +export class GestaoHospitalComponent implements OnInit { + + title = 'Sistema de Gestão Hospitalar'; + hospital = {}; + hospitais = []; + paciente = {}; + pacientes = []; + produto = {}; + produtos = []; + + constructor( + private hospitalService: HospitalService, + private messageService: MessageService + ) { } + + ngOnInit() { + this.consultar(); + this.consultarPaciente(); + this.consultarProduto(); + } + consultar() { + this.hospitalService.listarHospitais() + .subscribe(resposta => this.hospitais = resposta as any); + } + adicionar() { + this.hospitalService.adicionar(this.hospital) + .subscribe(() => { + this.hospital = {}; + this.consultar(); + this.messageService.add({ + severity: 'success', + summary: 'Hospital adicionado com sucesso!' + }); + }, + resposta => { + let msg = 'Erro inesperado. Tente novamente.'; + if (resposta.error.message) { + msg = resposta.error.message; + } + this.messageService.add({ + severity: 'error', + summary: msg + }); + }); + } + consultarPaciente() { + this.hospitalService.listarPacientes() + .subscribe(resposta => this.pacientes = resposta as any); + } + adicionarPaciente() { + this.hospitalService.adicionarPaciente(this.paciente) + .subscribe(() => { + this.paciente = {}; + this.consultarPaciente(); + this.messageService.add({ + severity: 'success', + summary: 'Paciente adicionado com sucesso!' + }); + }, + resposta => { + let msg = 'Erro inesperado. Tente novamente.'; + if (resposta.error.message) { + msg = resposta.error.message; + } + this.messageService.add({ + severity: 'error', + summary: msg + }); + }); + } + consultarProduto() { + this.hospitalService.listarProdutos() + .subscribe(resposta => this.produtos = resposta as any); + } + adicionarProduto() { + this.hospitalService.adicionarProduto(this.produto) + .subscribe(() => { + this.produto = {}; + this.consultarProduto(); + this.messageService.add({ + severity: 'success', + summary: 'Produto adicionado com sucesso!' + }); + }, + resposta => { + let msg = 'Erro inesperado. Tente novamente.'; + if (resposta.error.message) { + msg = resposta.error.message; + } + this.messageService.add({ + severity: 'error', + summary: msg + }); + }); + } +} diff --git a/sut-gestaoHospital/hospital-ui/src/app/hospital.service.spec.ts b/sut-gestaoHospital/hospital-ui/src/app/hospital.service.spec.ts new file mode 100644 index 0000000..777ea03 --- /dev/null +++ b/sut-gestaoHospital/hospital-ui/src/app/hospital.service.spec.ts @@ -0,0 +1,12 @@ +import { TestBed } from '@angular/core/testing'; + +import { HospitalService } from './hospital.service'; + +describe('HospitalService', () => { + beforeEach(() => TestBed.configureTestingModule({})); + + it('should be created', () => { + const service: HospitalService = TestBed.get(HospitalService); + expect(service).toBeTruthy(); + }); +}); diff --git a/sut-gestaoHospital/hospital-ui/src/app/hospital.service.ts b/sut-gestaoHospital/hospital-ui/src/app/hospital.service.ts new file mode 100644 index 0000000..32e8be5 --- /dev/null +++ b/sut-gestaoHospital/hospital-ui/src/app/hospital.service.ts @@ -0,0 +1,33 @@ +import { Injectable } from '@angular/core'; +import { HttpClient } from '@angular/common/http'; + +@Injectable({ + providedIn: 'root' +}) +export class HospitalService { + + apiUrl = 'http://localhost:8080/v1/hospitais/'; + apiUrl2 = 'http://localhost:8080/v1/hospitais/3/pacientes/'; + apiUrl3 = 'http://localhost:8080/v1/hospitais/3/estoque/'; + + constructor(private httpClient: HttpClient) { } + + listarHospitais() { + return this.httpClient.get(this.apiUrl); + } + adicionar(hospital: any) { + return this.httpClient.post(this.apiUrl, hospital); + } + listarPacientes() { + return this.httpClient.get(this.apiUrl2); + } + adicionarPaciente(paciente: any) { + return this.httpClient.post(this.apiUrl, paciente); + } + listarProdutos() { + return this.httpClient.get(this.apiUrl3); + } + adicionarProduto(produto: any) { + return this.httpClient.post(this.apiUrl, produto); + } +} diff --git a/sut-gestaoHospital/hospital-ui/src/assets/.gitkeep b/sut-gestaoHospital/hospital-ui/src/assets/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/sut-gestaoHospital/hospital-ui/src/browserslist b/sut-gestaoHospital/hospital-ui/src/browserslist new file mode 100644 index 0000000..37371cb --- /dev/null +++ b/sut-gestaoHospital/hospital-ui/src/browserslist @@ -0,0 +1,11 @@ +# This file is currently used by autoprefixer to adjust CSS to support the below specified browsers +# For additional information regarding the format and rule options, please see: +# https://github.com/browserslist/browserslist#queries +# +# For IE 9-11 support, please remove 'not' from the last line of the file and adjust as needed + +> 0.5% +last 2 versions +Firefox ESR +not dead +not IE 9-11 \ No newline at end of file diff --git a/sut-gestaoHospital/hospital-ui/src/environments/environment.prod.ts b/sut-gestaoHospital/hospital-ui/src/environments/environment.prod.ts new file mode 100644 index 0000000..3612073 --- /dev/null +++ b/sut-gestaoHospital/hospital-ui/src/environments/environment.prod.ts @@ -0,0 +1,3 @@ +export const environment = { + production: true +}; diff --git a/sut-gestaoHospital/hospital-ui/src/environments/environment.ts b/sut-gestaoHospital/hospital-ui/src/environments/environment.ts new file mode 100644 index 0000000..7b4f817 --- /dev/null +++ b/sut-gestaoHospital/hospital-ui/src/environments/environment.ts @@ -0,0 +1,16 @@ +// This file can be replaced during build by using the `fileReplacements` array. +// `ng build --prod` replaces `environment.ts` with `environment.prod.ts`. +// The list of file replacements can be found in `angular.json`. + +export const environment = { + production: false +}; + +/* + * For easier debugging in development mode, you can import the following file + * to ignore zone related error stack frames such as `zone.run`, `zoneDelegate.invokeTask`. + * + * This import should be commented out in production mode because it will have a negative impact + * on performance if an error is thrown. + */ +// import 'zone.js/dist/zone-error'; // Included with Angular CLI. diff --git a/sut-gestaoHospital/hospital-ui/src/favicon.ico b/sut-gestaoHospital/hospital-ui/src/favicon.ico new file mode 100644 index 0000000..e0f9fe1 Binary files /dev/null and b/sut-gestaoHospital/hospital-ui/src/favicon.ico differ diff --git a/sut-gestaoHospital/hospital-ui/src/index.html b/sut-gestaoHospital/hospital-ui/src/index.html new file mode 100644 index 0000000..9414b85 --- /dev/null +++ b/sut-gestaoHospital/hospital-ui/src/index.html @@ -0,0 +1,16 @@ + + + + + Sistema de Gestão Hospitalar + + + + + + + + + + + \ No newline at end of file diff --git a/sut-gestaoHospital/hospital-ui/src/karma.conf.js b/sut-gestaoHospital/hospital-ui/src/karma.conf.js new file mode 100644 index 0000000..23e4a92 --- /dev/null +++ b/sut-gestaoHospital/hospital-ui/src/karma.conf.js @@ -0,0 +1,32 @@ +// Karma configuration file, see link for more information +// https://karma-runner.github.io/1.0/config/configuration-file.html + +module.exports = function (config) { + config.set({ + basePath: '', + frameworks: ['jasmine', '@angular-devkit/build-angular'], + plugins: [ + require('karma-jasmine'), + require('karma-chrome-launcher'), + require('karma-jasmine-html-reporter'), + require('karma-coverage-istanbul-reporter'), + require('@angular-devkit/build-angular/plugins/karma') + ], + client: { + clearContext: false // leave Jasmine Spec Runner output visible in browser + }, + coverageIstanbulReporter: { + dir: require('path').join(__dirname, '../coverage/hospital-ui'), + reports: ['html', 'lcovonly', 'text-summary'], + fixWebpackSourcePaths: true + }, + reporters: ['progress', 'kjhtml'], + port: 9876, + colors: true, + logLevel: config.LOG_INFO, + autoWatch: true, + browsers: ['Chrome'], + singleRun: false, + restartOnFileChange: true + }); +}; diff --git a/sut-gestaoHospital/hospital-ui/src/main.ts b/sut-gestaoHospital/hospital-ui/src/main.ts new file mode 100644 index 0000000..c7b673c --- /dev/null +++ b/sut-gestaoHospital/hospital-ui/src/main.ts @@ -0,0 +1,12 @@ +import { enableProdMode } from '@angular/core'; +import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; + +import { AppModule } from './app/app.module'; +import { environment } from './environments/environment'; + +if (environment.production) { + enableProdMode(); +} + +platformBrowserDynamic().bootstrapModule(AppModule) + .catch(err => console.error(err)); diff --git a/sut-gestaoHospital/hospital-ui/src/polyfills.ts b/sut-gestaoHospital/hospital-ui/src/polyfills.ts new file mode 100644 index 0000000..75d6393 --- /dev/null +++ b/sut-gestaoHospital/hospital-ui/src/polyfills.ts @@ -0,0 +1,63 @@ +/** + * This file includes polyfills needed by Angular and is loaded before the app. + * You can add your own extra polyfills to this file. + * + * This file is divided into 2 sections: + * 1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers. + * 2. Application imports. Files imported after ZoneJS that should be loaded before your main + * file. + * + * The current setup is for so-called "evergreen" browsers; the last versions of browsers that + * automatically update themselves. This includes Safari >= 10, Chrome >= 55 (including Opera), + * Edge >= 13 on the desktop, and iOS 10 and Chrome on mobile. + * + * Learn more in https://angular.io/guide/browser-support + */ + +/*************************************************************************************************** + * BROWSER POLYFILLS + */ + +/** IE10 and IE11 requires the following for NgClass support on SVG elements */ +// import 'classlist.js'; // Run `npm install --save classlist.js`. + +/** + * Web Animations `@angular/platform-browser/animations` + * Only required if AnimationBuilder is used within the application and using IE/Edge or Safari. + * Standard animation support in Angular DOES NOT require any polyfills (as of Angular 6.0). + */ +// import 'web-animations-js'; // Run `npm install --save web-animations-js`. + +/** + * By default, zone.js will patch all possible macroTask and DomEvents + * user can disable parts of macroTask/DomEvents patch by setting following flags + * because those flags need to be set before `zone.js` being loaded, and webpack + * will put import in the top of bundle, so user need to create a separate file + * in this directory (for example: zone-flags.ts), and put the following flags + * into that file, and then add the following code before importing zone.js. + * import './zone-flags.ts'; + * + * The flags allowed in zone-flags.ts are listed here. + * + * The following flags will work for all browsers. + * + * (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame + * (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick + * (window as any).__zone_symbol__BLACK_LISTED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames + * + * in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js + * with the following flag, it will bypass `zone.js` patch for IE/Edge + * + * (window as any).__Zone_enable_cross_context_check = true; + * + */ + +/*************************************************************************************************** + * Zone JS is required by default for Angular itself. + */ +import 'zone.js/dist/zone'; // Included with Angular CLI. + + +/*************************************************************************************************** + * APPLICATION IMPORTS + */ diff --git a/sut-gestaoHospital/hospital-ui/src/styles.css b/sut-gestaoHospital/hospital-ui/src/styles.css new file mode 100644 index 0000000..25a3146 --- /dev/null +++ b/sut-gestaoHospital/hospital-ui/src/styles.css @@ -0,0 +1,3 @@ +body{ + background-image: linear-gradient( #330033,#7800FF,#330033,#7800FF,#330033); +} \ No newline at end of file diff --git a/sut-gestaoHospital/hospital-ui/src/test.ts b/sut-gestaoHospital/hospital-ui/src/test.ts new file mode 100644 index 0000000..1631789 --- /dev/null +++ b/sut-gestaoHospital/hospital-ui/src/test.ts @@ -0,0 +1,20 @@ +// This file is required by karma.conf.js and loads recursively all the .spec and framework files + +import 'zone.js/dist/zone-testing'; +import { getTestBed } from '@angular/core/testing'; +import { + BrowserDynamicTestingModule, + platformBrowserDynamicTesting +} from '@angular/platform-browser-dynamic/testing'; + +declare const require: any; + +// First, initialize the Angular testing environment. +getTestBed().initTestEnvironment( + BrowserDynamicTestingModule, + platformBrowserDynamicTesting() +); +// Then we find all the tests. +const context = require.context('./', true, /\.spec\.ts$/); +// And load the modules. +context.keys().map(context); diff --git a/sut-gestaoHospital/hospital-ui/src/tsconfig.app.json b/sut-gestaoHospital/hospital-ui/src/tsconfig.app.json new file mode 100644 index 0000000..190fd30 --- /dev/null +++ b/sut-gestaoHospital/hospital-ui/src/tsconfig.app.json @@ -0,0 +1,11 @@ +{ + "extends": "../tsconfig.json", + "compilerOptions": { + "outDir": "../out-tsc/app", + "types": [] + }, + "exclude": [ + "test.ts", + "**/*.spec.ts" + ] +} diff --git a/sut-gestaoHospital/hospital-ui/src/tsconfig.spec.json b/sut-gestaoHospital/hospital-ui/src/tsconfig.spec.json new file mode 100644 index 0000000..de77336 --- /dev/null +++ b/sut-gestaoHospital/hospital-ui/src/tsconfig.spec.json @@ -0,0 +1,18 @@ +{ + "extends": "../tsconfig.json", + "compilerOptions": { + "outDir": "../out-tsc/spec", + "types": [ + "jasmine", + "node" + ] + }, + "files": [ + "test.ts", + "polyfills.ts" + ], + "include": [ + "**/*.spec.ts", + "**/*.d.ts" + ] +} diff --git a/sut-gestaoHospital/hospital-ui/src/tslint.json b/sut-gestaoHospital/hospital-ui/src/tslint.json new file mode 100644 index 0000000..aa7c3ee --- /dev/null +++ b/sut-gestaoHospital/hospital-ui/src/tslint.json @@ -0,0 +1,17 @@ +{ + "extends": "../tslint.json", + "rules": { + "directive-selector": [ + true, + "attribute", + "app", + "camelCase" + ], + "component-selector": [ + true, + "element", + "app", + "kebab-case" + ] + } +} diff --git a/sut-gestaoHospital/hospital-ui/tsconfig.json b/sut-gestaoHospital/hospital-ui/tsconfig.json new file mode 100644 index 0000000..b271fd9 --- /dev/null +++ b/sut-gestaoHospital/hospital-ui/tsconfig.json @@ -0,0 +1,22 @@ +{ + "compileOnSave": false, + "compilerOptions": { + "baseUrl": "./", + "outDir": "./dist/out-tsc", + "sourceMap": true, + "declaration": false, + "module": "es2015", + "moduleResolution": "node", + "emitDecoratorMetadata": true, + "experimentalDecorators": true, + "importHelpers": true, + "target": "es5", + "typeRoots": [ + "node_modules/@types" + ], + "lib": [ + "es2018", + "dom" + ] + } +} diff --git a/sut-gestaoHospital/hospital-ui/tslint.json b/sut-gestaoHospital/hospital-ui/tslint.json new file mode 100644 index 0000000..868ecba --- /dev/null +++ b/sut-gestaoHospital/hospital-ui/tslint.json @@ -0,0 +1,75 @@ +{ + "extends": "tslint:recommended", + "rulesDirectory": [ + "codelyzer" + ], + "rules": { + "array-type": false, + "arrow-parens": false, + "deprecation": { + "severity": "warn" + }, + "import-blacklist": [ + true, + "rxjs/Rx" + ], + "interface-name": false, + "max-classes-per-file": false, + "max-line-length": [ + true, + 140 + ], + "member-access": false, + "member-ordering": [ + true, + { + "order": [ + "static-field", + "instance-field", + "static-method", + "instance-method" + ] + } + ], + "no-consecutive-blank-lines": false, + "no-console": [ + true, + "debug", + "info", + "time", + "timeEnd", + "trace" + ], + "no-empty": false, + "no-inferrable-types": [ + true, + "ignore-params" + ], + "no-non-null-assertion": true, + "no-redundant-jsdoc": true, + "no-switch-case-fall-through": true, + "no-use-before-declare": true, + "no-var-requires": false, + "object-literal-key-quotes": [ + true, + "as-needed" + ], + "object-literal-sort-keys": false, + "ordered-imports": false, + "quotemark": [ + true, + "single" + ], + "trailing-comma": false, + "no-output-on-prefix": true, + "use-input-property-decorator": true, + "use-output-property-decorator": true, + "use-host-property-decorator": true, + "no-input-rename": true, + "no-output-rename": true, + "use-life-cycle-interface": true, + "use-pipe-transform-interface": true, + "component-class-suffix": true, + "directive-class-suffix": true + } +} diff --git a/sut-gestaoHospital/mvnw b/sut-gestaoHospital/mvnw new file mode 100644 index 0000000..5551fde --- /dev/null +++ b/sut-gestaoHospital/mvnw @@ -0,0 +1,286 @@ +#!/bin/sh +# ---------------------------------------------------------------------------- +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# ---------------------------------------------------------------------------- + +# ---------------------------------------------------------------------------- +# Maven2 Start Up Batch script +# +# Required ENV vars: +# ------------------ +# JAVA_HOME - location of a JDK home dir +# +# Optional ENV vars +# ----------------- +# M2_HOME - location of maven2's installed home dir +# MAVEN_OPTS - parameters passed to the Java VM when running Maven +# e.g. to debug Maven itself, use +# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +# MAVEN_SKIP_RC - flag to disable loading of mavenrc files +# ---------------------------------------------------------------------------- + +if [ -z "$MAVEN_SKIP_RC" ] ; then + + if [ -f /etc/mavenrc ] ; then + . /etc/mavenrc + fi + + if [ -f "$HOME/.mavenrc" ] ; then + . "$HOME/.mavenrc" + fi + +fi + +# OS specific support. $var _must_ be set to either true or false. +cygwin=false; +darwin=false; +mingw=false +case "`uname`" in + CYGWIN*) cygwin=true ;; + MINGW*) mingw=true;; + Darwin*) darwin=true + # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home + # See https://developer.apple.com/library/mac/qa/qa1170/_index.html + if [ -z "$JAVA_HOME" ]; then + if [ -x "/usr/libexec/java_home" ]; then + export JAVA_HOME="`/usr/libexec/java_home`" + else + export JAVA_HOME="/Library/Java/Home" + fi + fi + ;; +esac + +if [ -z "$JAVA_HOME" ] ; then + if [ -r /etc/gentoo-release ] ; then + JAVA_HOME=`java-config --jre-home` + fi +fi + +if [ -z "$M2_HOME" ] ; then + ## resolve links - $0 may be a link to maven's home + PRG="$0" + + # need this for relative symlinks + while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG="`dirname "$PRG"`/$link" + fi + done + + saveddir=`pwd` + + M2_HOME=`dirname "$PRG"`/.. + + # make it fully qualified + M2_HOME=`cd "$M2_HOME" && pwd` + + cd "$saveddir" + # echo Using m2 at $M2_HOME +fi + +# For Cygwin, ensure paths are in UNIX format before anything is touched +if $cygwin ; then + [ -n "$M2_HOME" ] && + M2_HOME=`cygpath --unix "$M2_HOME"` + [ -n "$JAVA_HOME" ] && + JAVA_HOME=`cygpath --unix "$JAVA_HOME"` + [ -n "$CLASSPATH" ] && + CLASSPATH=`cygpath --path --unix "$CLASSPATH"` +fi + +# For Mingw, ensure paths are in UNIX format before anything is touched +if $mingw ; then + [ -n "$M2_HOME" ] && + M2_HOME="`(cd "$M2_HOME"; pwd)`" + [ -n "$JAVA_HOME" ] && + JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`" + # TODO classpath? +fi + +if [ -z "$JAVA_HOME" ]; then + javaExecutable="`which javac`" + if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then + # readlink(1) is not available as standard on Solaris 10. + readLink=`which readlink` + if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then + if $darwin ; then + javaHome="`dirname \"$javaExecutable\"`" + javaExecutable="`cd \"$javaHome\" && pwd -P`/javac" + else + javaExecutable="`readlink -f \"$javaExecutable\"`" + fi + javaHome="`dirname \"$javaExecutable\"`" + javaHome=`expr "$javaHome" : '\(.*\)/bin'` + JAVA_HOME="$javaHome" + export JAVA_HOME + fi + fi +fi + +if [ -z "$JAVACMD" ] ; then + if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + else + JAVACMD="`which java`" + fi +fi + +if [ ! -x "$JAVACMD" ] ; then + echo "Error: JAVA_HOME is not defined correctly." >&2 + echo " We cannot execute $JAVACMD" >&2 + exit 1 +fi + +if [ -z "$JAVA_HOME" ] ; then + echo "Warning: JAVA_HOME environment variable is not set." +fi + +CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher + +# traverses directory structure from process work directory to filesystem root +# first directory with .mvn subdirectory is considered project base directory +find_maven_basedir() { + + if [ -z "$1" ] + then + echo "Path not specified to find_maven_basedir" + return 1 + fi + + basedir="$1" + wdir="$1" + while [ "$wdir" != '/' ] ; do + if [ -d "$wdir"/.mvn ] ; then + basedir=$wdir + break + fi + # workaround for JBEAP-8937 (on Solaris 10/Sparc) + if [ -d "${wdir}" ]; then + wdir=`cd "$wdir/.."; pwd` + fi + # end of workaround + done + echo "${basedir}" +} + +# concatenates all lines of a file +concat_lines() { + if [ -f "$1" ]; then + echo "$(tr -s '\n' ' ' < "$1")" + fi +} + +BASE_DIR=`find_maven_basedir "$(pwd)"` +if [ -z "$BASE_DIR" ]; then + exit 1; +fi + +########################################################################################## +# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central +# This allows using the maven wrapper in projects that prohibit checking in binary data. +########################################################################################## +if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found .mvn/wrapper/maven-wrapper.jar" + fi +else + if [ "$MVNW_VERBOSE" = true ]; then + echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..." + fi + jarUrl="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.4.2/maven-wrapper-0.4.2.jar" + while IFS="=" read key value; do + case "$key" in (wrapperUrl) jarUrl="$value"; break ;; + esac + done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties" + if [ "$MVNW_VERBOSE" = true ]; then + echo "Downloading from: $jarUrl" + fi + wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" + + if command -v wget > /dev/null; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found wget ... using wget" + fi + wget "$jarUrl" -O "$wrapperJarPath" + elif command -v curl > /dev/null; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found curl ... using curl" + fi + curl -o "$wrapperJarPath" "$jarUrl" + else + if [ "$MVNW_VERBOSE" = true ]; then + echo "Falling back to using Java to download" + fi + javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java" + if [ -e "$javaClass" ]; then + if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then + if [ "$MVNW_VERBOSE" = true ]; then + echo " - Compiling MavenWrapperDownloader.java ..." + fi + # Compiling the Java class + ("$JAVA_HOME/bin/javac" "$javaClass") + fi + if [ -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then + # Running the downloader + if [ "$MVNW_VERBOSE" = true ]; then + echo " - Running MavenWrapperDownloader.java ..." + fi + ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR") + fi + fi + fi +fi +########################################################################################## +# End of extension +########################################################################################## + +export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"} +if [ "$MVNW_VERBOSE" = true ]; then + echo $MAVEN_PROJECTBASEDIR +fi +MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" + +# For Cygwin, switch paths to Windows format before running java +if $cygwin; then + [ -n "$M2_HOME" ] && + M2_HOME=`cygpath --path --windows "$M2_HOME"` + [ -n "$JAVA_HOME" ] && + JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"` + [ -n "$CLASSPATH" ] && + CLASSPATH=`cygpath --path --windows "$CLASSPATH"` + [ -n "$MAVEN_PROJECTBASEDIR" ] && + MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"` +fi + +WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +exec "$JAVACMD" \ + $MAVEN_OPTS \ + -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ + "-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ + ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" diff --git a/sut-gestaoHospital/mvnw.cmd b/sut-gestaoHospital/mvnw.cmd new file mode 100644 index 0000000..e5cfb0a --- /dev/null +++ b/sut-gestaoHospital/mvnw.cmd @@ -0,0 +1,161 @@ +@REM ---------------------------------------------------------------------------- +@REM Licensed to the Apache Software Foundation (ASF) under one +@REM or more contributor license agreements. See the NOTICE file +@REM distributed with this work for additional information +@REM regarding copyright ownership. The ASF licenses this file +@REM to you under the Apache License, Version 2.0 (the +@REM "License"); you may not use this file except in compliance +@REM with the License. You may obtain a copy of the License at +@REM +@REM http://www.apache.org/licenses/LICENSE-2.0 +@REM +@REM Unless required by applicable law or agreed to in writing, +@REM software distributed under the License is distributed on an +@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +@REM KIND, either express or implied. See the License for the +@REM specific language governing permissions and limitations +@REM under the License. +@REM ---------------------------------------------------------------------------- + +@REM ---------------------------------------------------------------------------- +@REM Maven2 Start Up Batch script +@REM +@REM Required ENV vars: +@REM JAVA_HOME - location of a JDK home dir +@REM +@REM Optional ENV vars +@REM M2_HOME - location of maven2's installed home dir +@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands +@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a key stroke before ending +@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven +@REM e.g. to debug Maven itself, use +@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files +@REM ---------------------------------------------------------------------------- + +@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' +@echo off +@REM set title of command window +title %0 +@REM enable echoing my setting MAVEN_BATCH_ECHO to 'on' +@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% + +@REM set %HOME% to equivalent of $HOME +if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") + +@REM Execute a user defined script before this one +if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre +@REM check for pre script, once with legacy .bat ending and once with .cmd ending +if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat" +if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd" +:skipRcPre + +@setlocal + +set ERROR_CODE=0 + +@REM To isolate internal variables from possible post scripts, we use another setlocal +@setlocal + +@REM ==== START VALIDATION ==== +if not "%JAVA_HOME%" == "" goto OkJHome + +echo. +echo Error: JAVA_HOME not found in your environment. >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +:OkJHome +if exist "%JAVA_HOME%\bin\java.exe" goto init + +echo. +echo Error: JAVA_HOME is set to an invalid directory. >&2 +echo JAVA_HOME = "%JAVA_HOME%" >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +@REM ==== END VALIDATION ==== + +:init + +@REM Find the project base dir, i.e. the directory that contains the folder ".mvn". +@REM Fallback to current working directory if not found. + +set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% +IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir + +set EXEC_DIR=%CD% +set WDIR=%EXEC_DIR% +:findBaseDir +IF EXIST "%WDIR%"\.mvn goto baseDirFound +cd .. +IF "%WDIR%"=="%CD%" goto baseDirNotFound +set WDIR=%CD% +goto findBaseDir + +:baseDirFound +set MAVEN_PROJECTBASEDIR=%WDIR% +cd "%EXEC_DIR%" +goto endDetectBaseDir + +:baseDirNotFound +set MAVEN_PROJECTBASEDIR=%EXEC_DIR% +cd "%EXEC_DIR%" + +:endDetectBaseDir + +IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig + +@setlocal EnableExtensions EnableDelayedExpansion +for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a +@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% + +:endReadAdditionalConfig + +SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" +set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" +set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.4.2/maven-wrapper-0.4.2.jar" +FOR /F "tokens=1,2 delims==" %%A IN (%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties) DO ( + IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B +) + +@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central +@REM This allows using the maven wrapper in projects that prohibit checking in binary data. +if exist %WRAPPER_JAR% ( + echo Found %WRAPPER_JAR% +) else ( + echo Couldn't find %WRAPPER_JAR%, downloading it ... + echo Downloading from: %DOWNLOAD_URL% + powershell -Command "(New-Object Net.WebClient).DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')" + echo Finished downloading %WRAPPER_JAR% +) +@REM End of extension + +%MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* +if ERRORLEVEL 1 goto error +goto end + +:error +set ERROR_CODE=1 + +:end +@endlocal & set ERROR_CODE=%ERROR_CODE% + +if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost +@REM check for post script, once with legacy .bat ending and once with .cmd ending +if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat" +if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd" +:skipRcPost + +@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' +if "%MAVEN_BATCH_PAUSE%" == "on" pause + +if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE% + +exit /B %ERROR_CODE% diff --git a/sut-gestaoHospital/pom.xml b/sut-gestaoHospital/pom.xml new file mode 100644 index 0000000..035e916 --- /dev/null +++ b/sut-gestaoHospital/pom.xml @@ -0,0 +1,65 @@ + + + 4.0.0 + + org.springframework.boot + spring-boot-starter-parent + 2.1.3.RELEASE + + + com.codenation + gestaohospitalar + 0.0.1 + gestaohospitalar + Code:Nation - Sistema de Gestão Hospitalar + + + 1.8 + + + + + io.springfox + springfox-swagger2 + 2.6.1 + + + io.springfox + springfox-swagger-ui + 2.6.1 + + + + org.springframework.boot + spring-boot-starter-web + + + + org.springframework.boot + spring-boot-starter-hateoas + + + + org.springframework.boot + spring-boot-starter-test + test + + + + org.springframework.boot + spring-boot-starter-data-mongodb + + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + + diff --git a/sut-gestaoHospital/src/main/java/br/com/codenation/hospital/GestaohospitalarApplication.java b/sut-gestaoHospital/src/main/java/br/com/codenation/hospital/GestaohospitalarApplication.java new file mode 100644 index 0000000..5644363 --- /dev/null +++ b/sut-gestaoHospital/src/main/java/br/com/codenation/hospital/GestaohospitalarApplication.java @@ -0,0 +1,13 @@ +package br.com.codenation.hospital; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class GestaohospitalarApplication { + + public static void main(String[] args) { + SpringApplication.run(GestaohospitalarApplication.class, args); + } + +} diff --git a/sut-gestaoHospital/src/main/java/br/com/codenation/hospital/config/Instantiation.java b/sut-gestaoHospital/src/main/java/br/com/codenation/hospital/config/Instantiation.java new file mode 100644 index 0000000..2a0461c --- /dev/null +++ b/sut-gestaoHospital/src/main/java/br/com/codenation/hospital/config/Instantiation.java @@ -0,0 +1,205 @@ +package br.com.codenation.hospital.config; + +import java.text.DateFormat; +import java.text.SimpleDateFormat; +import java.util.Arrays; +import java.util.TimeZone; + +import org.bson.types.ObjectId; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.CommandLineRunner; +import org.springframework.context.annotation.Configuration; + +import br.com.codenation.hospital.domain.Hospital; +import br.com.codenation.hospital.domain.Location; +import br.com.codenation.hospital.domain.LocationBuilder; +import br.com.codenation.hospital.domain.LocationCategory; +import br.com.codenation.hospital.domain.Patient; +import br.com.codenation.hospital.domain.Product; +import br.com.codenation.hospital.domain.ProductType; +import br.com.codenation.hospital.repository.HospitalRepository; +import br.com.codenation.hospital.repository.LocationRepository; +import br.com.codenation.hospital.repository.PatientRepository; +import br.com.codenation.hospital.repository.ProductRepository; + +//Operação de instanciação da base de dados + +@Configuration +public class Instantiation implements CommandLineRunner{ + + @Autowired + private HospitalRepository hospitalRepository; + + @Autowired + private PatientRepository patientRepository; + + @Autowired + private ProductRepository productRepository; + + @Autowired + private LocationRepository locationRepository; + + @Override + public void run(String... args) throws Exception { + DateFormat sdf = new SimpleDateFormat("dd/MM/yyyy"); + sdf.setTimeZone(TimeZone.getTimeZone("GMT")); + + hospitalRepository.deleteAll(); //deleta todos dados do mongodb + patientRepository.deleteAll(); + productRepository.deleteAll(); + locationRepository.deleteAll(); + + Location locationUm = new LocationBuilder() + .setReferenceId("Av. Albert Einstein, 627 - Jardim Leonor, São Paulo - SP, 05652-900") + .setLocationCategory(LocationCategory.HOSPITAL) + .setName("Hospital Israelita Albert Einstein") + .setLatitude(-23.5920091D) + .setLongitude(-46.6388042029871D) + .build(); + + Location locationDois = new LocationBuilder() + .setReferenceId("Rua Engenheiro Oscar Americano, 840 - Jardim Guedala, São Paulo - SP, 05605-050") + .setLocationCategory(LocationCategory.HOSPITAL) + .setName("Hospital São Luiz Unidade Morumbi") + .setLatitude(-23.591093D) + .setLongitude(-46.703459) + .build(); + + Location locationTres = new LocationBuilder() + .setReferenceId("Av. Prof. Francisco Morato, 719 - Butantã, São Paulo - SP, 05513-000") + .setLocationCategory(LocationCategory.HOSPITAL) + .setName("Hospital Next Butantã") + .setLatitude(-23.578151D) + .setLongitude(-46.708343D) + .build(); + + + Location locationPatientUm = new LocationBuilder() + .setReferenceId("R. José Pepe, 40-142 - Jardim Leonor, São Paulo - SP, 05652-080") + .setLocationCategory(LocationCategory.PATIENT) + .setName("Maria") + .setLatitude(-23.597442D) + .setLongitude(-46.713830D) + .build(); + + Location locationPatientDois = new LocationBuilder() + .setReferenceId("Rua Dr. Celso Dario Guimarães, 201 - Jardim Morumby, São Paulo - SP, 05655-030") + .setLocationCategory(LocationCategory.PATIENT) + .setName("Pedro") + .setLatitude(-23.608176D) + .setLongitude(-46.71718D) + .build(); + + Location locationPatientTres = new LocationBuilder() + .setReferenceId("R. Alvorada do Sul, 183 - Morumbi, São Paulo - SP, 05612-010") + .setLocationCategory(LocationCategory.PATIENT) + .setName("Joana") + .setLatitude(-23.591692D) + .setLongitude(-46.708806D) + .build(); + + Location locationPatientQuatro = new LocationBuilder() + .setReferenceId("Av. George Saville Dodd, 45 - Morumbi, São Paulo - SP, 05608-020") + .setLocationCategory(LocationCategory.PATIENT) + .setName("Arya") + .setLatitude(-23.580128D) + .setLongitude(-46.708799D) + .build(); + + + Location locationPatientCinco = new LocationBuilder() + .setReferenceId("Rua dos Limantos, 156 - Cidade Jardim, São Paulo - SP, 05675-020") + .setLocationCategory(LocationCategory.PATIENT) + .setName("João") + .setLatitude(-23.593855D) + .setLongitude(-46.701794D) + .build(); + + Location locationPatientSeis = new LocationBuilder() + .setReferenceId("Av. Morumbi, 354 - Morumbi, São Paulo - SP, 05606-010") + .setLocationCategory(LocationCategory.PATIENT) + .setName("Gabriel") + .setLatitude(-23.578307D) + .setLongitude(-46.706638D) + .build(); + + Location locationPatientSete = new LocationBuilder() + .setReferenceId("R. Campo Verde, 700 - Jardim Europa, São Paulo - SP, 04794-000") + .setLocationCategory(LocationCategory.PATIENT) + .setName("Ana") + .setLatitude(-23.577483D) + .setLongitude(-46.694697D) + .build(); + + Location locationPatientOito = new LocationBuilder() + .setReferenceId("R. Campo Verde, 516 - Jardim Europa, São Paulo - SP, 01456-010") + .setLocationCategory(LocationCategory.PATIENT) + .setName("Paula") + .setLatitude(-23.576718D) + .setLongitude(-46.693139D) + .build(); + + locationRepository.saveAll(Arrays.asList(locationUm,locationDois,locationTres)); //adiciona dados + locationRepository.saveAll(Arrays.asList(locationPatientUm,locationPatientDois,locationPatientTres)); //adiciona dados + locationRepository.saveAll(Arrays.asList(locationPatientQuatro,locationPatientCinco,locationPatientSeis)); //adiciona dados + locationRepository.saveAll(Arrays.asList(locationPatientSete,locationPatientOito)); //adiciona dados + + Hospital hospitalUm = new Hospital("1", "Hospital Israelita Albert Einstein", "Av. Albert Einstein, 627 - Jardim Leonor, São Paulo - SP, 05652-900", 21,5, locationUm); + Hospital hospitalDois = new Hospital("2", "Hospital São Luiz Unidade Morumbi", "Rua Engenheiro Oscar Americano, 840 - Jardim Guedala, São Paulo - SP, 05605-050", 11,6, locationDois); + Hospital hospitalTres = new Hospital("3", "Hospital Next Butantã", "Av. Prof. Francisco Morato, 719 - Butantã, São Paulo - SP, 05513-000", 32,12, locationTres); + + hospitalRepository.saveAll(Arrays.asList(hospitalUm,hospitalDois,hospitalTres)); //adiciona dados + + Patient pacient1 = new Patient("1", "Maria", "864789205", sdf.parse("16/07/2003"), "feminino", sdf.parse("16/07/2019"), locationPatientUm); + Patient pacient2 = new Patient("2", "Pedro", "864789205", sdf.parse("16/07/2003"), "masculino", sdf.parse("16/07/2019"), locationPatientDois); + Patient pacient3 = new Patient("3", "Joana", "864789205", sdf.parse("16/07/2003"), "feminino", sdf.parse("16/07/2019"), locationPatientTres); + Patient pacient4 = new Patient("4", "Arya", "864789205", sdf.parse("16/07/2003"), "feminino", sdf.parse("16/07/2019"), locationPatientQuatro); + Patient pacient5 = new Patient("5", "João", "864789205", sdf.parse("16/07/2003"), "masculino", sdf.parse("16/07/2019"), locationPatientCinco); + Patient pacient6 = new Patient("6", "Gabriel", "864789205", sdf.parse("16/07/2003"), "masculino", sdf.parse("16/07/2019"), locationPatientSeis); + Patient pacient7 = new Patient("7", "Ana", "864789205", sdf.parse("16/07/2003"), "feminino", sdf.parse("16/07/2019"), locationPatientSete); + Patient pacient8 = new Patient("8", "Paula", "864789205", sdf.parse("16/07/2003"), "feminino", sdf.parse("16/07/2019"), locationPatientOito); + + patientRepository.saveAll(Arrays.asList(pacient1,pacient2,pacient3,pacient4,pacient5,pacient6,pacient7,pacient8)); + + + // Se cambia la instanciación del identificador de productos (ObjectId a String) para facilitar comparación de resultados de test + + /*Product produto1 = new Product(ObjectId.get(), "Alimento", "Maçã", 12, ProductType.COMMON); + Product produto2 = new Product(ObjectId.get(), "Alimento", "Arroz", 3, ProductType.COMMON); + Product produto3 = new Product(ObjectId.get(), "Alimento", "Feijão", 2, ProductType.COMMON); + Product produto4 = new Product(ObjectId.get(), "Alimento", "Massa", 5, ProductType.COMMON); + Product produto5 = new Product(ObjectId.get(), "Alimento", "Massa", 5, ProductType.COMMON); + + Product produto6 = new Product(ObjectId.get(), "Banco de Sangue", "Sangue", 8, ProductType.BLOOD); + Product produto7 = new Product(ObjectId.get(), "Banco de Sangue", "Sangue", 1, ProductType.BLOOD); + Product produto8 = new Product(ObjectId.get(), "Banco de Sangue", "Sangue", 4, ProductType.BLOOD);*/ + + Product produto1 = new Product("1", "Alimento", "Maçã", 12, ProductType.COMMON); + Product produto2 = new Product("2", "Alimento", "Arroz", 3, ProductType.COMMON); + Product produto3 = new Product("3", "Alimento", "Feijão", 2, ProductType.COMMON); + Product produto4 = new Product("4", "Alimento", "Massa", 5, ProductType.COMMON); + Product produto5 = new Product("5", "Alimento", "Massa", 5, ProductType.COMMON); + + Product produto6 = new Product("6", "Banco de Sangue", "Sangue", 8, ProductType.BLOOD); + Product produto7 = new Product("7", "Banco de Sangue", "Sangue", 1, ProductType.BLOOD); + Product produto8 = new Product("8", "Banco de Sangue", "Sangue", 4, ProductType.BLOOD); + + + productRepository.saveAll(Arrays.asList(produto1,produto2,produto3,produto4,produto5,produto6,produto7,produto8)); + + //referenciando pacientes e produtos ao hospital + hospitalUm.getPatients().addAll(Arrays.asList(pacient1,pacient2)); + hospitalTres.getPatients().addAll(Arrays.asList(pacient3,pacient4,pacient5,pacient6,pacient7,pacient8)); + + hospitalUm.getProducts().addAll(Arrays.asList(produto5,produto6)); + hospitalDois.getProducts().addAll(Arrays.asList(produto1,produto7)); + hospitalTres.getProducts().addAll(Arrays.asList(produto2,produto3,produto4,produto8)); + + hospitalRepository.save(hospitalUm); + hospitalRepository.save(hospitalDois); + hospitalRepository.save(hospitalTres); + +// MongoCollection collection = database.getCollection("hospital_collection"); +// collection.createIndex(Indexes.geo2dsphere("location")); + } +} \ No newline at end of file diff --git a/sut-gestaoHospital/src/main/java/br/com/codenation/hospital/config/SwaggerConfig.java b/sut-gestaoHospital/src/main/java/br/com/codenation/hospital/config/SwaggerConfig.java new file mode 100644 index 0000000..d1c6bcd --- /dev/null +++ b/sut-gestaoHospital/src/main/java/br/com/codenation/hospital/config/SwaggerConfig.java @@ -0,0 +1,35 @@ +package br.com.codenation.hospital.config; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import springfox.documentation.builders.ApiInfoBuilder; +import springfox.documentation.builders.PathSelectors; +import springfox.documentation.builders.RequestHandlerSelectors; +import springfox.documentation.service.ApiInfo; +import springfox.documentation.spi.DocumentationType; +import springfox.documentation.spring.web.plugins.Docket; +import springfox.documentation.swagger2.annotations.EnableSwagger2; + +@Configuration +@EnableSwagger2 +// url Swagger - http://localhost:8080/swagger-ui.html +public class SwaggerConfig { + + @Bean + public Docket api() { + return new Docket(DocumentationType.SWAGGER_2).select() + .apis(RequestHandlerSelectors.basePackage("br.com.codenation.hospital.resource")) + .paths(PathSelectors.any()).build() + .apiInfo(apiInfo()); + + } + + private ApiInfo apiInfo() { + return new ApiInfoBuilder().title("Sistema de Gestão Hospital API") + .description("Documentação da API de acesso aos endpoints da GestaoHospitalAPI - Aceleradev Brasil\r\n" + + "Jornada de desafios da Aceleradev Brasil - CodeNation") + .version("1.0") + .build(); + } +} diff --git a/sut-gestaoHospital/src/main/java/br/com/codenation/hospital/constant/Constant.java b/sut-gestaoHospital/src/main/java/br/com/codenation/hospital/constant/Constant.java new file mode 100644 index 0000000..4b8d4d0 --- /dev/null +++ b/sut-gestaoHospital/src/main/java/br/com/codenation/hospital/constant/Constant.java @@ -0,0 +1,10 @@ +package br.com.codenation.hospital.constant; + +public class Constant { + + public final static String V1 = "/v1/hospitais/"; + public final static String V1Path = "/v1/hospitais/{hospital_id}/"; + private Constant() { + throw new AssertionError(); + } +} \ No newline at end of file diff --git a/sut-gestaoHospital/src/main/java/br/com/codenation/hospital/domain/Hospital.java b/sut-gestaoHospital/src/main/java/br/com/codenation/hospital/domain/Hospital.java new file mode 100644 index 0000000..872cfbf --- /dev/null +++ b/sut-gestaoHospital/src/main/java/br/com/codenation/hospital/domain/Hospital.java @@ -0,0 +1,211 @@ +package br.com.codenation.hospital.domain; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; + +import org.springframework.data.annotation.Id; +import org.springframework.data.mongodb.core.mapping.DBRef; +import org.springframework.data.mongodb.core.mapping.Document; + +import br.com.codenation.hospital.resource.exception.ResourceNotFoundException; + +@Document(collection="hospital_collection") +// https://codeboje.de/spring-data-mongodb/ +// https://lishman.io/object-mapping-with-spring-data-mongodb +public class Hospital implements Serializable{ + private static final long serialVersionUID = 1L; + + @Id + private String id; + private String name; + private String address; + private int beds; + private int availableBeds; + + private Location location; + + //só serão acessados se forem carregados + //Using Collection References + @DBRef(lazy=true) + private List patients = new ArrayList<>(); + + //Using Collection References + @DBRef(lazy=true) + private List products = new ArrayList<>(); + + public Hospital() { + + } + + public Hospital(String id) { + super(); + this.id = id; + } + + public Hospital(String id, String name, String address, int beds, int availableBeds) { + super(); + this.id = id; + this.name = name; + this.address = address; + this.beds = beds; + this.availableBeds = availableBeds; + } + + public Hospital(String id, String name, String address, int beds, int availableBeds, Location location) { + super(); + this.id = id; + this.name = name; + this.address = address; + this.beds = beds; + this.availableBeds = availableBeds; + this.location = location; + } + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getAddress() { + return address; + } + + public void setAddress(String address) { + this.address = address; + } + + public int getBeds() { + return beds; + } + + public void setBeds(int beds) { + this.beds = beds; + } + + public int getAvailableBeds() { + return availableBeds; + } + + public void setAvailableBeds(int availableBeds) { + if(availableBeds < 0){ + throw new RuntimeException("Não possui leitos disponiveis"); + } + this.availableBeds = availableBeds; + } + + public List getPatients() { + return patients; + } + + public boolean temVaga(){ + return availableBeds > 0; + } + + public void addPacient(Patient patient){ + this.availableBeds--; + patients.add(patient); + } + + public void removePacient(Patient patient){ + availableBeds++; + patients.remove(patient); + } + + public void setPatients(List patients) { + this.patients = patients; + } + + public List getProducts() { + return products; + } + + public void setProducts(List products) { + this.products = products; + } + + public void setProduct(Product product) { + this.products.add(product); + } + + public Location getLocation() { + return location; + } + + public void setLocation(Location location) { + this.location = location; + } + + public Patient hasPatient(Patient patient){ + return patients.stream() + .filter(patientFilter -> patientFilter.equals(patient)) + .findFirst() + .orElseThrow(() -> new ResourceNotFoundException("Paciente não está neste hospital!")); + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((address == null) ? 0 : address.hashCode()); + result = prime * result + availableBeds; + result = prime * result + beds; + result = prime * result + ((id == null) ? 0 : id.hashCode()); + result = prime * result + ((name == null) ? 0 : name.hashCode()); + result = prime * result + ((patients == null) ? 0 : patients.hashCode()); + result = prime * result + ((products == null) ? 0 : products.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + Hospital other = (Hospital) obj; + if (address == null) { + if (other.address != null) + return false; + } else if (!address.equals(other.address)) + return false; + if (availableBeds != other.availableBeds) + return false; + if (beds != other.beds) + return false; + if (id == null) { + if (other.id != null) + return false; + } else if (!id.equals(other.id)) + return false; + if (name == null) { + if (other.name != null) + return false; + } else if (!name.equals(other.name)) + return false; + if (patients == null) { + if (other.patients != null) + return false; + } else if (!patients.equals(other.patients)) + return false; + if (products == null) { + if (other.products != null) + return false; + } else if (!products.equals(other.products)) + return false; + return true; + } +} \ No newline at end of file diff --git a/sut-gestaoHospital/src/main/java/br/com/codenation/hospital/domain/Location.java b/sut-gestaoHospital/src/main/java/br/com/codenation/hospital/domain/Location.java new file mode 100644 index 0000000..633d559 --- /dev/null +++ b/sut-gestaoHospital/src/main/java/br/com/codenation/hospital/domain/Location.java @@ -0,0 +1,134 @@ +package br.com.codenation.hospital.domain; + +import java.io.Serializable; +import java.util.Objects; + +import org.springframework.data.annotation.Id; +import org.springframework.data.mongodb.core.geo.GeoJsonPoint; +import org.springframework.data.mongodb.core.index.GeoSpatialIndexType; +import org.springframework.data.mongodb.core.index.GeoSpatialIndexed; +import org.springframework.data.mongodb.core.mapping.Document; + +@Document(collection = "location_collection") +public class Location implements Serializable{ + private static final long serialVersionUID = 19L; + + @Id + private String id; + private String name; + private String referenceId; + private LocationCategory locationCategory; + + @GeoSpatialIndexed(type = GeoSpatialIndexType.GEO_2DSPHERE) + private GeoJsonPoint position; + + public Location() { + } + + public Location(String referenceId, LocationCategory locationCategory, String name, GeoJsonPoint position) { + this.referenceId = referenceId; + this.locationCategory = locationCategory; + this.name = name; + this.position = position; + } + + public Location(LocationCategory locationCategory, String name, GeoJsonPoint position) { + this.locationCategory = locationCategory; + this.name = name; + this.position = position; + } + + public Location(String referenceId, LocationCategory locationCategory, String name, double longitude, double latitude) { + GeoJsonPoint position = new GeoJsonPoint(latitude, longitude); + + this.referenceId = referenceId; + this.locationCategory = locationCategory; + this.name = name; + this.position = position; + } + + public Location(LocationCategory locationCategory, String name, double longitude, double latitude) { + GeoJsonPoint position = new GeoJsonPoint(latitude, longitude); + + this.locationCategory = locationCategory; + this.name = name; + this.position = position; + } + + public Location(String subject, GeoJsonPoint position) { + this.name = subject; + this.position = position; + } + + public Location(String subject, double longitude, double latitude) { + GeoJsonPoint position = new GeoJsonPoint(latitude, longitude); + + this.name = subject; + this.position = position; + } + + public String getId() { + return this.id; + } + + public void setId(final String id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public GeoJsonPoint getLocation() { + return this.position; + } + + public void setLocation(final GeoJsonPoint position) { + this.position = position; + } + + public LocationCategory getLocationCategory() { + return locationCategory; + } + + public void setLocationCategory(LocationCategory locationCategory) { + this.locationCategory = locationCategory; + } + + public GeoJsonPoint getPosition() { + return position; + } + + public void setPosition(GeoJsonPoint position) { + this.position = position; + } + + public String getReferenceId() { + return referenceId; + } + + public void setReferenceId(String referenceId) { + this.referenceId = referenceId; + } + + @Override + public boolean equals(final Object o) { + if (this == o) return true; + if (o == null || this.getClass() != o.getClass()) return false; + final Location that = (Location) o; + return Objects.equals(this.getId(), that.getId()) && + Objects.equals(this.getName(), that.getName()) && + Objects.equals(this.getReferenceId(), that.getReferenceId()) && + Objects.equals(this.getPosition(), that.getPosition()) && + Objects.equals(this.getLocationCategory(), that.getLocationCategory()); + } + + @Override + public int hashCode() { + return Objects.hash(this.getId(), this.getReferenceId(), this.getName(), this.getPosition(), this.getLocationCategory()); + } +} diff --git a/sut-gestaoHospital/src/main/java/br/com/codenation/hospital/domain/LocationBuilder.java b/sut-gestaoHospital/src/main/java/br/com/codenation/hospital/domain/LocationBuilder.java new file mode 100644 index 0000000..fb40e39 --- /dev/null +++ b/sut-gestaoHospital/src/main/java/br/com/codenation/hospital/domain/LocationBuilder.java @@ -0,0 +1,38 @@ +package br.com.codenation.hospital.domain; + +public class LocationBuilder { + private Double latitude; + private Double longitude; + private String name; + private String referenceId; + private LocationCategory category; + + public LocationBuilder setLatitude(Double latitude) { + this.latitude = latitude; + return this; + } + + public LocationBuilder setLongitude(Double longitude) { + this.longitude = longitude; + return this; + } + + public LocationBuilder setName(String name) { + this.name = name; + return this; + } + + public LocationBuilder setLocationCategory(LocationCategory category) { + this.category = category; + return this; + } + + public LocationBuilder setReferenceId(String referenceId) { + this.referenceId = referenceId; + return this; + } + + public Location build() { + return new Location(referenceId, category, name, latitude, longitude); + } +} \ No newline at end of file diff --git a/sut-gestaoHospital/src/main/java/br/com/codenation/hospital/domain/LocationCategory.java b/sut-gestaoHospital/src/main/java/br/com/codenation/hospital/domain/LocationCategory.java new file mode 100644 index 0000000..a7d813b --- /dev/null +++ b/sut-gestaoHospital/src/main/java/br/com/codenation/hospital/domain/LocationCategory.java @@ -0,0 +1,16 @@ +package br.com.codenation.hospital.domain; + +public enum LocationCategory { + HOSPITAL("HOSPITAL"), + PATIENT("PACIENTE"); + + private String descricao; + + LocationCategory(String descricao) { + this.descricao = descricao; + } + + public String getDescricao() { + return descricao; + } +} diff --git a/sut-gestaoHospital/src/main/java/br/com/codenation/hospital/domain/Patient.java b/sut-gestaoHospital/src/main/java/br/com/codenation/hospital/domain/Patient.java new file mode 100644 index 0000000..b8b624c --- /dev/null +++ b/sut-gestaoHospital/src/main/java/br/com/codenation/hospital/domain/Patient.java @@ -0,0 +1,207 @@ +package br.com.codenation.hospital.domain; + +import java.io.Serializable; +import java.util.Date; + +import org.springframework.data.annotation.Id; +import org.springframework.data.mongodb.core.mapping.Document; + +@Document(collection="patient_collection") +public class Patient implements Serializable{ + private static final long serialVersionUID = 1L; + + @Id + private String id; + private String name; + private String cpf; + private Date birthDate; + private String gender; + private Date entryDate; + private Date exitDate; + private boolean active; + + private Location location; + private String idHospital; //added reference to an hospital + + public Patient() { + this.active = false; + this.exitDate = null; + } + + public Patient(String name, String cpf, Date birthDate, String gender) { + this.name = name; + this.cpf = cpf; + this.birthDate = birthDate; + this.gender = gender; + } + + public Patient(String id, String name, String cpf, Date birthDate, String gender, Date entryDate) { + super(); + this.id = id; + this.name = name; + this.cpf = cpf; + this.birthDate = birthDate; + this.gender = gender; + this.entryDate = entryDate; + this.exitDate = null; + this.active = false; + } + + public Patient(String id, String name, String cpf, Date birthDate, String gender, Date entryDate, Location location) { + super(); + this.id = id; + this.name = name; + this.cpf = cpf; + this.birthDate = birthDate; + this.gender = gender; + this.entryDate = entryDate; + this.exitDate = null; + this.active = false; + this.location = location; + } + //added constructor with the reference to an hospital + public Patient(String id, String name, String cpf, Date birthDate, String gender, Date entryDate, Location location,String idHospital) { + super(); + this.id = id; + this.name = name; + this.cpf = cpf; + this.birthDate = birthDate; + this.gender = gender; + this.entryDate = entryDate; + this.exitDate = null; + this.active = false; + this.location = location; + this.idHospital = idHospital; + } + + public String getId() { + return id; + } + + public void setId(String paciente_id) { + this.id = paciente_id; + } + public String getName() { + return name; + } + public void setName(String name) { + this.name = name; + } + public String getCpf() { + return cpf; + } + public void setCpf(String cpf) { + this.cpf = cpf; + } + public Date getBirthDate() { + return birthDate; + } + public void setBirthDate(Date birthDate) { + this.birthDate = birthDate; + } + public String getGender() { + return gender; + } + public void setGender(String gender) { + this.gender = gender; + } + public Date getExitDate() { + return entryDate; + } + public void setExitDate(Date exitDate) { + this.exitDate = exitDate; + } + public Date getEntryDate() { + return entryDate; + } + public void setEntryDate(Date entryDate) { + this.entryDate = entryDate; + } + public void setActive(Boolean active){ + this.active = active; + } + + public Location getLocation() { + return location; + } + + public void setLocation(Location location) { + this.location = location; + } + + //Added getters y seeter for idHopital + public String getIdHospital() { + return idHospital; + } + + public void setIdHospital(String idHospital) { + this.idHospital = idHospital; + } + + + public boolean isActive(){return this.active;} + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + (active ? 1231 : 1237); + result = prime * result + ((birthDate == null) ? 0 : birthDate.hashCode()); + result = prime * result + ((cpf == null) ? 0 : cpf.hashCode()); + result = prime * result + ((entryDate == null) ? 0 : entryDate.hashCode()); + result = prime * result + ((exitDate == null) ? 0 : exitDate.hashCode()); + result = prime * result + ((gender == null) ? 0 : gender.hashCode()); + result = prime * result + ((id == null) ? 0 : id.hashCode()); + result = prime * result + ((name == null) ? 0 : name.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + Patient other = (Patient) obj; + if (active != other.active) + return false; + if (birthDate == null) { + if (other.birthDate != null) + return false; + } else if (!birthDate.equals(other.birthDate)) + return false; + if (cpf == null) { + if (other.cpf != null) + return false; + } else if (!cpf.equals(other.cpf)) + return false; + if (entryDate == null) { + if (other.entryDate != null) + return false; + } else if (!entryDate.equals(other.entryDate)) + return false; + if (exitDate == null) { + if (other.exitDate != null) + return false; + } else if (!exitDate.equals(other.exitDate)) + return false; + if (gender == null) { + if (other.gender != null) + return false; + } else if (!gender.equals(other.gender)) + return false; + if (id == null) { + if (other.id != null) + return false; + } else if (!id.equals(other.id)) + return false; + if (name == null) { + if (other.name != null) + return false; + } else if (!name.equals(other.name)) + return false; + return true; + } +} \ No newline at end of file diff --git a/sut-gestaoHospital/src/main/java/br/com/codenation/hospital/domain/Product.java b/sut-gestaoHospital/src/main/java/br/com/codenation/hospital/domain/Product.java new file mode 100644 index 0000000..555ed46 --- /dev/null +++ b/sut-gestaoHospital/src/main/java/br/com/codenation/hospital/domain/Product.java @@ -0,0 +1,138 @@ +package br.com.codenation.hospital.domain; + +import java.io.Serializable; + +import org.bson.types.ObjectId; +import org.springframework.data.annotation.Id; +import org.springframework.data.mongodb.core.mapping.Document; + +@Document(collection="product_collection") +public class Product implements Serializable{ + private static final long serialVersionUID = 1L; + + + @Id + //Cambia el tipo del _id (ObjectId a String) para facilitar comparación de resultados de test + private String _id; + private String name; + private String description; + private int quantity; + private ProductType productType; + private String hospitalId; //added reference to the hospital + + public Product() { + + } + + public Product(String _id, String name, String description, int quantity, ProductType productType) { + this._id = _id; + this.name = name; + this.description = description; + this.quantity = quantity; + this.productType = productType; + } + + public Product(String name, String description, int quantity, ProductType productType) { + this.name = name; + this.description = description; + this.quantity = quantity; + this.productType = productType; + } + + //added constructor with the reference to an hospital + public Product(String id, String name, String description, int quantity, ProductType productType,String hospitalId) { + this._id = id; + this.name = name; + this.description = description; + this.quantity = quantity; + this.productType = productType; + this.hospitalId = hospitalId; + } + + public String getId() { + //return _id.toHexString(); + return _id; + } + + //add setter for product _id + public void setId(String id) { + this._id=id; + } + + public String getName() { + return name; + } + public void setName(String name) { + this.name = name; + } + public String getDescription() { + return description; + } + public void setDescription(String description) { + this.description = description; + } + public int getQuantity() { + return quantity; + } + public void setQuantity(int quantity) { + this.quantity = quantity; + } + public ProductType getProductType() { + return productType; + } + public void setProductType(ProductType productType) { + this.productType = productType; + } + + public void diminuiQuantidade(int quantity){ + this.quantity-=quantity; + } + + //added getters and setters for idHospital + public String getHospitalId() { + return hospitalId; + } + public void setIdHospital(String hospitalId) { + this.hospitalId = hospitalId; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((description == null) ? 0 : description.hashCode()); + result = prime * result + ((_id == null) ? 0 : _id.hashCode()); + result = prime * result + ((name == null) ? 0 : name.hashCode()); + result = prime * result + quantity; + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + Product other = (Product) obj; + if (description == null) { + if (other.description != null) + return false; + } else if (!description.equals(other.description)) + return false; + if (_id == null) { + if (other._id != null) + return false; + } else if (!_id.equals(other._id)) + return false; + if (name == null) { + if (other.name != null) + return false; + } else if (!name.equals(other.name)) + return false; + if (quantity != other.quantity) + return false; + return true; + } +} \ No newline at end of file diff --git a/sut-gestaoHospital/src/main/java/br/com/codenation/hospital/domain/ProductType.java b/sut-gestaoHospital/src/main/java/br/com/codenation/hospital/domain/ProductType.java new file mode 100644 index 0000000..721aa4f --- /dev/null +++ b/sut-gestaoHospital/src/main/java/br/com/codenation/hospital/domain/ProductType.java @@ -0,0 +1,16 @@ +package br.com.codenation.hospital.domain; + +public enum ProductType { + COMMON("Comum"), + BLOOD("Sangue"); + + private String descricao; + + ProductType(String descricao) { + this.descricao = descricao; + } + + public String getDescricao() { + return descricao; + } +} diff --git a/sut-gestaoHospital/src/main/java/br/com/codenation/hospital/dto/HospitalDTO.java b/sut-gestaoHospital/src/main/java/br/com/codenation/hospital/dto/HospitalDTO.java new file mode 100644 index 0000000..3d08fc3 --- /dev/null +++ b/sut-gestaoHospital/src/main/java/br/com/codenation/hospital/dto/HospitalDTO.java @@ -0,0 +1,88 @@ +package br.com.codenation.hospital.dto; + +import java.io.Serializable; +import br.com.codenation.hospital.domain.Hospital; + +public class HospitalDTO implements Serializable{ + private static final long serialVersionUID = 1L; + + private String id; + private String name; + private String address; + private int beds; + private int availableBeds; + private String longitude; + private String latitude; + + public HospitalDTO() { + + } + + public HospitalDTO(Hospital obj) { + this.id = obj.getId(); + this.name = obj.getName(); + this.address = obj.getAddress(); + this.beds = obj.getBeds(); + this.availableBeds = obj.getAvailableBeds(); + if(obj.getLocation() != null) { + this.longitude = String.valueOf(obj.getLocation().getPosition().getX()); + this.latitude = String.valueOf(obj.getLocation().getPosition().getY()); + } + } + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getAddress() { + return address; + } + + public void setAddress(String address) { + this.address = address; + } + + public int getBeds() { + return beds; + } + + public void setBeds(int beds) { + this.beds = beds; + } + + public int getAvailableBeds() { + return availableBeds; + } + + public void setAvailableBeds(int availableBeds) { + this.availableBeds = availableBeds; + } + + public String getLongitude() { + return longitude; + } + + public void setLongitude(String longitude) { + this.longitude = longitude; + } + + public String getLatitude() { + return latitude; + } + + public void setLatitude(String latitude) { + this.latitude = latitude; + } +} \ No newline at end of file diff --git a/sut-gestaoHospital/src/main/java/br/com/codenation/hospital/dto/LocationDTO.java b/sut-gestaoHospital/src/main/java/br/com/codenation/hospital/dto/LocationDTO.java new file mode 100644 index 0000000..10c5475 --- /dev/null +++ b/sut-gestaoHospital/src/main/java/br/com/codenation/hospital/dto/LocationDTO.java @@ -0,0 +1,96 @@ +package br.com.codenation.hospital.dto; + +import java.io.Serializable; +import java.util.Objects; + +import br.com.codenation.hospital.domain.Location; +import br.com.codenation.hospital.domain.Product; + +public class LocationDTO implements Serializable { + private static final long serialVersionUID = 10L; + + private String id; + private String name; + private String referenceId; + private String category; + private String longitude; + private String latitude; + + public LocationDTO(String category, String name, String longitude, String latitude) { + this.category = category; + this.name = name; + this.longitude = longitude; + this.latitude = latitude; + } + + public LocationDTO(Location obj) { + this.id = obj.getId(); + this.category = obj.getLocationCategory().getDescricao(); + this.referenceId = obj.getReferenceId(); + this.name = obj.getName(); + this.latitude = String.valueOf(obj.getPosition().getY()); + this.longitude = String.valueOf(obj.getPosition().getX()); + } + + public String getId() { + return id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getCategory() { + return category; + } + + public void setCategory(String category) { + this.category = category; + } + + public String getLongitude() { + return longitude; + } + + public void setLongitude(String longitude) { + this.longitude = longitude; + } + + public String getLatitude() { + return latitude; + } + + public void setLatitude(String latitude) { + this.latitude = latitude; + } + + public String getReferenceId() { + return referenceId; + } + + public void setReferenceId(String referenceId) { + this.referenceId = referenceId; + } + + @Override + public boolean equals(final Object o) { + if (this == o) return true; + if (o == null || this.getClass() != o.getClass()) return false; + final LocationDTO that = (LocationDTO) o; + return Objects.equals(this.getId(), that.getId()) && + Objects.equals(this.getReferenceId(), that.getReferenceId()) && + Objects.equals(this.getName(), that.getName()) && + Objects.equals(this.getLongitude(), that.getLongitude()) && + Objects.equals(this.getLatitude(), that.getLatitude()) && + Objects.equals(this.getCategory(), that.getCategory()); + } + + @Override + public int hashCode() { + return Objects.hash(this.getId(), this.getName(), this.getReferenceId(), this.getLongitude(), this.getLatitude(), this.getCategory()); + } +} diff --git a/sut-gestaoHospital/src/main/java/br/com/codenation/hospital/dto/ProductDTO.java b/sut-gestaoHospital/src/main/java/br/com/codenation/hospital/dto/ProductDTO.java new file mode 100644 index 0000000..d21552c --- /dev/null +++ b/sut-gestaoHospital/src/main/java/br/com/codenation/hospital/dto/ProductDTO.java @@ -0,0 +1,97 @@ +package br.com.codenation.hospital.dto; + +import java.io.Serializable; + +import javax.validation.constraints.NotEmpty; + +import br.com.codenation.hospital.domain.Product; +import br.com.codenation.hospital.domain.ProductType; + +public class ProductDTO implements Serializable{ + private static final long serialVersionUID = 1L; + + private String id; + + @NotEmpty + private String name; + + + private String description; + + @NotEmpty + private int quantity; + + @NotEmpty + private ProductType productType; + + //added reference to hospital + private String hospitalDTOId; + + public ProductDTO() { + + } + + public ProductDTO(Product obj) { + this.id = obj.getId(); + this.name = obj.getName(); + this.quantity = obj.getQuantity(); + this.productType = obj.getProductType(); + this.hospitalDTOId = obj.getHospitalId(); + } + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getProductName() { + return name; + } + + public void setProductName(String productName) { + this.name = productName; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public int getQuantity() { + return quantity; + } + + public void setQuantity(int quantity) { + this.quantity = quantity; + } + + public ProductType getProductType() { + return productType; + } + + public void setProductType(ProductType productType) { + this.productType = productType; + } + //added setters and getters for hospitalId + public void setHospitalId(String hospitalDTOId) { + this.hospitalDTOId = hospitalDTOId; + } + + public String getHospitalDTOId() { + return hospitalDTOId; + } +} \ No newline at end of file diff --git a/sut-gestaoHospital/src/main/java/br/com/codenation/hospital/integration/LocationIQResponse.java b/sut-gestaoHospital/src/main/java/br/com/codenation/hospital/integration/LocationIQResponse.java new file mode 100644 index 0000000..aa6e891 --- /dev/null +++ b/sut-gestaoHospital/src/main/java/br/com/codenation/hospital/integration/LocationIQResponse.java @@ -0,0 +1,107 @@ +package br.com.codenation.hospital.integration; + +import java.util.ArrayList; +import java.util.List; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonGetter; +import com.fasterxml.jackson.annotation.JsonProperty; + +public class LocationIQResponse { + + private String placeId; + private String licence; + private String osmType; + private String osmId; + private List boundingbox; + private String lat; + private String lon; + private String displayName; + private String classfication; + private String type; + private String importance; + private String icon; + + + public LocationIQResponse() { + boundingbox = new ArrayList<>(); + } + + @JsonCreator + public LocationIQResponse( + @JsonProperty("place_id") String placeId, + @JsonProperty("licence") String licence, + @JsonProperty("osm_type") String osmType, + @JsonProperty("osm_id") String osmId, + @JsonProperty("boundingbox") List boundingbox, + @JsonProperty("lat") String lat, + @JsonProperty("lon") String lon, + @JsonProperty("display_name") String displayName, + @JsonProperty("class") String classfication, + @JsonProperty("type") String type, + @JsonProperty("importance") String importance, + @JsonProperty("icon") String icon) + { + this.placeId = placeId; + this.licence = licence; + this.osmType = osmType; + this.osmId = osmId; + this.boundingbox = boundingbox; + this.lat = lat; + this.lon = lon; + this.displayName = displayName; + this.classfication = classfication; + this.type = type; + this.importance = importance; + this.icon = icon; + } + + public String getPlaceId() { + return placeId; + } + + public String getLicence() { + return licence; + } + + public String getOsmType() { + return osmType; + } + + public String getOsmId() { + return osmId; + } + + public List getBoundingbox() { + return boundingbox; + } + + public String getLat() { + return lat; + } + + public String getLon() { + return lon; + } + + public String getDisplayName() { + return displayName; + } + + public String getClassfication() { + return classfication; + } + + public String getType() { + return type; + } + + public String getImportance() { + return importance; + } + + public String getIcon() { + return icon; + } + +} diff --git a/sut-gestaoHospital/src/main/java/br/com/codenation/hospital/integration/LocationIQService.java b/sut-gestaoHospital/src/main/java/br/com/codenation/hospital/integration/LocationIQService.java new file mode 100644 index 0000000..7c4d37a --- /dev/null +++ b/sut-gestaoHospital/src/main/java/br/com/codenation/hospital/integration/LocationIQService.java @@ -0,0 +1,81 @@ +package br.com.codenation.hospital.integration; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.net.HttpURLConnection; +import java.net.MalformedURLException; +import java.net.URL; +import java.net.URLEncoder; +import java.util.ArrayList; +import java.util.List; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Service; + +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; + +import br.com.codenation.hospital.resource.ProductResource; + +@Service +public class LocationIQService { + private static final Logger LOGGER = LoggerFactory.getLogger(LocationIQService.class); + + private final String locationKey = "43b382813d8baa"; + private final String locationFormat = "json"; + private final String locationUrl = "https://us1.locationiq.com/v1/search.php"; + + public LocationIQService() { + } + + public List getLocationIQResponse(String search) { + return CallLocationIQAPI(search); + } + + private List CallLocationIQAPI(String search) { + ArrayList locationsResponse = new ArrayList<>(); + + try { + Thread.sleep(1000); + + URL url = new URL(locationUrl + "?key=" + locationKey + "&q=" + URLEncoder.encode(search, "UTF-8") + "&format=" + locationFormat); + HttpURLConnection conn = (HttpURLConnection) url.openConnection(); + conn.setRequestMethod("GET"); + conn.setRequestProperty("Accept", "application/json"); + + BufferedReader br; + if (200 <= conn.getResponseCode() && conn.getResponseCode() <= 299) { + br = new BufferedReader(new InputStreamReader(conn.getInputStream())); + } else { + br = new BufferedReader(new InputStreamReader(conn.getErrorStream())); + } + + StringBuilder sb = new StringBuilder(); + String output; + while ((output = br.readLine()) != null) { + sb.append(output); + } + + if (conn.getResponseCode() != 200) { + LOGGER.error("Failed : HTTP error - Error with message: {}", sb.toString()); + throw new RuntimeException("Failed : HTTP error code : " + conn.getResponseCode()); + } + + conn.disconnect(); + + locationsResponse = new ObjectMapper() + .readValue(sb.toString(), new TypeReference>() {}); + + } catch (MalformedURLException e) { + LOGGER.error("getLocationIQResponse - MalformedURLException - Error with message: {}", e.getMessage()); + } catch (IOException e) { + LOGGER.error("getLocationIQResponse - IOException - Error with message: {}", e.getMessage()); + } catch (InterruptedException e) { + LOGGER.error("getLocationIQResponse - InterruptedException - Error with message: {}", e.getMessage()); + } + + return locationsResponse; + } +} diff --git a/sut-gestaoHospital/src/main/java/br/com/codenation/hospital/repository/HospitalRepository.java b/sut-gestaoHospital/src/main/java/br/com/codenation/hospital/repository/HospitalRepository.java new file mode 100644 index 0000000..583206f --- /dev/null +++ b/sut-gestaoHospital/src/main/java/br/com/codenation/hospital/repository/HospitalRepository.java @@ -0,0 +1,16 @@ +package br.com.codenation.hospital.repository; + +import java.util.List; + +import org.springframework.data.geo.Point; +import org.springframework.data.mongodb.repository.MongoRepository; +import org.springframework.stereotype.Repository; +import br.com.codenation.hospital.domain.Hospital; +import br.com.codenation.hospital.domain.Product; + +@Repository +public interface HospitalRepository extends MongoRepository{ + List findByNameLikeIgnoreCase(String name); + +// List findByPositionNearAndAvailableBeds(Point p, int beds); +} \ No newline at end of file diff --git a/sut-gestaoHospital/src/main/java/br/com/codenation/hospital/repository/LocationRepository.java b/sut-gestaoHospital/src/main/java/br/com/codenation/hospital/repository/LocationRepository.java new file mode 100644 index 0000000..b25c09a --- /dev/null +++ b/sut-gestaoHospital/src/main/java/br/com/codenation/hospital/repository/LocationRepository.java @@ -0,0 +1,26 @@ +package br.com.codenation.hospital.repository; + +import br.com.codenation.hospital.domain.Hospital; +import br.com.codenation.hospital.domain.Location; +import br.com.codenation.hospital.domain.Product; + +import java.util.List; + +import org.springframework.data.geo.Box; +import org.springframework.data.geo.Circle; +import org.springframework.data.geo.Distance; +import org.springframework.data.geo.Point; +import org.springframework.data.mongodb.repository.MongoRepository; +import org.springframework.stereotype.Repository; + +@Repository +public interface LocationRepository extends MongoRepository { + + List findByNameAndLocationNear(String sid, Point p, Distance d); + + List findByNameLikeIgnoreCase(String subject); + + List findByPositionNear(Point p, Distance d); + + +} diff --git a/sut-gestaoHospital/src/main/java/br/com/codenation/hospital/repository/PatientRepository.java b/sut-gestaoHospital/src/main/java/br/com/codenation/hospital/repository/PatientRepository.java new file mode 100644 index 0000000..ec5a29f --- /dev/null +++ b/sut-gestaoHospital/src/main/java/br/com/codenation/hospital/repository/PatientRepository.java @@ -0,0 +1,11 @@ +package br.com.codenation.hospital.repository; + +import org.springframework.data.mongodb.repository.MongoRepository; +import org.springframework.stereotype.Repository; + +import br.com.codenation.hospital.domain.Patient; + +@Repository +public interface PatientRepository extends MongoRepository{ + +} \ No newline at end of file diff --git a/sut-gestaoHospital/src/main/java/br/com/codenation/hospital/repository/ProductRepository.java b/sut-gestaoHospital/src/main/java/br/com/codenation/hospital/repository/ProductRepository.java new file mode 100644 index 0000000..ae6f117 --- /dev/null +++ b/sut-gestaoHospital/src/main/java/br/com/codenation/hospital/repository/ProductRepository.java @@ -0,0 +1,14 @@ +package br.com.codenation.hospital.repository; + +import java.util.List; + +import org.bson.types.ObjectId; +import org.springframework.data.mongodb.repository.MongoRepository; +import br.com.codenation.hospital.domain.Product; + + +public interface ProductRepository extends MongoRepository{ + Product findBy_id(ObjectId _id); + + List findByNameLikeIgnoreCase(String name); +} \ No newline at end of file diff --git a/sut-gestaoHospital/src/main/java/br/com/codenation/hospital/resource/HospitalResource.java b/sut-gestaoHospital/src/main/java/br/com/codenation/hospital/resource/HospitalResource.java new file mode 100644 index 0000000..ca5140d --- /dev/null +++ b/sut-gestaoHospital/src/main/java/br/com/codenation/hospital/resource/HospitalResource.java @@ -0,0 +1,129 @@ +package br.com.codenation.hospital.resource; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.stream.Collectors; + +import br.com.codenation.hospital.domain.LocationBuilder; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +import br.com.codenation.hospital.constant.Constant; +import br.com.codenation.hospital.domain.Hospital; +import br.com.codenation.hospital.domain.Location; +import br.com.codenation.hospital.dto.HospitalDTO; +import br.com.codenation.hospital.resource.exception.ResourceNotFoundException; +import br.com.codenation.hospital.services.HospitalService; + +@CrossOrigin("http://localhost:4200") // permissão para o Angular +@RestController +@RequestMapping(path = Constant.V1) +public class HospitalResource { + private static final Logger LOGGER = LoggerFactory.getLogger(ProductResource.class); + + @Autowired + private HospitalService service; + + @GetMapping() + public ResponseEntity> findAll() { + try { + List list = service.findAll(); + List listDTO = list.stream().map(x -> new HospitalDTO(x)).collect(Collectors.toList()); + return ResponseEntity.ok().body(listDTO); + } catch (Exception e) { + LOGGER.error("findAllHospital - Error with message: {}", e.getMessage()); + return ResponseEntity.badRequest().build(); + } + } + + @GetMapping(path = "/{hospital_id}") + public ResponseEntity findById(@PathVariable String hospital_id) { + try { + Hospital obj = service.findById(hospital_id); + + HospitalDTO hospitalDTO = new HospitalDTO(obj); + + return Optional.ofNullable(hospitalDTO).map(hospitalResponse -> ResponseEntity.ok().body(hospitalResponse)) + .orElseGet(() -> ResponseEntity.notFound().build()); + } catch (Exception e) { + LOGGER.error("findHospitalById - Error with message: {}", e.getMessage()); + return ResponseEntity.notFound().build(); + } + } + + + @PostMapping() + public ResponseEntity insert(@RequestBody HospitalDTO objDTO) { + try { + Hospital obj = service.fromDTO(objDTO); + if(obj.getName().equals("") || obj.getAddress().equals("") || obj.getBeds()<0 || obj.getAvailableBeds()<0) { + throw new ResourceNotFoundException("Preencha os campos corretamente!"); //precisa tratar erro + } + obj = service.insert(obj); + HospitalDTO hospitalDTO = new HospitalDTO(obj); + return Optional.ofNullable(hospitalDTO).map(hospitalResponse -> ResponseEntity.ok().body(hospitalResponse)) + .orElseGet(() -> ResponseEntity.notFound().build()); + } catch (Exception e) { + LOGGER.error("insertHospital - Handling error with message: {}", e.getMessage()); + return ResponseEntity.badRequest().build(); + } + } + + @DeleteMapping(path = "/{hospital_id}") + public ResponseEntity deleteById(@PathVariable String hospital_id) { + try { + Hospital obj = service.findById(hospital_id); + if (obj != null) { + service.delete(hospital_id); + } + return Optional.ofNullable(obj) + .map(hospitalResponse -> ResponseEntity.ok().body("Hospital apagado id: " + hospital_id)) + .orElseGet(() -> ResponseEntity.notFound().build()); + } catch (Exception e) { + LOGGER.error("deleteHospitalById - Handling error with message: {}", e.getMessage()); + return ResponseEntity.badRequest().build(); + } + } + + @PutMapping(path = "/{hospital_id}") + public ResponseEntity update(@RequestBody HospitalDTO objDTO, @PathVariable String hospital_id) { + try { + Hospital obj = service.fromDTO(objDTO); + obj.setId(hospital_id); + if(obj.getName().equals("") || obj.getAddress().equals("") || obj.getBeds()<0 || obj.getAvailableBeds()<0) { + throw new ResourceNotFoundException("Preencha os campos corretamente!"); //precisa tratar erro + } + obj = service.update(obj); + HospitalDTO hospitalDTO = new HospitalDTO(obj); + return Optional.ofNullable(hospitalDTO).map(hospitalResponse -> ResponseEntity.ok().body(hospitalResponse)) + .orElseGet(() -> ResponseEntity.notFound().build()); + } catch (Exception e) { + LOGGER.error("updateHospital - Handling error with message: {}", e.getMessage()); + return ResponseEntity.badRequest().build(); + } + } + + @GetMapping(path = "/{id}/leitos") + public Map verificaLeitosDisponiveis(@PathVariable String id) { + Hospital hospital = service.findById(id); + Map leitos = new HashMap<>(); + leitos.put("leitos", hospital.getAvailableBeds()); + return leitos; + } + + @GetMapping(path = "/maisProximo") + public HospitalDTO hospitalMaisProximo(@RequestParam Double lat, @RequestParam Double lon, @RequestParam Double raioMaximo) { + return service.findHospitalMaisProximoComVagas(lat, lon, raioMaximo); + } + + @PostMapping(path = "{id}/transferencia/{productId}") + public String transferenciaProduto(@PathVariable String id, @PathVariable String productId, @RequestBody Integer quantidade) { + Hospital hospital = service.findById(id); + return service.transfereProduto(hospital, productId, quantidade); + } +} \ No newline at end of file diff --git a/sut-gestaoHospital/src/main/java/br/com/codenation/hospital/resource/LocationResource.java b/sut-gestaoHospital/src/main/java/br/com/codenation/hospital/resource/LocationResource.java new file mode 100644 index 0000000..6cecd28 --- /dev/null +++ b/sut-gestaoHospital/src/main/java/br/com/codenation/hospital/resource/LocationResource.java @@ -0,0 +1,66 @@ +package br.com.codenation.hospital.resource; + +import java.util.List; +import java.util.Optional;import java.util.stream.Collector; +import java.util.stream.Collectors; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.geo.Distance; +import org.springframework.data.geo.Metrics; +import org.springframework.data.geo.Point; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.CrossOrigin; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +import br.com.codenation.hospital.constant.Constant; +import br.com.codenation.hospital.domain.Hospital; +import br.com.codenation.hospital.domain.Location; +import br.com.codenation.hospital.domain.LocationCategory; +import br.com.codenation.hospital.dto.HospitalDTO; +import br.com.codenation.hospital.dto.LocationDTO; +import br.com.codenation.hospital.dto.ProductDTO; +import br.com.codenation.hospital.repository.LocationRepository; +import br.com.codenation.hospital.services.HospitalService; +import br.com.codenation.hospital.services.LocationService; + +@CrossOrigin("http://localhost:4200") // permissão para o Angular +@RestController +@RequestMapping(path = Constant.V1Path) +public class LocationResource { + private static final Logger LOGGER = LoggerFactory.getLogger(LocationResource.class); + @Autowired + private LocationService locationService; + + @GetMapping(path = "/proximidades") + public ResponseEntity> findLocationNearHospitalBy(@PathVariable String hospital_id) { + try { + List locations = locationService.findLocationNearHospitalBy(hospital_id); + + return Optional.ofNullable(locations).map(productReponse -> ResponseEntity.ok().body(productReponse)) + .orElseGet(() -> ResponseEntity.notFound().build()); + } catch (Exception ex) { + LOGGER.error("findLocationNearHospitalBy - Handling error with message: {}", ex.getMessage()); + return ResponseEntity.badRequest().build(); + } + } + + @GetMapping(path = "/hospitaisProximos") + public ResponseEntity> findHospitalNearHospitalBy(@PathVariable String hospital_id, @RequestParam Double raio) { + try { + List locations = locationService.findHospitalNearHospitalBy(hospital_id, raio); + + return Optional.ofNullable(locations).map(productReponse -> ResponseEntity.ok().body(productReponse)) + .orElseGet(() -> ResponseEntity.notFound().build()); + } catch (Exception ex) { + LOGGER.error("findHospitalNearHospitalBy - Handling error with message: {}", ex.getMessage()); + return ResponseEntity.badRequest().build(); + } + } +} diff --git a/sut-gestaoHospital/src/main/java/br/com/codenation/hospital/resource/PatientResource.java b/sut-gestaoHospital/src/main/java/br/com/codenation/hospital/resource/PatientResource.java new file mode 100644 index 0000000..ef62920 --- /dev/null +++ b/sut-gestaoHospital/src/main/java/br/com/codenation/hospital/resource/PatientResource.java @@ -0,0 +1,113 @@ +package br.com.codenation.hospital.resource; + +import java.util.List; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.CrossOrigin; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import br.com.codenation.hospital.constant.Constant; +import br.com.codenation.hospital.domain.Hospital; +import br.com.codenation.hospital.domain.Patient; +import br.com.codenation.hospital.resource.exception.ResourceNotFoundException; +import br.com.codenation.hospital.services.HospitalService; +import br.com.codenation.hospital.services.PatientService; + +@CrossOrigin("http://localhost:4200") // permissão para o Angular +@RestController +@RequestMapping(path = Constant.V1Path) +public class PatientResource { + private static final Logger LOGGER = LoggerFactory.getLogger(ProductResource.class); + + @Autowired + private PatientService service; + + @Autowired + private HospitalService hospitalService; + + @GetMapping(path = "pacientes") + public ResponseEntity> findPatients(@PathVariable String hospital_id) { + try { + Hospital obj = hospitalService.findById(hospital_id); + List patientList = obj.getPatients(); + if (patientList != null) { + return ResponseEntity.ok(patientList); + } + throw new ResourceNotFoundException("Hospital sem pacientes!"); + } catch (Exception e) { + LOGGER.error("findPatients - Error with message: {}", e.getMessage()); + return ResponseEntity.notFound().build(); + } + } + + @GetMapping(path = "pacientes/{patientId}") + public ResponseEntity findPatientById(@PathVariable String hospital_id, @PathVariable String patientId) { + try { + Patient patient = service.findById(patientId); + return ResponseEntity.ok().body(patient); + } catch (Exception e) { + LOGGER.error("findPatientById - Error with message: {}", e.getMessage()); + return ResponseEntity.notFound().build(); + } + } + + // added post endpint for insert paciente + @PostMapping(path = "paciente") + public ResponseEntity insertPaciente(@RequestBody Patient patient) { + try { + Hospital hospital = hospitalService.findById(patient.getIdHospital()); + return ResponseEntity.ok(hospitalService.checkIn(hospital, patient)); + } catch (Exception e) { + LOGGER.error("insert Paciente - Error with message: {}", e.getMessage()); + return ResponseEntity.notFound().build(); + } + } + + + @PostMapping(path = "pacientes/checkin", produces = "application/json") + public ResponseEntity checkinPacient(@PathVariable("hospital_id") String idHospital, @RequestBody Patient patient) { + try { + Hospital hospital = hospitalService.findById(idHospital); + return ResponseEntity.ok(hospitalService.checkIn(hospital, patient)); + } catch (Exception e) { + LOGGER.error("checkinPacient - Error with message: {}", e.getMessage()); + return ResponseEntity.notFound().build(); + } + } + + @PostMapping(path = "pacientes/checkout", produces = "application/json") + public ResponseEntity checkoutPacient(@PathVariable("hospital_id") String idHospital, @RequestBody String idPatient) { + try { + Hospital hospital = hospitalService.findById(idHospital); + return ResponseEntity.ok(hospitalService.checkOut(hospital, idPatient)); + } catch (Exception e) { + LOGGER.error("checkoutPacient - Error with message: {}", e.getMessage()); + return ResponseEntity.notFound().build(); + } + } + + @PutMapping(path = "pacientes/{patientId}") + public ResponseEntity updatePatient(@PathVariable("hospital_id") String idHospital, @PathVariable String patientId, + @RequestBody Patient patient) { + try { + Patient p = service.findById(patientId); + p.setName(patient.getName()); + p.setCpf(patient.getCpf()); + p.setBirthDate(patient.getBirthDate()); + p.setGender(patient.getGender()); + return ResponseEntity.ok(service.update(p)); + } catch (Exception e) { + LOGGER.error("updatePatient - Error with message: {}", e.getMessage()); + return ResponseEntity.notFound().build(); + } + } +} \ No newline at end of file diff --git a/sut-gestaoHospital/src/main/java/br/com/codenation/hospital/resource/ProductResource.java b/sut-gestaoHospital/src/main/java/br/com/codenation/hospital/resource/ProductResource.java new file mode 100644 index 0000000..1426242 --- /dev/null +++ b/sut-gestaoHospital/src/main/java/br/com/codenation/hospital/resource/ProductResource.java @@ -0,0 +1,120 @@ +package br.com.codenation.hospital.resource; + +import java.util.List; +import java.util.Optional; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.CrossOrigin; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import br.com.codenation.hospital.constant.Constant; +import br.com.codenation.hospital.dto.ProductDTO; +import br.com.codenation.hospital.services.ProductService; + +@CrossOrigin("http://localhost:4200") // permissão para o Angular +@RestController +//@RequestMapping(path = Constant.V1Path) + +//Para los resorurces de Product está utilizando como path base para los endpoints V1Path="/v1/hospitais/{hospital_id}/". +//Dado que no se manejan aún los endpoint con variables en el paht ({hospital_id}), se utiliza como path base V1= "/v1/hospitais/"y +//se modifican los endpoints correspondientes para obtener el id ({hospital_id}) sin necesidad de que vayan en el path +@RequestMapping(path = Constant.V1) + +public class ProductResource { + private static final Logger LOGGER = LoggerFactory.getLogger(ProductResource.class); + + @Autowired + private ProductService service; + + @GetMapping(path = "estoque/{produto_id}") + public ResponseEntity findProductBy(@PathVariable String hospital_id, @PathVariable String produto_id) { + try { + ProductDTO productDTO = service.findById(produto_id); + return Optional.ofNullable(productDTO).map(productReponse -> ResponseEntity.ok().body(productReponse)) + .orElseGet(() -> ResponseEntity.notFound().build()); + } catch (Exception ex) { + LOGGER.error("findProductBy - Handling error with message: {}", ex.getMessage()); + return ResponseEntity.notFound().build(); + } + } + + @GetMapping(path = "estoque") + public ResponseEntity> findAllProductBy(@PathVariable String hospital_id) { + try { + List productList = service.findByHospitalId(hospital_id); + return Optional.ofNullable(productList).map(productReponse -> ResponseEntity.ok().body(productReponse)) + .orElseGet(() -> ResponseEntity.notFound().build()); + } catch (Exception ex) { + LOGGER.error("findAllProductBy - Handling error with message: {}", ex.getMessage()); + return ResponseEntity.badRequest().build(); + } + } + + /*@PostMapping(path = "estoque") + public ResponseEntity insert(@PathVariable String hospital_id, @RequestBody ProductDTO productDTO) { + try { + ProductDTO newProductDTO = service.insert(hospital_id, productDTO); + return Optional.ofNullable(newProductDTO).map(productReponse -> ResponseEntity.ok().body(productReponse)) + .orElseGet(() -> ResponseEntity.notFound().build()); + } catch (Exception ex) { + LOGGER.error("insert - Handling error with message: {}", ex.getMessage()); + return ResponseEntity.badRequest().build(); + } + }*/ + + //Modificado endpoint (original) para insertar un producto en un hospital sin utilizar PathVariable hospital_id. + //hospital_id lo obtiene del productDTO + @PostMapping(path = "estoque") + public ResponseEntity insert(@RequestBody ProductDTO productDTO) { + try { + String hospital_id = productDTO.getHospitalDTOId(); + ProductDTO newProductDTO = service.insert(hospital_id, productDTO); + return Optional.ofNullable(newProductDTO).map(productReponse -> ResponseEntity.ok().body(productReponse)) + .orElseGet(() -> ResponseEntity.notFound().build()); + } catch (Exception ex) { + LOGGER.error("insert - Handling error with message: {}", ex.getMessage()); + return ResponseEntity.badRequest().build(); + } + } + + @DeleteMapping(path = "estoque/{produto_id}") + public ResponseEntity delete(@PathVariable String hospital_id, @PathVariable String produto_id) { + try { + ProductDTO deleteProductDTO = service.findById(produto_id); + if (deleteProductDTO != null) { + service.delete(hospital_id, deleteProductDTO.getId()); + } + return Optional.ofNullable(deleteProductDTO) + .map(productReponse -> ResponseEntity.ok().body("Produto apagado id: " + produto_id)) + .orElseGet(() -> ResponseEntity.notFound().build()); + } catch (Exception ex) { + LOGGER.error("delete - Handling error with message: {}", ex.getMessage()); + return ResponseEntity.badRequest().build(); + } + } + + @PutMapping(path = "estoque/{produto_id}") + public ResponseEntity update(@RequestBody ProductDTO productDTO, @PathVariable String hospital_id, + @PathVariable String produto_id) { + try { + productDTO.setId(produto_id); + ProductDTO updateProductDTO = service.update(hospital_id, productDTO); + return Optional.ofNullable(updateProductDTO) + .map(hospitalReponse -> ResponseEntity.ok().body(hospitalReponse)) + .orElseGet(() -> ResponseEntity.notFound().build()); + } catch (Exception ex) { + LOGGER.error("update - Handling error with message: {}", ex.getMessage()); + return ResponseEntity.badRequest().build(); + } + } +} \ No newline at end of file diff --git a/sut-gestaoHospital/src/main/java/br/com/codenation/hospital/resource/TestResource.java b/sut-gestaoHospital/src/main/java/br/com/codenation/hospital/resource/TestResource.java new file mode 100644 index 0000000..9eb063b --- /dev/null +++ b/sut-gestaoHospital/src/main/java/br/com/codenation/hospital/resource/TestResource.java @@ -0,0 +1,51 @@ +package br.com.codenation.hospital.resource; + +import java.util.Optional; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import br.com.codenation.hospital.constant.Constant; +import br.com.codenation.hospital.services.TestService; + +@RestController +@RequestMapping(path = Constant.V1) + +// Added a resource for test + +public class TestResource { + private static final Logger LOGGER = LoggerFactory.getLogger(TestResource.class); + + @Autowired + private TestService service; + + @DeleteMapping(path = "/test/deleteAll") + public ResponseEntity deleteAll() { + try { + service.deleteAll(); + return ResponseEntity.ok().body("Deleted all data"); + + } catch (Exception e) { + LOGGER.error("deleteAll - Handling error with message: {}", e.getMessage()); + return ResponseEntity.badRequest().build(); + } + } + + @GetMapping(path = "/test/getAll") + public ResponseEntity getAll() { + try { + Object allData = service.getAll(); + return Optional.ofNullable(allData).map(testResponse -> ResponseEntity.ok().body(testResponse)) + .orElseGet(() -> ResponseEntity.notFound().build()); + } catch (Exception e) { + LOGGER.error("getAll - Handling error with message: {}", e.getMessage()); + return ResponseEntity.badRequest().build(); + } + } +} diff --git a/sut-gestaoHospital/src/main/java/br/com/codenation/hospital/resource/exception/HospitalCheioException.java b/sut-gestaoHospital/src/main/java/br/com/codenation/hospital/resource/exception/HospitalCheioException.java new file mode 100644 index 0000000..13d9d69 --- /dev/null +++ b/sut-gestaoHospital/src/main/java/br/com/codenation/hospital/resource/exception/HospitalCheioException.java @@ -0,0 +1,17 @@ +package br.com.codenation.hospital.resource.exception; + +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.ResponseStatus; + +@ResponseStatus(value = HttpStatus.BAD_REQUEST) +public class HospitalCheioException extends RuntimeException{ + private static final long serialVersionUID = 1L; + + public HospitalCheioException() { + super("Hospital sem vagas para check in!"); + } + + public HospitalCheioException(String message) { + super(message); + } +} diff --git a/sut-gestaoHospital/src/main/java/br/com/codenation/hospital/resource/exception/ResourceNotFoundException.java b/sut-gestaoHospital/src/main/java/br/com/codenation/hospital/resource/exception/ResourceNotFoundException.java new file mode 100644 index 0000000..7e758f4 --- /dev/null +++ b/sut-gestaoHospital/src/main/java/br/com/codenation/hospital/resource/exception/ResourceNotFoundException.java @@ -0,0 +1,21 @@ +package br.com.codenation.hospital.resource.exception; + +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.ResponseStatus; + +//@ControllerAdvice //tratar possiveis erros nas requisições +@ResponseStatus(value = HttpStatus.NOT_FOUND) +public class ResourceNotFoundException extends RuntimeException { + private static final long serialVersionUID = 1L; + + public ResourceNotFoundException(String message) { + super(message); + } + // @ExceptionHandler(ObjectNotFoundException.class) +// public ResponseEntity objectNotFound(ObjectNotFoundException e, HttpServletRequest request){ +// +// HttpStatus status = HttpStatus.NOT_FOUND; +// StandardError error = new StandardError(System.currentTimeMillis(), status.value(), "Não encontrado.", e.getMessage(), request.getRequestURI()); +// return ResponseEntity.status(status).body(error); +// } +} \ No newline at end of file diff --git a/sut-gestaoHospital/src/main/java/br/com/codenation/hospital/resource/exception/StandardError.java b/sut-gestaoHospital/src/main/java/br/com/codenation/hospital/resource/exception/StandardError.java new file mode 100644 index 0000000..12c3f27 --- /dev/null +++ b/sut-gestaoHospital/src/main/java/br/com/codenation/hospital/resource/exception/StandardError.java @@ -0,0 +1,66 @@ +package br.com.codenation.hospital.resource.exception; + +import java.io.Serializable; + +public class StandardError implements Serializable{ + private static final long serialVersionUID = -7332208912862259408L; + + private Long timestamp; + private Integer status; + private String error; + private String message; + private String path; + + public StandardError() { + + } + + public StandardError(Long timestamp, Integer status, String error, String message, String path) { + super(); + this.timestamp = timestamp; + this.status = status; + this.error = error; + this.message = message; + this.path = path; + } + + public Long getTimestamp() { + return timestamp; + } + + public void setTimestamp(Long timestamp) { + this.timestamp = timestamp; + } + + public Integer getStatus() { + return status; + } + + public void setStatus(Integer status) { + this.status = status; + } + + public String getError() { + return error; + } + + public void setError(String error) { + this.error = error; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public String getPath() { + return path; + } + + public void setPath(String path) { + this.path = path; + } +} \ No newline at end of file diff --git a/sut-gestaoHospital/src/main/java/br/com/codenation/hospital/resource/util/URL.java b/sut-gestaoHospital/src/main/java/br/com/codenation/hospital/resource/util/URL.java new file mode 100644 index 0000000..4ad7c54 --- /dev/null +++ b/sut-gestaoHospital/src/main/java/br/com/codenation/hospital/resource/util/URL.java @@ -0,0 +1,15 @@ +package br.com.codenation.hospital.resource.util; + +import java.io.UnsupportedEncodingException; +import java.net.URLDecoder; + +public class URL { + + public static String decodeParam(String text) { + try { + return URLDecoder.decode(text, "UTF-8"); + } catch (UnsupportedEncodingException e) { + return ""; + } + } +} \ No newline at end of file diff --git a/sut-gestaoHospital/src/main/java/br/com/codenation/hospital/services/HospitalService.java b/sut-gestaoHospital/src/main/java/br/com/codenation/hospital/services/HospitalService.java new file mode 100644 index 0000000..25dd177 --- /dev/null +++ b/sut-gestaoHospital/src/main/java/br/com/codenation/hospital/services/HospitalService.java @@ -0,0 +1,194 @@ +package br.com.codenation.hospital.services; + +import java.util.Date; +import java.util.List; +import java.util.Optional; +import java.util.stream.Collectors; + +import br.com.codenation.hospital.resource.exception.ResourceNotFoundException; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.geo.Point; +import org.springframework.data.mongodb.core.MongoTemplate; +import org.springframework.data.mongodb.core.query.Criteria; +import org.springframework.data.mongodb.core.query.Query; +import org.springframework.stereotype.Service; + +import br.com.codenation.hospital.domain.Hospital; +import br.com.codenation.hospital.domain.Location; +import br.com.codenation.hospital.domain.Patient; +import br.com.codenation.hospital.domain.Product; +import br.com.codenation.hospital.dto.HospitalDTO; +import br.com.codenation.hospital.repository.HospitalRepository; +import br.com.codenation.hospital.repository.PatientRepository; +import br.com.codenation.hospital.repository.ProductRepository; +import br.com.codenation.hospital.resource.exception.HospitalCheioException; +import br.com.codenation.hospital.services.exception.ObjectNotFoundException; + +@Service +public class HospitalService { + + @Autowired + private HospitalRepository repo; + + @Autowired + private PatientRepository patientRepository; + + @Autowired + private ProductRepository productRepository; + + + @Autowired + private LocationService locationService; + + /*Duplicated declaration + @Autowired + private LocationService locationService; + */ + + + public List findAll(){ + return repo.findAll(); + } + + + public Hospital findById(String hospital_id) { + Optional obj = repo.findById(hospital_id); + return obj.orElseThrow(() -> new ObjectNotFoundException("Hospital não encontrado! ID:"+ hospital_id)); + } + + public Hospital insert(Hospital obj) { + Location location = locationService.insertLocationByHospital(obj); + obj.setLocation(location); + + return repo.insert(obj); + } + + public void delete(String hospital_id) { + findById(hospital_id); + repo.deleteById(hospital_id); + } + + public Hospital update(Hospital obj) { + Hospital newObj = findById(obj.getId()); + updateData(newObj, obj); + return repo.save(newObj); + } + + /*Se añade método para guardar los datos del hospital + En el original no estaba, de forma que cuando se añaden productos a un hospital, + no se almacenaba la relación + */ + public Hospital save (Hospital hospital){ + return repo.save(hospital); + } + + private void updateData(Hospital newObj, Hospital obj) { + newObj.setName(obj.getName()); + newObj.setAddress(obj.getAddress()); + newObj.setBeds(obj.getBeds()); + newObj.setAvailableBeds(obj.getAvailableBeds()); + } + + /*Parece que cuando se inserta un nuevo hospital lo busca y si no lo encuentra emite excepción. + Debe de buscarlo en Location o algo así y no deja insertar a través de POST. + Elimino la comprobación para que permita ejecutar + ToDo - Confirmar esto. + */ + public Hospital fromDTO(HospitalDTO objDTO) { + //if(objDTO.getId() == null){ + return new Hospital(objDTO.getId(),objDTO.getName(),objDTO.getAddress(),objDTO.getBeds(),objDTO.getAvailableBeds()); + //} + //return findById(objDTO.getId()); + + } + + public HospitalDTO convertToDTO(Hospital model) { + return new HospitalDTO(model); + } + + public List convertToDTOs(List models) { + return models.stream() + .map(this::convertToDTO) + .collect(Collectors.toList()); + } + + public Patient checkIn(Hospital hospital, Patient patient){ + if(hospital.temVaga()) { + patient.setEntryDate(new Date()); + patient.setActive(true); + patient.setExitDate(null); + patientRepository.save(patient); + + hospital.addPacient(patient); + repo.save(hospital); + + return patient; + } + throw new HospitalCheioException(); + } + + public Patient checkOut(Hospital hospital, String idPatient){ + Patient patient = hospital.getPatients().stream() + .filter(p -> p.getId().equals(idPatient)) + .findFirst() + .orElseThrow(() -> new ObjectNotFoundException("Paciente não encontrado no hospital!")); + hospital.removePacient(patient); + repo.save(hospital); + patient.setActive(false); + patient.setExitDate(new Date()); + return patientRepository.save(patient); + } + + public HospitalDTO findHospitalMaisProximoComVagas(Double lat, Double lon, Double raioMaximo) { + List hospitais = locationService.findHospitalNearLocationBy(lat, lon, raioMaximo); + + return hospitais.stream() + .filter(h -> h.getAvailableBeds() > 0) + .findFirst() + .orElseThrow(() -> new ResourceNotFoundException("Nenhum hospital próximo com vagas encontrado!")); + } + + private Hospital findHospitalProximoComEstoque(String hospitalId, Product produto) { + + List hospitaisDTO = locationService.findHospitalNearHospitalBy(hospitalId, null); + + + List hospitais = hospitaisDTO.stream() + .map(h -> fromDTO(h)) + .collect(Collectors.toList()); + + return hospitais.stream() + .filter(h -> h.getProducts().contains(produto)) + .findFirst() + .orElseThrow(() -> new ResourceNotFoundException("Nenhum hospital próximo com este produto encontrado!")); + } + + public String transfereProduto(Hospital hospital, String idProduto, Integer quantidade) { + //produto existe? + Product product = productRepository.findById(idProduto) + .orElseThrow(()-> new ObjectNotFoundException("Produto não cadastrado em nenhum hospital!")); + //encontra hospital mais prox que contenha o produto + Hospital hospitalOrigem = findHospitalProximoComEstoque(hospital.getId(), product); + product = hospitalOrigem.getProducts().stream() + .filter(p -> p.getId().equals(idProduto)) + .findFirst().get(); + //verifica se tem quandidade suficiente para transferir + if(product.getQuantity() > quantidade + 4){ + //add novo produto no hospital + Product novoProduto = new Product(); + novoProduto.setName(product.getName()); + novoProduto.setDescription(product.getDescription()); + novoProduto.setProductType(product.getProductType()); + novoProduto.setQuantity(quantidade); + productRepository.save(novoProduto); + hospital.setProduct(novoProduto); + //diminui quantidade do hospital origem + product.diminuiQuantidade(quantidade); + productRepository.save(product); + return "transferencia realizada!"; + } + return "transferencia não pode ser feita!"; + } + + +} \ No newline at end of file diff --git a/sut-gestaoHospital/src/main/java/br/com/codenation/hospital/services/LocationService.java b/sut-gestaoHospital/src/main/java/br/com/codenation/hospital/services/LocationService.java new file mode 100644 index 0000000..19cc5d2 --- /dev/null +++ b/sut-gestaoHospital/src/main/java/br/com/codenation/hospital/services/LocationService.java @@ -0,0 +1,223 @@ +package br.com.codenation.hospital.services; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import java.util.stream.Collectors; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.geo.Distance; +import org.springframework.data.geo.Metrics; +import org.springframework.data.geo.Point; +import org.springframework.data.mongodb.core.geo.GeoJsonPoint; +import org.springframework.stereotype.Service; + +import br.com.codenation.hospital.domain.Hospital; +import br.com.codenation.hospital.domain.Location; +import br.com.codenation.hospital.domain.LocationBuilder; +import br.com.codenation.hospital.domain.LocationCategory; +import br.com.codenation.hospital.dto.HospitalDTO; +import br.com.codenation.hospital.dto.LocationDTO; +import br.com.codenation.hospital.integration.LocationIQResponse; +import br.com.codenation.hospital.integration.LocationIQService; +import br.com.codenation.hospital.repository.HospitalRepository; +import br.com.codenation.hospital.repository.LocationRepository; +import br.com.codenation.hospital.services.exception.ObjectNotFoundException; + +@Service +public class LocationService { + + @Autowired + private LocationRepository locationRepository; + + @Autowired + private HospitalRepository hospitalRepository; + + @Autowired + private HospitalService hospitalService; + + @Autowired + private LocationIQService locationIQService; + + public List findByNameAndLocationNear(String name, String longitude, String latitude, Double distance) { + return convertToDTOs(locationRepository.findByNameAndLocationNear(name, + new Point(Double.valueOf(longitude), Double.valueOf(latitude)), + new Distance(distance, Metrics.KILOMETERS))); + } + + public List findByPositionNear(String longitude, String latitude, Double distance) { + return convertToDTOs(locationRepository.findByPositionNear( + new Point(Double.valueOf(longitude), Double.valueOf(latitude)), + new Distance(distance, Metrics.KILOMETERS))); + } + + public List findLocationNearHospitalBy(String id) { + Hospital hospital = hospitalService.findById(id); + + + Double longitude = hospital.getLocation().getPosition().getX(); + Double latitude = hospital.getLocation().getPosition().getY(); + Double distance = 100.0d; + + List locations = locationRepository.findByPositionNear( + new Point(longitude, latitude), + new Distance(distance, Metrics.KILOMETERS)); + + List filterLocations = locations.stream() + .filter(f -> !f.getReferenceId().equals(id)) + .collect(Collectors.toList()); + + return convertToDTOs(filterLocations); + } + + // Usar para encontrar hospital perto de hospital + public List findHospitalNearHospitalBy(String id, Double raio) { + Hospital hospital = hospitalService.findById(id); + + Double longitude = hospital.getLocation().getPosition().getX(); + Double latitude = hospital.getLocation().getPosition().getY(); + + List locations = locationRepository.findByPositionNear( + new Point(longitude, latitude), + new Distance(raio != null ? raio : 10000, Metrics.KILOMETERS)); + + List filterLocations = locations.stream() + .filter(f -> f.getLocationCategory() == LocationCategory.HOSPITAL && !f.getName().equals(hospital.getName())) + .collect(Collectors.toList()); + + List hospitaisDTO = new ArrayList(); + + for (Location location : filterLocations) { + Hospital hosp = hospitalRepository.findByNameLikeIgnoreCase(location.getName()) + .stream() + .findFirst() + .orElse(null); + + if (hosp != null) { + hospitaisDTO.add(hospitalService.convertToDTO(hosp)); + } + } + + return hospitaisDTO; + } + + // Usar para encontrar hospital perto de paciente, enviar endereço do paciente + public List findHospitalNearLocationBy(Double longitude, Double latitude, Double distance) { + if(distance == null) + distance = 100.0d; + + List locations = locationRepository.findByPositionNear( + new Point(longitude, latitude), + new Distance(distance, Metrics.KILOMETERS)); + + List filterLocations = locations.stream() + .filter(f -> f.getLocationCategory() == LocationCategory.HOSPITAL) + .collect(Collectors.toList()); + + List hospitaisDTO = new ArrayList(); + + for (Location location : filterLocations) { + Hospital hospital = hospitalRepository.findByNameLikeIgnoreCase(location.getName()) + .stream() + .findFirst() + .orElse(null); + + if (hospital != null) { + hospitaisDTO.add(hospitalService.convertToDTO(hospital)); + } + } + + return hospitaisDTO; + } + + public List findAll(){ + return convertToDTOs(locationRepository.findAll()); + } + + public LocationDTO findById(String id) { + return convertToDTO(findLocationById(id)); + } + + public List findByNameLikeIgnoreCase(String subject) { + List locations = locationRepository.findByNameLikeIgnoreCase(subject); + return convertToDTOs(locations); + } + + public LocationDTO insert(LocationDTO locationDTO) { + Location location = fromDTO(locationDTO); + return convertToDTO(locationRepository.save(location)); + } + + public void delete(String id) { + locationRepository.deleteById(id); + } + + public LocationDTO update(String id, LocationDTO locationDTO) { + Location updateLocation = findLocationById(id); + Location locationData = fromDTO(locationDTO); + updateLocation.setName(locationData.getName()); + updateLocation.setPosition(locationData.getPosition()); + updateLocation.setLocationCategory(LocationCategory.valueOf(locationDTO.getCategory())); + return convertToDTO(locationRepository.save(updateLocation)); + } + + private Location findLocationById(String id) { + Optional result = locationRepository.findById(id); + return result.orElseThrow(() -> new ObjectNotFoundException("Location não encontrado! ID: "+ id)); + } + + public Location fromDTO(LocationDTO locationDTO) { + GeoJsonPoint locationPoint = new GeoJsonPoint( + Double.valueOf(locationDTO.getLatitude()), + Double.valueOf(locationDTO.getLongitude())); + + return new Location(LocationCategory.valueOf(locationDTO.getCategory()), locationDTO.getName(), locationPoint); + } + + private LocationDTO convertToDTO(Location model) { + return new LocationDTO(model); + } + + private List convertToDTOs(List models) { + return models.stream() + .map(this::convertToDTO) + .collect(Collectors.toList()); + } + + + public Location insertLocationByHospital(Hospital hospital) { + Location locationHospital = new Location(); + + List locationsResponse = locationIQService.getLocationIQResponse(hospital.getAddress()); + + LocationIQResponse locationResponse = new LocationIQResponse(); + if (!locationsResponse.isEmpty()) { + locationResponse = locationsResponse.get(0); + + locationHospital = new LocationBuilder() + .setReferenceId(hospital.getAddress()) + .setLocationCategory(LocationCategory.HOSPITAL) + .setName(hospital.getName()) + .setLatitude(Double.valueOf(locationResponse.getLat())) + .setLongitude(Double.valueOf(locationResponse.getLon())) + .build(); + } else { + locationHospital = new LocationBuilder() + .setReferenceId(hospital.getAddress()) + .setLocationCategory(LocationCategory.HOSPITAL) + .setName(hospital.getName()) + .setLatitude(0D) + .setLongitude(0D) + .build(); + } + //Cuando inserta un hospital, inserta también una location con id "aleatoria" + //que hace que algunos test live fallen. + //Para los test, pongo el id de location el mismo que hospital + locationHospital.setId(hospital.getId()); + + locationRepository.save(locationHospital); + + return locationHospital; + } + +} diff --git a/sut-gestaoHospital/src/main/java/br/com/codenation/hospital/services/PatientService.java b/sut-gestaoHospital/src/main/java/br/com/codenation/hospital/services/PatientService.java new file mode 100644 index 0000000..4a57bc8 --- /dev/null +++ b/sut-gestaoHospital/src/main/java/br/com/codenation/hospital/services/PatientService.java @@ -0,0 +1,25 @@ +package br.com.codenation.hospital.services; + +import java.util.Optional; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import br.com.codenation.hospital.domain.Patient; +import br.com.codenation.hospital.repository.PatientRepository; +import br.com.codenation.hospital.services.exception.ObjectNotFoundException; + +@Service +public class PatientService { + + @Autowired + private PatientRepository repo; + + public Patient findById(String id) { + Optional obj = repo.findById(id); + return obj.orElseThrow(() -> new ObjectNotFoundException("Paciente não encontrado!")); + } + public Patient update(Patient obj) { + return repo.save(obj); + } +} \ No newline at end of file diff --git a/sut-gestaoHospital/src/main/java/br/com/codenation/hospital/services/ProductService.java b/sut-gestaoHospital/src/main/java/br/com/codenation/hospital/services/ProductService.java new file mode 100644 index 0000000..a44851f --- /dev/null +++ b/sut-gestaoHospital/src/main/java/br/com/codenation/hospital/services/ProductService.java @@ -0,0 +1,108 @@ +package br.com.codenation.hospital.services; + +import java.util.List; +import java.util.Optional; +import java.util.stream.Collectors; + +import org.bson.types.ObjectId; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import br.com.codenation.hospital.domain.Hospital; +import br.com.codenation.hospital.domain.Product; +import br.com.codenation.hospital.dto.ProductDTO; +import br.com.codenation.hospital.repository.ProductRepository; +import br.com.codenation.hospital.services.exception.ObjectNotFoundException; + +@Service +public class ProductService { + + @Autowired + private ProductRepository productRepository; + + @Autowired + private HospitalService hospitalService; + + public List findAll(){ + return convertToDTOs(productRepository.findAll()); + } + + public ProductDTO findById(ObjectId id) { + return convertToDTO(productRepository.findBy_id(id)); + } + + public ProductDTO findById(String id) { + return convertToDTO(findProductById(id)); + } + + private Product findProductById(String id) { + Optional result = productRepository.findById(id); + return result.orElseThrow(() -> new ObjectNotFoundException("Product não encontrado! ID: "+ id)); + } + + public List findByHospitalId(String hospitalId) { + Hospital hospital = hospitalService.findById(hospitalId); + List products = hospital.getProducts(); + return convertToDTOs(products); + } + + public List findByName(String name) { + List products = productRepository.findByNameLikeIgnoreCase(name); + return convertToDTOs(products); + } + + public ProductDTO insert(String hospitalId, ProductDTO productDTO) { + Product product = fromDTO(productDTO); + product = productRepository.save(product); + Hospital hospital = hospitalService.findById(hospitalId); + hospital.setProduct(product); + + //añadido para que guarde los producto del hospital + hospitalService.save(hospital); + + return convertToDTO(product); + } + + public void delete(String hospitalId, String productId) { + Product removeProduct = findProductById(productId); + Hospital hospital = hospitalService.findById(hospitalId); + hospital.getProducts().remove(removeProduct); + + productRepository.deleteById(productId); + } + + public ProductDTO update(String hospitalId, ProductDTO product) { + Product updateProduct = findProductById(product.getId()); + updateProduct.setName(product.getName()); + updateProduct.setDescription(product.getDescription()); + updateProduct.setQuantity(product.getQuantity()); + updateProduct.setProductType(product.getProductType()); + return convertToDTO(productRepository.save(updateProduct)); + } + + public Product fromDTO(ProductDTO productDTO) { + //añade el id de hospital + return new Product(productDTO.getId(), productDTO.getProductName(), productDTO.getDescription(), productDTO.getQuantity(), productDTO.getProductType(), + productDTO.getHospitalDTOId()); + } + + private ProductDTO convertToDTO(Product model) { + ProductDTO dto = new ProductDTO(); + dto.setId(model.getId()); + dto.setName(model.getName()); + dto.setDescription(model.getDescription()); + dto.setQuantity(model.getQuantity()); + dto.setProductType(model.getProductType()); + + // añade el id de hospital + dto.setHospitalId(model.getHospitalId()); + + return dto; + } + + private List convertToDTOs(List models) { + return models.stream() + .map(this::convertToDTO) + .collect(Collectors.toList()); + } +} \ No newline at end of file diff --git a/sut-gestaoHospital/src/main/java/br/com/codenation/hospital/services/TestService.java b/sut-gestaoHospital/src/main/java/br/com/codenation/hospital/services/TestService.java new file mode 100644 index 0000000..551092f --- /dev/null +++ b/sut-gestaoHospital/src/main/java/br/com/codenation/hospital/services/TestService.java @@ -0,0 +1,56 @@ +package br.com.codenation.hospital.services; + +import java.util.ArrayList; +import java.util.List; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import br.com.codenation.hospital.domain.Hospital; +import br.com.codenation.hospital.domain.Location; +import br.com.codenation.hospital.domain.Patient; +import br.com.codenation.hospital.domain.Product; +import br.com.codenation.hospital.repository.HospitalRepository; +import br.com.codenation.hospital.repository.LocationRepository; +import br.com.codenation.hospital.repository.PatientRepository; +import br.com.codenation.hospital.repository.ProductRepository; + +//Added a service for test: clean the database (deleteAll) and get all data (getAll) + +@Service +public class TestService { + + public class AllData { + public List hospital=new ArrayList<>(); + public List patient=new ArrayList<>(); + public List product=new ArrayList<>(); + public List location=new ArrayList<>(); + } + + @Autowired + private HospitalRepository hospitalRepository; + + @Autowired + private PatientRepository patientRepository; + + @Autowired + private ProductRepository productRepository; + + @Autowired + private LocationRepository locationRepository; + + public void deleteAll() { + hospitalRepository.deleteAll(); + patientRepository.deleteAll(); + productRepository.deleteAll(); + locationRepository.deleteAll(); + } + public Object getAll() { + AllData data = new AllData(); + data.hospital= hospitalRepository.findAll(); + data.patient = patientRepository.findAll(); + data.product = productRepository.findAll(); + data.location= locationRepository.findAll(); + return data; + } +} diff --git a/sut-gestaoHospital/src/main/java/br/com/codenation/hospital/services/exception/ObjectNotFoundException.java b/sut-gestaoHospital/src/main/java/br/com/codenation/hospital/services/exception/ObjectNotFoundException.java new file mode 100644 index 0000000..b7097f2 --- /dev/null +++ b/sut-gestaoHospital/src/main/java/br/com/codenation/hospital/services/exception/ObjectNotFoundException.java @@ -0,0 +1,14 @@ +package br.com.codenation.hospital.services.exception; + +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.ResponseStatus; + +@ResponseStatus(value = HttpStatus.BAD_REQUEST) +public class ObjectNotFoundException extends RuntimeException{ + //auxilar para tratar exceção + private static final long serialVersionUID = -7915253746748523406L; + + public ObjectNotFoundException(String msg) { + super(msg); + } +} \ No newline at end of file diff --git a/sut-gestaoHospital/src/main/resources/GestaoHospitalar.postman_collection.json b/sut-gestaoHospital/src/main/resources/GestaoHospitalar.postman_collection.json new file mode 100644 index 0000000..3a9a757 --- /dev/null +++ b/sut-gestaoHospital/src/main/resources/GestaoHospitalar.postman_collection.json @@ -0,0 +1,368 @@ +{ + "info": { + "_postman_id": "d5fd816c-9b6c-4c90-8a0e-ebbae03513af", + "name": "GestaoHospitalar", + "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json" + }, + "item": [ + { + "name": "Hospitais - Insere um hospital", + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "name": "Content-Type", + "value": "application/json", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\n\t\"id\": 4,\n \"name\": \"HOSPITAL INFANTIL DARCY VARGAS\",\n \"address\": \"R. Dr. Seráfico de Assis Carvalho, 38-66 - Jardim Leonor, São Paulo - SP, 05614-040\",\n \"beds\": 15,\n \"availableBeds\": 1,\n \"latitude\": \"0\",\n \"longitude\": \"0\"\n}" + }, + "url": { + "raw": "http://localhost:8080/v1/hospitais/", + "protocol": "http", + "host": [ + "localhost" + ], + "port": "8080", + "path": [ + "v1", + "hospitais", + "" + ] + } + }, + "response": [] + }, + { + "name": "Hospitais - Altera um hospital", + "request": { + "method": "PUT", + "header": [ + { + "key": "Content-Type", + "name": "Content-Type", + "type": "text", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"name\": \"HOSPITAL INFANTIL DARCY VARGAS\",\n \"address\": \"R. Dr. Seráfico de Assis Carvalho, 38-66, Jardim Leonor, São Paulo - SP, 05614-040\",\n \"beds\": 15,\n \"availableBeds\": 1,\n \"latitude\": \"-23.593438\",\n \"longitude\": \"-46.710462\"\n}" + }, + "url": { + "raw": "http://localhost:8080/v1/hospitais/4", + "protocol": "http", + "host": [ + "localhost" + ], + "port": "8080", + "path": [ + "v1", + "hospitais", + "4" + ] + } + }, + "response": [] + }, + { + "name": "Hospitais - Retorna um hospital", + "request": { + "method": "GET", + "header": [], + "body": { + "mode": "raw", + "raw": "" + }, + "url": { + "raw": "http://localhost:8080/v1/hospitais/4", + "protocol": "http", + "host": [ + "localhost" + ], + "port": "8080", + "path": [ + "v1", + "hospitais", + "4" + ] + } + }, + "response": [] + }, + { + "name": "Hospitais - Retorna uma lista com todos os hospitais", + "request": { + "method": "GET", + "header": [], + "body": { + "mode": "raw", + "raw": "" + }, + "url": { + "raw": "http://localhost:8080/v1/hospitais/", + "protocol": "http", + "host": [ + "localhost" + ], + "port": "8080", + "path": [ + "v1", + "hospitais", + "" + ] + } + }, + "response": [] + }, + { + "name": "Estoque - Retorna uma lista com todos os produtos do estoque do hospital", + "request": { + "method": "GET", + "header": [], + "body": { + "mode": "raw", + "raw": "" + }, + "url": { + "raw": "http://localhost:8080/v1/hospitais/1/estoque", + "protocol": "http", + "host": [ + "localhost" + ], + "port": "8080", + "path": [ + "v1", + "hospitais", + "1", + "estoque" + ] + } + }, + "response": [] + }, + { + "name": "Estoque - Insere um produto no estoque do hospital", + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "name": "Content-Type", + "value": "application/json", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"description\": \"Curativos\",\n \"quantity\": 50,\n \"productType\": \"COMMON\",\n \"name\": \"Curativos\"\n}" + }, + "url": { + "raw": "http://localhost:8080/v1/hospitais/1/estoque", + "protocol": "http", + "host": [ + "localhost" + ], + "port": "8080", + "path": [ + "v1", + "hospitais", + "1", + "estoque" + ] + } + }, + "response": [] + }, + { + "name": "Estoque - Altera um produto no estoque do hospital", + "request": { + "method": "PUT", + "header": [ + { + "key": "Content-Type", + "name": "Content-Type", + "value": "application/json", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"description\": \"Curativos 2\",\n \"quantity\": 50,\n \"productType\": \"COMMON\",\n \"name\": \"Curativos 2\"\n}" + }, + "url": { + "raw": "http://localhost:8080/v1/hospitais/1/estoque/5cac077fa9c6543dc892fdf9", + "protocol": "http", + "host": [ + "localhost" + ], + "port": "8080", + "path": [ + "v1", + "hospitais", + "1", + "estoque", + "5cac077fa9c6543dc892fdf9" + ] + } + }, + "response": [] + }, + { + "name": "Estoque - Remove um produto do estoque do hospital", + "request": { + "method": "DELETE", + "header": [ + { + "key": "Content-Type", + "name": "Content-Type", + "type": "text", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "" + }, + "url": { + "raw": "http://localhost:8080/v1/hospitais/1/estoque/5cac07a5a9c6543dc892fdf5", + "protocol": "http", + "host": [ + "localhost" + ], + "port": "8080", + "path": [ + "v1", + "hospitais", + "1", + "estoque", + "5cac07a5a9c6543dc892fdf5" + ] + } + }, + "response": [] + }, + { + "name": "Estoque - Retorna um produto do estoque do hospital", + "request": { + "method": "GET", + "header": [], + "body": { + "mode": "raw", + "raw": "" + }, + "url": { + "raw": "http://localhost:8080/v1/hospitais/1/estoque/5cac074ea9c6543dc892fdf1", + "protocol": "http", + "host": [ + "localhost" + ], + "port": "8080", + "path": [ + "v1", + "hospitais", + "1", + "estoque", + "5cac074ea9c6543dc892fdf1" + ] + } + }, + "response": [] + }, + { + "name": "Localizacao - Retorna dados sobre um endereço", + "request": { + "method": "GET", + "header": [], + "body": { + "mode": "raw", + "raw": "" + }, + "url": { + "raw": "https://us1.locationiq.com/v1/search.php?key=43b382813d8baa&q=Avenida%20Paulista%20Sao%20Paulo&format=json", + "protocol": "https", + "host": [ + "us1", + "locationiq", + "com" + ], + "path": [ + "v1", + "search.php" + ], + "query": [ + { + "key": "key", + "value": "43b382813d8baa" + }, + { + "key": "q", + "value": "Avenida%20Paulista%20Sao%20Paulo" + }, + { + "key": "format", + "value": "json" + } + ] + } + }, + "response": [] + }, + { + "name": "Localizacao - Retorna uma lista de endereços perto de um hospital", + "request": { + "method": "GET", + "header": [], + "body": { + "mode": "raw", + "raw": "" + }, + "url": { + "raw": "http://localhost:8080/v1/hospitais/3/proximidades", + "protocol": "http", + "host": [ + "localhost" + ], + "port": "8080", + "path": [ + "v1", + "hospitais", + "3", + "proximidades" + ] + } + }, + "response": [] + }, + { + "name": "Localizacao - Retorna uma lista de hospitais perto de outro hospital", + "request": { + "method": "GET", + "header": [], + "body": { + "mode": "raw", + "raw": "" + }, + "url": { + "raw": "http://localhost:8080/v1/hospitais/3/hospitaisproximos", + "protocol": "http", + "host": [ + "localhost" + ], + "port": "8080", + "path": [ + "v1", + "hospitais", + "3", + "hospitaisproximos" + ] + } + }, + "response": [] + } + ] +} \ No newline at end of file diff --git a/sut-gestaoHospital/src/main/resources/application.properties b/sut-gestaoHospital/src/main/resources/application.properties new file mode 100644 index 0000000..b84fb13 --- /dev/null +++ b/sut-gestaoHospital/src/main/resources/application.properties @@ -0,0 +1 @@ +spring.data.mongodb.uri=mongodb://mongodb:27017/HospitalDB \ No newline at end of file diff --git a/sut-gestaoHospital/src/main/resources/gestaohospital-rest.json b/sut-gestaoHospital/src/main/resources/gestaohospital-rest.json new file mode 100644 index 0000000..5e558eb --- /dev/null +++ b/sut-gestaoHospital/src/main/resources/gestaohospital-rest.json @@ -0,0 +1,1227 @@ +{ + "swagger": "2.0", + "info": { + "description": "Documentação da API de acesso aos endpoints da GestaoHospitalAPI - Aceleradev Brasil\r\nJornada de desafios da Aceleradev Brasil - CodeNation", + "version": "1.0", + "title": "Sistema de Gestão Hospital API" + }, + "host": "localhost:8080", + "basePath": "/", + "tags": [ + { + "name": "location-resource", + "description": "Location Resource" + }, + { + "name": "product-resource", + "description": "Product Resource" + }, + { + "name": "patient-resource", + "description": "Patient Resource" + }, + { + "name": "hospital-resource", + "description": "Hospital Resource" + } + ], + "paths": { + "/v1/hospitais/": { + "get": { + "tags": [ + "hospital-resource" + ], + "summary": "findAll", + "operationId": "findAllUsingGET", + "consumes": [ + "application/json" + ], + "produces": [ + "*/*" + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/HospitalDTO" + } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + }, + "404": { + "description": "Not Found" + } + } + }, + "post": { + "tags": [ + "hospital-resource" + ], + "summary": "insert", + "operationId": "insertUsingPOST", + "consumes": [ + "application/json" + ], + "produces": [ + "*/*" + ], + "parameters": [ + { + "in": "body", + "name": "objDTO", + "description": "objDTO", + "required": true, + "schema": { + "$ref": "#/definitions/HospitalDTO" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/HospitalDTO" + } + }, + "201": { + "description": "Created" + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + }, + "404": { + "description": "Not Found" + } + } + } + }, + "/v1/hospitais/estoque": { + "post": { + "tags": [ + "product-resource" + ], + "summary": "insertProductByHospital", + "operationId": "insertUsingPOST_1", + "consumes": [ + "application/json" + ], + "produces": [ + "*/*" + ], + "parameters": [ + { + "in": "body", + "name": "productDTO", + "description": "productDTO", + "required": true, + "schema": { + "$ref": "#/definitions/ProductDTO" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/ProductDTO" + } + }, + "201": { + "description": "Created" + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + }, + "404": { + "description": "Not Found" + } + } + } + }, + + "/v1/hospitais/maisProximo": { + "get": { + "tags": [ + "hospital-resource" + ], + "summary": "hospitalMaisProximo", + "operationId": "hospitalMaisProximoUsingGET", + "consumes": [ + "application/json" + ], + "produces": [ + "*/*" + ], + "parameters": [ + { + "name": "lat", + "in": "query", + "description": "lat", + "required": true, + "type": "number", + "format": "double" + }, + { + "name": "lon", + "in": "query", + "description": "lon", + "required": true, + "type": "number", + "format": "double" + }, + { + "name": "raioMaximo", + "in": "query", + "description": "raioMaximo", + "required": true, + "type": "number", + "format": "double" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/HospitalDTO" + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + }, + "404": { + "description": "Not Found" + } + } + } + }, + "/v1/hospitais/{hospital_id}": { + "get": { + "tags": [ + "hospital-resource" + ], + "summary": "findById", + "operationId": "findByIdUsingGET", + "consumes": [ + "application/json" + ], + "produces": [ + "*/*" + ], + "parameters": [ + { + "name": "hospital_id", + "in": "path", + "description": "hospital_id", + "required": true, + "type": "string" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/HospitalDTO" + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + }, + "404": { + "description": "Not Found" + } + } + }, + "put": { + "tags": [ + "hospital-resource" + ], + "summary": "update", + "operationId": "updateUsingPUT", + "consumes": [ + "application/json" + ], + "produces": [ + "*/*" + ], + "parameters": [ + { + "in": "body", + "name": "objDTO", + "description": "objDTO", + "required": true, + "schema": { + "$ref": "#/definitions/HospitalDTO" + } + }, + { + "name": "hospital_id", + "in": "path", + "description": "hospital_id", + "required": true, + "type": "string" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/HospitalDTO" + } + }, + "201": { + "description": "Created" + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + }, + "404": { + "description": "Not Found" + } + } + }, + "delete": { + "tags": [ + "hospital-resource" + ], + "summary": "deleteById", + "operationId": "deleteByIdUsingDELETE", + "consumes": [ + "application/json" + ], + "produces": [ + "*/*" + ], + "parameters": [ + { + "name": "hospital_id", + "in": "path", + "description": "hospital_id", + "required": true, + "type": "string" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "string" + } + }, + "401": { + "description": "Unauthorized" + }, + "204": { + "description": "No Content" + }, + "403": { + "description": "Forbidden" + } + } + } + }, + "/v1/hospitais/{hospital_id}/estoque": { + "get": { + "tags": [ + "product-resource" + ], + "summary": "findAllProductBy", + "operationId": "findAllProductByUsingGET", + "consumes": [ + "application/json" + ], + "produces": [ + "*/*" + ], + "parameters": [ + { + "name": "hospital_id", + "in": "path", + "description": "hospital_id", + "required": true, + "type": "string" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/ProductDTO" + } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + }, + "404": { + "description": "Not Found" + } + } + }, + "post": { + "tags": [ + "product-resource" + ], + "summary": "insert", + "operationId": "insertUsingPOST_1", + "consumes": [ + "application/json" + ], + "produces": [ + "*/*" + ], + "parameters": [ + { + "name": "hospital_id", + "in": "path", + "description": "hospital_id", + "required": true, + "type": "string" + }, + { + "in": "body", + "name": "productDTO", + "description": "productDTO", + "required": true, + "schema": { + "$ref": "#/definitions/ProductDTO" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/ProductDTO" + } + }, + "201": { + "description": "Created" + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + }, + "404": { + "description": "Not Found" + } + } + } + }, + "/v1/hospitais/{hospital_id}/estoque/{produto_id}": { + "get": { + "tags": [ + "product-resource" + ], + "summary": "findProductBy", + "operationId": "findProductByUsingGET", + "consumes": [ + "application/json" + ], + "produces": [ + "*/*" + ], + "parameters": [ + { + "name": "hospital_id", + "in": "path", + "description": "hospital_id", + "required": true, + "type": "string" + }, + { + "name": "produto_id", + "in": "path", + "description": "produto_id", + "required": true, + "type": "string" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/ProductDTO" + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + }, + "404": { + "description": "Not Found" + } + } + }, + "put": { + "tags": [ + "product-resource" + ], + "summary": "update", + "operationId": "updateUsingPUT_1", + "consumes": [ + "application/json" + ], + "produces": [ + "*/*" + ], + "parameters": [ + { + "in": "body", + "name": "productDTO", + "description": "productDTO", + "required": true, + "schema": { + "$ref": "#/definitions/ProductDTO" + } + }, + { + "name": "hospital_id", + "in": "path", + "description": "hospital_id", + "required": true, + "type": "string" + }, + { + "name": "produto_id", + "in": "path", + "description": "produto_id", + "required": true, + "type": "string" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/ProductDTO" + } + }, + "201": { + "description": "Created" + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + }, + "404": { + "description": "Not Found" + } + } + }, + "delete": { + "tags": [ + "product-resource" + ], + "summary": "delete", + "operationId": "deleteUsingDELETE", + "consumes": [ + "application/json" + ], + "produces": [ + "*/*" + ], + "parameters": [ + { + "name": "hospital_id", + "in": "path", + "description": "hospital_id", + "required": true, + "type": "string" + }, + { + "name": "produto_id", + "in": "path", + "description": "produto_id", + "required": true, + "type": "string" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "string" + } + }, + "401": { + "description": "Unauthorized" + }, + "204": { + "description": "No Content" + }, + "403": { + "description": "Forbidden" + } + } + } + }, + "/v1/hospitais/{hospital_id}/hospitaisProximos": { + "get": { + "tags": [ + "location-resource" + ], + "summary": "findHospitalNearHospitalBy", + "operationId": "findHospitalNearHospitalByUsingGET", + "consumes": [ + "application/json" + ], + "produces": [ + "*/*" + ], + "parameters": [ + { + "name": "hospital_id", + "in": "path", + "description": "hospital_id", + "required": true, + "type": "string" + }, + { + "name": "raio", + "in": "query", + "description": "raio", + "required": true, + "type": "number", + "format": "double" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/HospitalDTO" + } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + }, + "404": { + "description": "Not Found" + } + } + } + }, + "/v1/hospitais/{hospital_id}/pacientes": { + "get": { + "tags": [ + "patient-resource" + ], + "summary": "findPatients", + "operationId": "findPatientsUsingGET", + "consumes": [ + "application/json" + ], + "produces": [ + "*/*" + ], + "parameters": [ + { + "name": "hospital_id", + "in": "path", + "description": "hospital_id", + "required": true, + "type": "string" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/Patient" + } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + }, + "404": { + "description": "Not Found" + } + } + } + }, + "/v1/hospitais/{hospital_id}/pacientes/checkin": { + "post": { + "tags": [ + "patient-resource" + ], + "summary": "checkinPacient", + "operationId": "checkinPacientUsingPOST", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "parameters": [ + { + "name": "hospital_id", + "in": "path", + "description": "hospital_id", + "required": true, + "type": "string" + }, + { + "in": "body", + "name": "patient", + "description": "patient", + "required": true, + "schema": { + "$ref": "#/definitions/Patient" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/Patient" + } + }, + "201": { + "description": "Created" + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + }, + "404": { + "description": "Not Found" + } + } + } + }, + "/v1/hospitais/{hospital_id}/pacientes/checkout": { + "post": { + "tags": [ + "patient-resource" + ], + "summary": "checkoutPacient", + "operationId": "checkoutPacientUsingPOST", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "parameters": [ + { + "name": "hospital_id", + "in": "path", + "description": "hospital_id", + "required": true, + "type": "string" + }, + { + "in": "body", + "name": "idPatient", + "description": "idPatient", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/Patient" + } + }, + "201": { + "description": "Created" + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + }, + "404": { + "description": "Not Found" + } + } + } + }, + "/v1/hospitais/{hospital_id}/pacientes/{patientId}": { + "get": { + "tags": [ + "patient-resource" + ], + "summary": "findPatientById", + "operationId": "findPatientByIdUsingGET", + "consumes": [ + "application/json" + ], + "produces": [ + "*/*" + ], + "parameters": [ + { + "name": "hospital_id", + "in": "path", + "description": "hospital_id", + "required": true, + "type": "string" + }, + { + "name": "patientId", + "in": "path", + "description": "patientId", + "required": true, + "type": "string" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/Patient" + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + }, + "404": { + "description": "Not Found" + } + } + }, + "put": { + "tags": [ + "patient-resource" + ], + "summary": "updatePatient", + "operationId": "updatePatientUsingPUT", + "consumes": [ + "application/json" + ], + "produces": [ + "*/*" + ], + "parameters": [ + { + "name": "hospital_id", + "in": "path", + "description": "hospital_id", + "required": true, + "type": "string" + }, + { + "name": "patientId", + "in": "path", + "description": "patientId", + "required": true, + "type": "string" + }, + { + "in": "body", + "name": "patient", + "description": "patient", + "required": true, + "schema": { + "$ref": "#/definitions/Patient" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/Patient" + } + }, + "201": { + "description": "Created" + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + }, + "404": { + "description": "Not Found" + } + } + } + }, + "/v1/hospitais/{hospital_id}/proximidades": { + "get": { + "tags": [ + "location-resource" + ], + "summary": "findLocationNearHospitalBy", + "operationId": "findLocationNearHospitalByUsingGET", + "consumes": [ + "application/json" + ], + "produces": [ + "*/*" + ], + "parameters": [ + { + "name": "hospital_id", + "in": "path", + "description": "hospital_id", + "required": true, + "type": "string" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/LocationDTO" + } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + }, + "404": { + "description": "Not Found" + } + } + } + }, + "/v1/hospitais/{id}/leitos": { + "get": { + "tags": [ + "hospital-resource" + ], + "summary": "verificaLeitosDisponiveis", + "operationId": "verificaLeitosDisponiveisUsingGET", + "consumes": [ + "application/json" + ], + "produces": [ + "*/*" + ], + "parameters": [ + { + "name": "id", + "in": "path", + "description": "id", + "required": true, + "type": "string" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "object", + "additionalProperties": { + "type": "integer", + "format": "int32" + } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + }, + "404": { + "description": "Not Found" + } + } + } + }, + "/v1/hospitais/{id}/transferencia/{productId}": { + "post": { + "tags": [ + "hospital-resource" + ], + "summary": "transferenciaProduto", + "operationId": "transferenciaProdutoUsingPOST", + "consumes": [ + "application/json" + ], + "produces": [ + "*/*" + ], + "parameters": [ + { + "name": "id", + "in": "path", + "description": "id", + "required": true, + "type": "string" + }, + { + "name": "productId", + "in": "path", + "description": "productId", + "required": true, + "type": "string" + }, + { + "in": "body", + "name": "quantidade", + "description": "quantidade", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "string" + } + }, + "201": { + "description": "Created" + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + }, + "404": { + "description": "Not Found" + } + } + } + } + }, + + "definitions": { + "HospitalDTO": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "address": { + "type": "string" + }, + "availableBeds": { + "type": "integer", + "format": "int32" + }, + "beds": { + "type": "integer", + "format": "int32" + }, + "latitude": { + "type": "string" + }, + "longitude": { + "type": "string" + }, + "name": { + "type": "string" + } + } + }, + "Patient": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "hospitalDTOId": { + "type": "string" + }, + "active": { + "type": "boolean" + }, + "birthDate": { + "type": "string", + "format": "date-time" + }, + "cpf": { + "type": "string" + }, + "entryDate": { + "type": "string", + "format": "date-time" + }, + "exitDate": { + "type": "string", + "format": "date-time" + }, + "gender": { + "type": "string" + }, + "location": { + "$ref": "#/definitions/Location" + }, + "name": { + "type": "string" + } + } + }, + + "GeoJsonPoint": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "coordinates": { + "type": "array", + "items": { + "type": "number", + "format": "double" + } + }, + "type": { + "type": "string" + }, + "x": { + "type": "number", + "format": "double" + }, + "y": { + "type": "number", + "format": "double" + } + } + }, + "LocationDTO": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "category": { + "type": "string" + }, + "latitude": { + "type": "string" + }, + "longitude": { + "type": "string" + }, + "name": { + "type": "string" + }, + "referenceId_X": { + "type": "string" + } + } + }, + "ProductDTO": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "hospitalDTOId": { + "type": "string" + }, + "description": { + "type": "string" + }, + "name": { + "type": "string" + }, + "productName": { + "type": "string" + }, + "productType": { + "type": "string", + "enum": [ + "COMMON", + "BLOOD" + ] + }, + "quantity": { + "type": "integer", + "format": "int32" + } + } + }, + "Location": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "location": { + "$ref": "#/definitions/GeoJsonPoint" + }, + "locationCategory": { + "type": "string", + "enum": [ + "HOSPITAL", + "PATIENT" + ] + }, + "name": { + "type": "string" + }, + "position": { + "$ref": "#/definitions/GeoJsonPoint" + }, + "referenceId_X": { + "type": "string" + } + } + } + } +} \ No newline at end of file diff --git a/sut-gestaoHospital/src/test/java/br/com/codenation/hospital/GestaohospitalarApplicationTests.java b/sut-gestaoHospital/src/test/java/br/com/codenation/hospital/GestaohospitalarApplicationTests.java new file mode 100644 index 0000000..82d4203 --- /dev/null +++ b/sut-gestaoHospital/src/test/java/br/com/codenation/hospital/GestaohospitalarApplicationTests.java @@ -0,0 +1,16 @@ +package br.com.codenation.hospital; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.junit4.SpringRunner; + +@RunWith(SpringRunner.class) +@SpringBootTest +public class GestaohospitalarApplicationTests { + + @Test + public void contextLoads() { + } + +} diff --git a/sut-gestaoHospital/src/test/java/br/com/codenation/hospital/HospitalResourceTest.java b/sut-gestaoHospital/src/test/java/br/com/codenation/hospital/HospitalResourceTest.java new file mode 100644 index 0000000..4c02702 --- /dev/null +++ b/sut-gestaoHospital/src/test/java/br/com/codenation/hospital/HospitalResourceTest.java @@ -0,0 +1,155 @@ +package br.com.codenation.hospital; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import br.com.codenation.hospital.domain.Hospital; +import br.com.codenation.hospital.domain.Product; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.context.SpringBootTest.WebEnvironment; +import org.springframework.boot.test.web.client.TestRestTemplate; +import org.springframework.core.ParameterizedTypeReference; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.test.context.junit4.SpringRunner; + +import br.com.codenation.hospital.constant.Constant; +import br.com.codenation.hospital.dto.HospitalDTO; + +@RunWith(SpringRunner.class) +@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT) +@EnableAutoConfiguration +public class HospitalResourceTest { + @Autowired + private TestRestTemplate restTemplate; + + private final HttpHeaders httpHeaders; + + private ResponseEntity response; + + @Mock + private Hospital hospitalMock; + + @Mock + private Product productMock; + + public HospitalResourceTest() { + httpHeaders = new HttpHeaders(); + httpHeaders.setContentType(MediaType.APPLICATION_JSON_UTF8); + } + + @Before + public void setUp() { + String hospitalJson = "{\"name\": \"Hospital Um\", \"address\": \"Rua dos Sonhos, 1213\", \"beds\": \"21\", \"availableBeds\": \"5\"}"; + response = restTemplate.exchange(Constant.V1, HttpMethod.POST, new HttpEntity<>(hospitalJson, httpHeaders), + HospitalDTO.class); + } + + @Test + public void deveSalvarHospital() { + String hospitalJson = "{\"name\": \"Hospital Novo\", \"address\": \"Rua dos Novos, 0001\", \"beds\": \"10\", \"availableBeds\": \"9\"}"; + ResponseEntity salvarResponse = restTemplate.exchange(Constant.V1, HttpMethod.POST, + new HttpEntity<>(hospitalJson, httpHeaders), HospitalDTO.class); + + assertThat(salvarResponse.getStatusCode()).isEqualTo(HttpStatus.OK); + } + + @Test + public void deveAtualizarHospital() { + String hospitalJson = "{\"name\": \"Hospital Novo\", \"address\": \"Rua dos Novos, 1000\", \"beds\": \"10\", \"availableBeds\": \"10\"}"; + Map param = new HashMap<>(); + ResponseEntity atualizarResponse = restTemplate.exchange(Constant.V1 + response.getBody().getId(), + HttpMethod.PUT, new HttpEntity<>(hospitalJson, httpHeaders), Void.class); + + assertThat(atualizarResponse.getStatusCode()).isEqualTo(HttpStatus.OK); + } + + @Test + public void deveRemoverHospital() { + ResponseEntity removerResponse = restTemplate.exchange(Constant.V1 + response.getBody().getId(), + HttpMethod.DELETE, null, Void.class); + assertThat(removerResponse.getStatusCode()).isEqualTo(HttpStatus.OK); + } + + @Test + public void deveListarHospitalPeloId() { + ResponseEntity getResponse = restTemplate.exchange(Constant.V1 + response.getBody().getId(), + HttpMethod.GET, null, HospitalDTO.class); + assertThat(getResponse.getStatusCode()).isEqualTo(HttpStatus.OK); + } + + @Test + public void deveListarTodosHospitais() { + ResponseEntity> response = restTemplate.exchange(Constant.V1, HttpMethod.GET, null, + new ParameterizedTypeReference>() { + }); + assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK); + } + + @Test + public void naoDeveListarHospital() { + ResponseEntity getResponse = restTemplate.exchange(Constant.V1 + "0", HttpMethod.GET, null, + HospitalDTO.class); + assertThat(getResponse.getStatusCode()).isEqualTo(HttpStatus.NOT_FOUND); + } + + @Test + public void deveRetornarLeitosDisponiveis() { + @SuppressWarnings("rawtypes") + ResponseEntity getResponse = restTemplate.exchange(Constant.V1 + "1/leitos", HttpMethod.GET, null, + Map.class); + assertThat(getResponse.getStatusCode()).isEqualTo(HttpStatus.OK); + assertThat(getResponse.getBody().get("leitos")).isEqualTo(5); + } + + @Test + public void naoDeveRetornarLeitosDisponiveis() { + @SuppressWarnings("rawtypes") + ResponseEntity getResponse = restTemplate.exchange(Constant.V1 + "0/leitos", HttpMethod.GET, null, + Map.class); + assertThat(getResponse.getStatusCode()).isEqualTo(HttpStatus.BAD_REQUEST); + } + + @Test + public void naoDeveFazerTransferenciaDoHospitalMaisProximo() { + ResponseEntity getResponse = restTemplate + .exchange(Constant.V1 + hospitalMock.getId() + "/transferencia/" + productMock.getId(), + HttpMethod.POST, + new HttpEntity<>("{\"quantity\": 5}", httpHeaders), + HospitalDTO.class); + assertThat(getResponse.getStatusCode()).isEqualTo(HttpStatus.BAD_REQUEST); + } + +// @Test +// public void deveFazerTransferenciaDoHospitalMaisProximo() { +// ResponseEntity getResponse = restTemplate +// .exchange(Constant.V1 + "1/transferencia/5cac04a481b2d504d0ed2a5a", +// HttpMethod.POST, +// new HttpEntity<>("{\"quantity\": 5}", httpHeaders), +// HospitalDTO.class); +// assertThat(getResponse.getStatusCode()).isEqualTo(HttpStatus.OK); +// } + + @Test + public void deveRetornarHospitalMaisProximoComLeitosDisponiveis() { + ResponseEntity getResponse = restTemplate + .exchange(Constant.V1 + "/maisProximo?lat=50&lon=50&raioMaximo=50000", + HttpMethod.GET, + null, + HospitalDTO.class); + assertThat(getResponse.getStatusCode()).isEqualTo(HttpStatus.OK); + } +} \ No newline at end of file diff --git a/sut-gestaoHospital/src/test/java/br/com/codenation/hospital/LocationIQServiceTest.java b/sut-gestaoHospital/src/test/java/br/com/codenation/hospital/LocationIQServiceTest.java new file mode 100644 index 0000000..b610171 --- /dev/null +++ b/sut-gestaoHospital/src/test/java/br/com/codenation/hospital/LocationIQServiceTest.java @@ -0,0 +1,72 @@ +package br.com.codenation.hospital; + +import static org.junit.Assert.assertEquals; + +import java.io.IOException; +import java.util.List; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.context.SpringBootTest.WebEnvironment; +import org.springframework.boot.test.web.client.TestRestTemplate; +import org.springframework.data.mongodb.core.index.GeospatialIndex; +import org.springframework.http.HttpHeaders; +import org.springframework.http.MediaType; +import org.springframework.test.context.junit4.SpringRunner; + +import com.fasterxml.jackson.databind.ObjectMapper; + +import br.com.codenation.hospital.domain.Location; +import br.com.codenation.hospital.integration.LocationIQResponse; +import br.com.codenation.hospital.integration.LocationIQService; + +@RunWith(SpringRunner.class) +@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT) +@EnableAutoConfiguration +public class LocationIQServiceTest { + + @Autowired + private LocationIQService locationIQService; + + @Autowired + private TestRestTemplate restTemplate; + + private final HttpHeaders httpHeaders; + private String search; + + public LocationIQServiceTest() { + httpHeaders = new HttpHeaders(); + httpHeaders.setContentType(MediaType.APPLICATION_JSON_UTF8); + } + + @Before + public void setUp() { + search = "Hospital Israelita Albert Einstein"; + } + + @Test + public void deveConverterJsonParaLocationIQResponse() throws IOException { + LocationIQResponse locationIQResponse = new LocationIQResponse(); + String json = "{\"place_id\": \"35288236\", \"licence\": \"https://locationiq.com/attribution\", \"osm_type\": \"node\", \"osm_id\": \"2837177940\",\"boundingbox\": [\"41.3438445\", \"41.3439445\", \"-86.3112295\", \"-86.3111295\"], \"lat\": \"41.3438945\", \"lon\": \"-86.3111795\", \"display_name\": \"Statue of Liberty, North Center Street, Plymouth, Marshall County, Indiana, 46563, Estados Unidos da América\", \"class\": \"tourism\", \"type\": \"artwork\", \"importance\": 0.66954659981371, \"icon\": \"https://locationiq.org/static/images/mapicons/tourist_art_gallery2.p.20.png\"}"; + + locationIQResponse = new ObjectMapper() + .readerFor(LocationIQResponse.class) + .readValue(json); + + assertEquals("35288236", locationIQResponse.getPlaceId()); + } + + @Test + public void deveRetornarLocationIQResponse() throws IOException { + + List locationsResponse = locationIQService.getLocationIQResponse(search); + + if (!locationsResponse.isEmpty()) { + assertEquals("125076245", locationsResponse.get(0).getPlaceId()); + } + } +} diff --git a/sut-gestaoHospital/src/test/java/br/com/codenation/hospital/LocationRepositoryTest.java b/sut-gestaoHospital/src/test/java/br/com/codenation/hospital/LocationRepositoryTest.java new file mode 100644 index 0000000..8c3209c --- /dev/null +++ b/sut-gestaoHospital/src/test/java/br/com/codenation/hospital/LocationRepositoryTest.java @@ -0,0 +1,108 @@ +package br.com.codenation.hospital; + +import java.util.List; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.context.SpringBootTest.WebEnvironment; +import org.springframework.boot.test.web.client.TestRestTemplate; +import org.springframework.data.mongodb.core.MongoTemplate; +import org.springframework.data.mongodb.core.index.GeospatialIndex; +import org.springframework.http.HttpHeaders; +import org.springframework.http.MediaType; +import org.springframework.test.context.junit4.SpringRunner; + +import br.com.codenation.hospital.domain.Hospital; +import br.com.codenation.hospital.domain.Location; +import br.com.codenation.hospital.domain.Product; +import br.com.codenation.hospital.repository.LocationRepository; +import br.com.codenation.hospital.services.HospitalService; +import br.com.codenation.hospital.services.LocationService; + + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.geo.Box; +import org.springframework.data.geo.Circle; +import org.springframework.data.geo.Distance; +import org.springframework.data.geo.Metrics; +import org.springframework.data.geo.Point; +import org.springframework.data.mongodb.core.MongoTemplate; +import org.springframework.data.mongodb.core.index.GeoSpatialIndexType; +import org.springframework.data.mongodb.core.index.GeospatialIndex; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; + +@RunWith(SpringRunner.class) +@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT) +@EnableAutoConfiguration +public class LocationRepositoryTest { + @Autowired + private TestRestTemplate restTemplate; + + @Autowired + private LocationRepository repo; + + @Autowired + MongoTemplate template; + + private final HttpHeaders httpHeaders; + private Location locationTest; + + public LocationRepositoryTest() { + httpHeaders = new HttpHeaders(); + httpHeaders.setContentType(MediaType.APPLICATION_JSON_UTF8); + } + + @Before + public void setUp() { + // ensure geospatial index + template.indexOps(Location.class).ensureIndex( new GeospatialIndex("position") ); + // prepare data + repo.save( new Location("A", 0.001, -0.002) ); + repo.save( new Location("B", 1, 1) ); + repo.save( new Location("C", 0.5, 0.5) ); + repo.save( new Location("D", -0.5, -0.5) ); + } + + @Test public void shouldFindAroundOrigin() { + // when + //List locations = repo.findByPositionWithin( new Circle(0,0, 0.75) ); + + // then + // assertLocations( locations, "A", "C", "D" ); + } + + @Test public void shouldFindWithinBox() { + // when + //List locations = repo.findByPositionWithin( new Box( new Point(0.25, 0.25), new Point(1,1)) ); + + // then + //assertLocations( locations, "B", "C" ); + } + + + private static void assertLocations(List locations, String... ids) { + //assertThat( locations, notNullValue() ); + out("-----------------------------"); + for (Location l : locations) { + out(l); + } + /*assertThat("Mismatch location count", ids.length, is(locations.size())); + for (String id : ids) { + assertThat("Location " + id + " not found", + locations.contains(new Location(id, 0, 0)), is(true)); + }*/ + } + + private static void out(Object o) { + System.out.println(o); + } +} diff --git a/sut-gestaoHospital/src/test/java/br/com/codenation/hospital/PatientResourceTest.java b/sut-gestaoHospital/src/test/java/br/com/codenation/hospital/PatientResourceTest.java new file mode 100644 index 0000000..9d6d4d0 --- /dev/null +++ b/sut-gestaoHospital/src/test/java/br/com/codenation/hospital/PatientResourceTest.java @@ -0,0 +1,124 @@ +package br.com.codenation.hospital; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.Date; +import java.util.List; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.context.SpringBootTest.WebEnvironment; +import org.springframework.boot.test.web.client.TestRestTemplate; +import org.springframework.core.ParameterizedTypeReference; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.test.context.junit4.SpringRunner; + +import br.com.codenation.hospital.constant.Constant; +import br.com.codenation.hospital.domain.Hospital; +import br.com.codenation.hospital.domain.Patient; +import br.com.codenation.hospital.services.HospitalService; + +@RunWith(SpringRunner.class) +@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT) +@EnableAutoConfiguration +public class PatientResourceTest { + @Autowired + private TestRestTemplate restTemplate; + + @Autowired + private HospitalService hospitalService; + + private final HttpHeaders httpHeaders; + private Hospital hospitalTest; + private Patient patientTest; + + @Mock + private Hospital hospitalMock; + + @Mock + private Patient pacienteMock; + + public PatientResourceTest() { + httpHeaders = new HttpHeaders(); + httpHeaders.setContentType(MediaType.APPLICATION_JSON_UTF8); + + } + + @Before + public void setUp() { + hospitalTest = hospitalService.findById("1"); + if (hospitalTest != null) { + List patientList = hospitalTest.getPatients(); + if (!patientList.isEmpty()) { + patientTest = patientList.get(0); + } + } + } + + @Test + public void deveListarTodosPacienteDoHospital() { + ResponseEntity> response = restTemplate.exchange( + Constant.V1 + hospitalTest.getId() + "/pacientes", HttpMethod.GET, null, + new ParameterizedTypeReference>() { + }); + assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK); + } + + @Test + public void deveListarPacienteDoHospital() { + ResponseEntity response = restTemplate.exchange( + Constant.V1 + hospitalTest.getId() + "/pacientes/" + patientTest.getId(), HttpMethod.GET, null, + new ParameterizedTypeReference() { + }); + assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK); + assertThat(response.getBody().getId()).isEqualTo(patientTest.getId()); + } + + @Test // not working, only god knows why + public void naoDeveListarPacienteDoHospital() { + ResponseEntity response = restTemplate.exchange( + Constant.V1 + hospitalTest.getId() + "/pacientes/0", HttpMethod.GET, null, + new ParameterizedTypeReference() { + }); + assertThat(response.getStatusCode()).isEqualTo(HttpStatus.NOT_FOUND); + } + + @Test + public void deveFazerCheckinNoHospital() { + Patient paciente = new Patient("nome", "cpf", new Date(), "sexo"); + //int nPacientes = hospitalTest.getPatients().size(); + ResponseEntity response = restTemplate.postForEntity( + Constant.V1 + hospitalTest.getId() + "/pacientes/checkin", paciente, Patient.class, + hospitalTest.getId()); + assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK); + } + + @Test + public void deveFazerCheckoutNoHospital() { + + ResponseEntity response = restTemplate.postForEntity( + Constant.V1 + hospitalTest.getId() + "/pacientes/checkout", patientTest.getId(), Patient.class, + hospitalTest.getId()); + assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK); + } + + @Test // not working, only god knows why + public void deveAtualizarPacienteNoHospital() { + ResponseEntity response = restTemplate.exchange( + Constant.V1 + hospitalTest.getId() + "/pacientes/" + patientTest.getId(), HttpMethod.PUT, + new HttpEntity<>("{\n" + "\t\"name\": \"paciente update\",\n" + "\t\"cpf\": \"cpf update\",\n" + "}", + httpHeaders), + Void.class); + assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK); + } +} \ No newline at end of file diff --git a/sut-gestaoHospital/src/test/java/br/com/codenation/hospital/ProductResourceTest.java b/sut-gestaoHospital/src/test/java/br/com/codenation/hospital/ProductResourceTest.java new file mode 100644 index 0000000..e5619b1 --- /dev/null +++ b/sut-gestaoHospital/src/test/java/br/com/codenation/hospital/ProductResourceTest.java @@ -0,0 +1,111 @@ +package br.com.codenation.hospital; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.List; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.context.SpringBootTest.WebEnvironment; +import org.springframework.boot.test.web.client.TestRestTemplate; +import org.springframework.core.ParameterizedTypeReference; +import org.springframework.http.*; +import org.springframework.test.context.junit4.SpringRunner; + +import br.com.codenation.hospital.constant.Constant; +import br.com.codenation.hospital.domain.Hospital; +import br.com.codenation.hospital.domain.Product; +import br.com.codenation.hospital.services.HospitalService; + +@RunWith(SpringRunner.class) +@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT) +@EnableAutoConfiguration +public class ProductResourceTest { + @Autowired + private TestRestTemplate restTemplate; + + @Autowired + private HospitalService hospitalService; + + private final HttpHeaders httpHeaders; + private Hospital hospitalTest; + private Product productTest; + + public ProductResourceTest() { + httpHeaders = new HttpHeaders(); + httpHeaders.setContentType(MediaType.APPLICATION_JSON_UTF8); + } + + @Before + public void setUp() { + hospitalTest = hospitalService.findById("1"); + if (hospitalTest != null) { + List productList = hospitalTest.getProducts(); + if (productList.size() > 0) { + hospitalTest.setProducts(productList); + productTest = productList.get(0); + } + } + } + + @Test + public void deveListarTodosProdutosDoHospital() { + ResponseEntity> response = restTemplate.exchange( + Constant.V1 + hospitalTest.getId() + "/estoque", HttpMethod.GET, null, + new ParameterizedTypeReference>() { + }); + assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK); + } + + @Test + public void deveListarProdutoDoHospital() { + ResponseEntity response = restTemplate.exchange( + Constant.V1 + hospitalTest.getId() + "/estoque/" + productTest.getId(), HttpMethod.GET, null, + new ParameterizedTypeReference() { + }); + assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK); + } + + @Test + public void naoDeveListarProdutoDoHospital() { + ResponseEntity response = restTemplate.exchange(Constant.V1 + hospitalTest.getId() + "/estoque/0", + HttpMethod.GET, null, new ParameterizedTypeReference() { + }); + assertThat(response.getStatusCode()).isEqualTo(HttpStatus.NOT_FOUND); + } + + @Test + public void deveAddProdutoNoHospital() { + ResponseEntity response = restTemplate.exchange(Constant.V1 + hospitalTest.getId() + "/estoque", + HttpMethod.POST, + new HttpEntity<>("{\n" + "\t\"name\": \"produto teste\",\n" + "\t\"description\": \"\", \n" + + "\t\"quantity\": 10,\n" + "\t\"productType\": \"COMMON\"\n" + "}", httpHeaders), + new ParameterizedTypeReference() { + }); + assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK); + } + + @Test + public void deveAtualizarProdutoNoHospital() { + ResponseEntity response = restTemplate.exchange( + Constant.V1 + hospitalTest.getId() + "/estoque/" + productTest.getId(), HttpMethod.PUT, + new HttpEntity<>("{\n" + "\t\"name\": \"produto update\",\n" + "\t\"quantity\": 50,\n" + + "\t\"productType\": \"COMMON\"\n" + "}", httpHeaders), + new ParameterizedTypeReference() { + }); + assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK); + } + + @Test + public void deveDeletarProdutoNoHospital() { + ResponseEntity response = restTemplate.exchange( + Constant.V1 + hospitalTest.getId() + "/estoque/" + productTest.getId(), HttpMethod.DELETE, null, + new ParameterizedTypeReference() { + }); + assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK); + } +} \ No newline at end of file diff --git a/sut-market/.dockerignore b/sut-market/.dockerignore new file mode 100644 index 0000000..4ffb597 --- /dev/null +++ b/sut-market/.dockerignore @@ -0,0 +1,6 @@ +.dockerignore +**/*.log +**/*.err +Dockerfile +.git +.gitignore \ No newline at end of file diff --git a/sut-market/.github/workflows/maven.yml b/sut-market/.github/workflows/maven.yml new file mode 100644 index 0000000..64d435e --- /dev/null +++ b/sut-market/.github/workflows/maven.yml @@ -0,0 +1,39 @@ +name: build + +on: [push] + +jobs: + build: + runs-on: ubuntu-latest + name: Build Maven project + steps: + - name: Checkout + uses: actions/checkout@v1 + + - name: Cache + uses: actions/cache@v1 + with: + path: ~/.m2/repository + key: m2 + + - name: Set up JDK 11 + uses: actions/setup-java@v1 + with: + java-version: 11 + + - name: Compile + run: mvn --file pom.xml clean compile + + - name: Build + run: mvn --file pom.xml install + + - name: Report coverage to Codacy + shell: bash + env: + CODACY_PROJECT_TOKEN: ${{ secrets.CODACY_PROJECT_TOKEN }} + run: | + if [ "$CODACY_PROJECT_TOKEN" != "" ] ; then + bash <(curl -Ls https://coverage.codacy.com/get.sh) report -l Java -r market-coverage/target/coverage-report/coverage-report.xml + else + echo "No CODACY_PROJECT_TOKEN provided for Codacy report" + fi \ No newline at end of file diff --git a/sut-market/.github/workflows/publish.yml b/sut-market/.github/workflows/publish.yml new file mode 100644 index 0000000..5470dfc --- /dev/null +++ b/sut-market/.github/workflows/publish.yml @@ -0,0 +1,30 @@ +name: publish + +on: + push: + branches: + - master + release: + types: [published, created, edited] + +jobs: + build: + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + - name: Set up JDK 11 + uses: actions/setup-java@v1 + with: + java-version: 11 + server-id: github # Value of the distributionManagement/repository/id field of the pom.xml + settings-path: ${{ github.workspace }} # location for the settings.xml file + + - name: Build with Maven + run: mvn -B package --file pom.xml + + - name: Publish to GitHub Packages Apache Maven + run: mvn deploy -s $GITHUB_WORKSPACE/settings.xml + env: + GITHUB_TOKEN: ${{ github.token }} \ No newline at end of file diff --git a/sut-market/.gitignore b/sut-market/.gitignore new file mode 100644 index 0000000..042d1d5 --- /dev/null +++ b/sut-market/.gitignore @@ -0,0 +1,17 @@ +!.idea +.idea/* +!.idea/codeStyles +*.iml +.gradle +build +classes +*/out +.classpath +.project +.settings +*/bin +*.orig +.attach_pid* +java_pid*.hprof +.DS_Store +!.mvn/wrapper/maven-wrapper.jar \ No newline at end of file diff --git a/sut-market/.idea/codeStyles/Project.xml b/sut-market/.idea/codeStyles/Project.xml new file mode 100644 index 0000000..1b52a67 --- /dev/null +++ b/sut-market/.idea/codeStyles/Project.xml @@ -0,0 +1,79 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/sut-market/.idea/codeStyles/codeStyleConfig.xml b/sut-market/.idea/codeStyles/codeStyleConfig.xml new file mode 100644 index 0000000..79ee123 --- /dev/null +++ b/sut-market/.idea/codeStyles/codeStyleConfig.xml @@ -0,0 +1,5 @@ + + + + \ No newline at end of file diff --git a/sut-market/.mvn/wrapper/MavenWrapperDownloader.java b/sut-market/.mvn/wrapper/MavenWrapperDownloader.java new file mode 100644 index 0000000..e76d1f3 --- /dev/null +++ b/sut-market/.mvn/wrapper/MavenWrapperDownloader.java @@ -0,0 +1,117 @@ +/* + * Copyright 2007-present the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import java.net.*; +import java.io.*; +import java.nio.channels.*; +import java.util.Properties; + +public class MavenWrapperDownloader { + + private static final String WRAPPER_VERSION = "0.5.6"; + /** + * Default URL to download the maven-wrapper.jar from, if no 'downloadUrl' is provided. + */ + private static final String DEFAULT_DOWNLOAD_URL = "https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/" + + WRAPPER_VERSION + "/maven-wrapper-" + WRAPPER_VERSION + ".jar"; + + /** + * Path to the maven-wrapper.properties file, which might contain a downloadUrl property to + * use instead of the default one. + */ + private static final String MAVEN_WRAPPER_PROPERTIES_PATH = + ".mvn/wrapper/maven-wrapper.properties"; + + /** + * Path where the maven-wrapper.jar will be saved to. + */ + private static final String MAVEN_WRAPPER_JAR_PATH = + ".mvn/wrapper/maven-wrapper.jar"; + + /** + * Name of the property which should be used to override the default download url for the wrapper. + */ + private static final String PROPERTY_NAME_WRAPPER_URL = "wrapperUrl"; + + public static void main(String args[]) { + System.out.println("- Downloader started"); + File baseDirectory = new File(args[0]); + System.out.println("- Using base directory: " + baseDirectory.getAbsolutePath()); + + // If the maven-wrapper.properties exists, read it and check if it contains a custom + // wrapperUrl parameter. + File mavenWrapperPropertyFile = new File(baseDirectory, MAVEN_WRAPPER_PROPERTIES_PATH); + String url = DEFAULT_DOWNLOAD_URL; + if(mavenWrapperPropertyFile.exists()) { + FileInputStream mavenWrapperPropertyFileInputStream = null; + try { + mavenWrapperPropertyFileInputStream = new FileInputStream(mavenWrapperPropertyFile); + Properties mavenWrapperProperties = new Properties(); + mavenWrapperProperties.load(mavenWrapperPropertyFileInputStream); + url = mavenWrapperProperties.getProperty(PROPERTY_NAME_WRAPPER_URL, url); + } catch (IOException e) { + System.out.println("- ERROR loading '" + MAVEN_WRAPPER_PROPERTIES_PATH + "'"); + } finally { + try { + if(mavenWrapperPropertyFileInputStream != null) { + mavenWrapperPropertyFileInputStream.close(); + } + } catch (IOException e) { + // Ignore ... + } + } + } + System.out.println("- Downloading from: " + url); + + File outputFile = new File(baseDirectory.getAbsolutePath(), MAVEN_WRAPPER_JAR_PATH); + if(!outputFile.getParentFile().exists()) { + if(!outputFile.getParentFile().mkdirs()) { + System.out.println( + "- ERROR creating output directory '" + outputFile.getParentFile().getAbsolutePath() + "'"); + } + } + System.out.println("- Downloading to: " + outputFile.getAbsolutePath()); + try { + downloadFileFromURL(url, outputFile); + System.out.println("Done"); + System.exit(0); + } catch (Throwable e) { + System.out.println("- Error downloading"); + e.printStackTrace(); + System.exit(1); + } + } + + private static void downloadFileFromURL(String urlString, File destination) throws Exception { + if (System.getenv("MVNW_USERNAME") != null && System.getenv("MVNW_PASSWORD") != null) { + String username = System.getenv("MVNW_USERNAME"); + char[] password = System.getenv("MVNW_PASSWORD").toCharArray(); + Authenticator.setDefault(new Authenticator() { + @Override + protected PasswordAuthentication getPasswordAuthentication() { + return new PasswordAuthentication(username, password); + } + }); + } + URL website = new URL(urlString); + ReadableByteChannel rbc; + rbc = Channels.newChannel(website.openStream()); + FileOutputStream fos = new FileOutputStream(destination); + fos.getChannel().transferFrom(rbc, 0, Long.MAX_VALUE); + fos.close(); + rbc.close(); + } + +} diff --git a/sut-market/.mvn/wrapper/maven-wrapper.jar b/sut-market/.mvn/wrapper/maven-wrapper.jar new file mode 100644 index 0000000..2cc7d4a Binary files /dev/null and b/sut-market/.mvn/wrapper/maven-wrapper.jar differ diff --git a/sut-market/.mvn/wrapper/maven-wrapper.properties b/sut-market/.mvn/wrapper/maven-wrapper.properties new file mode 100644 index 0000000..642d572 --- /dev/null +++ b/sut-market/.mvn/wrapper/maven-wrapper.properties @@ -0,0 +1,2 @@ +distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.6.3/apache-maven-3.6.3-bin.zip +wrapperUrl=https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar diff --git a/sut-market/Dockerfile b/sut-market/Dockerfile new file mode 100644 index 0000000..a8184b4 --- /dev/null +++ b/sut-market/Dockerfile @@ -0,0 +1,26 @@ +# cache offline as much dependencies as possible +FROM maven:3.6.3-jdk-11-slim@sha256:68ce1cd457891f48d1e137c7d6a4493f60843e84c9e2634e3df1d3d5b381d36c as dependencies +WORKDIR /app +COPY market-core/pom.xml market-core/pom.xml +COPY market-rest/pom.xml market-rest/pom.xml +COPY market-web/pom.xml market-web/pom.xml +COPY market-coverage/pom.xml market-coverage/pom.xml +COPY pom.xml . +RUN mvn -B -e -C org.apache.maven.plugins:maven-dependency-plugin:3.1.2:go-offline + +# build the project +FROM maven:3.6.3-jdk-11-slim@sha256:68ce1cd457891f48d1e137c7d6a4493f60843e84c9e2634e3df1d3d5b381d36c as build +WORKDIR /app +COPY --from=dependencies /root/.m2 /root/.m2 +COPY --from=dependencies /app/ /app +COPY market-core/src /app/market-core/src +COPY market-rest/src /app/market-rest/src +COPY market-web/src /app/market-web/src +RUN mvn -B -e clean install -DskipTests=true + +# create an image with the specified application +FROM adoptopenjdk/openjdk11:alpine-jre@sha256:89d70c41f6642605c5a7c655969e386815f2f4c0cf923bc1d87e2eadf8669330 +WORKDIR /app +ARG module +COPY --from=build /app/${module}/target/*.jar ./java-app.jar +ENTRYPOINT ["java","-jar","/app/java-app.jar"] \ No newline at end of file diff --git a/sut-market/LICENSE b/sut-market/LICENSE new file mode 100644 index 0000000..d5aea4a --- /dev/null +++ b/sut-market/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2020 Aleksey Lukyanets + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/sut-market/README.adoc b/sut-market/README.adoc new file mode 100644 index 0000000..b2a2819 --- /dev/null +++ b/sut-market/README.adoc @@ -0,0 +1,128 @@ += Simple internet-market + +image:https://api.codacy.com/project/badge/Grade/8c0bd51bdba44e04bd2cbbfd7f643e9f[link=https://www.codacy.com/manual/aleksey-lukyanets/market?utm_source=github.com&utm_medium=referral&utm_content=aleksey-lukyanets/market&utm_campaign=Badge_Grade] +image:https://api.codacy.com/project/badge/Coverage/8c0bd51bdba44e04bd2cbbfd7f643e9f[link=https://www.codacy.com/manual/aleksey-lukyanets/market?utm_source=github.com&utm_medium=referral&utm_content=aleksey-lukyanets/market&utm_campaign=Badge_Coverage] +image:https://github.com/aleksey-lukyanets/market/workflows/build/badge.svg[link] + +This project was started in 2014 as a study for Spring and related web technologies, +partly modernized in 2020-21. Since around 2014 professionally I am focused on backend development, +a modernization/maintenance of this project continues occasionally. +This is not a perfectly finished project, so it may contain imperfection and some mess. + +== Technologies + +* servlet: ``Spring MVC`` ``Thymeleaf`` ``jQuery`` ``Spring Security`` +* data: ``Spring Data JPA`` ``H2`` ``Hibernate`` +* RESTful service: ``Spring MVC`` ``Spring HATEOAS`` +* tests: ``JUnit`` ``Hamcrest`` ``Mockito`` ``JSONPath`` + +== How to + +The project is divided to modules and includes two applications: + +* Web-application, module *market-web*, default port: 8080 +** visit ``localhost:8080`` and ``localhost:8080/admin`` in your browser to enjoy the app +** visit ``localhost:8080/h2-console/`` to access H2 console +* REST-service, module *market-rest*, default port: 8081 +** visit ``localhost:8081/products`` to observe market products +** visit ``localhost:8081/swagger-ui/#/`` to observe Swagger API page +** visit ``localhost:8081/h2-console/`` to access H2 console + +These applications are designed to use similar database scheme and could be run together. + +You are able to open the project in your favourite IDE and simply run the main class of the application. + +By default an app is started with its own in-memory https://www.h2database.com[H2 database] +and thus all the data is dropped after stopping the app. +To connect to a standalone https://www.postgresql.org/[PostgreSQL] database run application with active Spring profile ``prod``. + +=== Running with Maven + +Each application could be run separately by executing the following Maven command +against a specific module: + +* Web-application: ``./mvnw spring-boot:run -pl market-web`` +* REST-service: ``./mvnw spring-boot:run -pl market-rest`` + +=== Running with Docker + +Both applications are packed into https://docs.docker.com/[Docker] images +(available https://hub.docker.com/u/alukyanets[on Docker Hub]). + +To run the Web-application with Docker you shoukd have Docker installed and follow the steps below: + +* pull the image from Docker Hub: ``docker pull alukyanets/market-web`` +* run a container with application listening on port 8080: ``docker run -p 8080:8080 alukyanets/market-web`` + +Quite similar for the REST-service: + +* pull the image from Docker Hub: ``docker pull alukyanets/market-rest`` +* run a container with application listening on port 8081: ``docker run -p 8081:8081 alukyanets/market-rest`` + +=== Running with docker-compose + +https://docs.docker.com/compose/[Docker-compose] makes possible starting with a single command +both an application and a standalone PostgreSQL database volume: + +* ``docker-compose -f docker-compose-web.yaml up`` or ``docker-compose -f docker-compose-rest.yaml up`` + +or running both applications sharing the same standalone PostgreSQL volume: + +* ``docker-compose -f docker-compose.yaml up`` + +To stop the running containers use ``Ctrl + C``. You are able to remove all the containers, e.g.: + +* ``docker-compose -f docker-compose.yaml down`` + +The created PostgreSQL volume will keep database state after stopping application. + +== Applications attributes + +[%header,cols=".^2,.^3,.^3",width=90%] +|=== +| |Web application |RESTful service +|*module* |market-web |market-rest +|*Java version* 2+^|11 +|*main class* |``WebApplication.java`` |``RestApplication.java`` +|*default port* |8080 |8081 +|*basic URL* |``localhost:8080``, ``localhost:8080/admin`` |``localhost:8081/products`` +|*user login* 2+|``admin/password`` ``ivan.petrov@yandex.ru/petrov`` +|*logs* 2+|``logs/`` +|*H2 console* |``localhost:8080/h2-console/`` |``localhost:8081/h2-console/`` +|*Swagger UI* |- |``localhost:8081/swagger-ui/#/`` +|=== + +== Functionality + +* Visual representation of the product range +* Customer's shopping cart +** selecting a product: add, delete, change a quantity +** viewing the contents of the cart +** placing an order +** storing in the database a shopping cart of a registered customer +* Market control panel +** products and categories: add, edit, delete +** viewing information about placed orders +** managing the availability of goods in stock +** transfer orders from the "in progress" state to the "executed" state +* Secured access to the application +** registration and authorization of customers +** restricted access to the control panel +* Double check of form content: client-side and server-side + +== Building Docker images + +To build an image run: + +* ``docker build -t alukyanets/market-web --build-arg module=market-web .`` +* or ``docker build -t alukyanets/market-rest --build-arg module=market-rest .`` + +== Legacy branches + +``jsp-2021`` - status with web views based on JSP and Apache Tiles, before moving to Thymeleaf + +``good-old-2014`` - status for 2014 + +== Links + +For project description in Russian from good old 2014 refer to ``README_RU.md`` diff --git a/sut-market/README_RU.md b/sut-market/README_RU.md new file mode 100644 index 0000000..78f1a70 --- /dev/null +++ b/sut-market/README_RU.md @@ -0,0 +1,316 @@ +[![Codacy Badge](https://api.codacy.com/project/badge/Grade/8c0bd51bdba44e04bd2cbbfd7f643e9f)](https://www.codacy.com/manual/aleksey-lukyanets/market?utm_source=github.com&utm_medium=referral&utm_content=aleksey-lukyanets/market&utm_campaign=Badge_Grade) +[![Codacy Badge](https://api.codacy.com/project/badge/Coverage/8c0bd51bdba44e04bd2cbbfd7f643e9f)](https://www.codacy.com/manual/aleksey-lukyanets/market?utm_source=github.com&utm_medium=referral&utm_content=aleksey-lukyanets/market&utm_campaign=Badge_Coverage) +![build](https://github.com/aleksey-lukyanets/market/workflows/build/badge.svg) + +

Технологии

+ +
    +
  • сервлет: Spring MVC, JavaServer Pages, Arache Tiles
  • +
  • авторизация пользователей: Spring Security
  • +
  • доступ к данным: Hibernate, Spring Data JPA
  • +
  • веб-служба в стиле REST
  • +
  • тесты: Spring Test, JUnit, Hamcrest, JSONPath;
  • +
  • веб-интерфейс: jQuery, jQuery Validate Plugin, Bootstrap
  • +
  • база данных: PostgreSQL
  • +
  • контейнер сервлетов: Apache Tomcat
  • +
+ +

Функционал магазина

+ +
    +
  1. Наглядное представление ассортимента товаров
  2. +
  3. Корзина покупателя +
      +
    • выбор товаров: добавление, удаление, изменение количества
    • +
    • просмотр содержимого корзины
    • +
    • оформление заказа
    • +
    • хранение корзины зарегистрированного покупателя в базе данных
    • +
    +
  4. +
  5. Панель управления магазином +
      +
    • товары и категории: добавление, редактирование, удаление
    • +
    • просмотр информации о размещённых заказах
    • +
    • управление наличием товаров на складе
    • +
    • перевод заказов из состояния "в исполнении" в состояние "исполнен"
    • +
    +
  6. +
  7. Безопасный доступ к приложению +
      +
    • регистрация и авторизация пользователей
    • +
    • ограничение доступа к панели управления
    • +
    +
  8. +
  9. Двойная проверка содержимого форм: на стороне клиента и на стороне сервера
  10. +
+ +

Оформление заказа

+ +

Ниже приведена диаграмма процесса оформления заказа, на которую нанесены + элементы данных и доступные покупателю действия.

+
+ + процесс оформления заказа + +
+
+ +

Веб-служба REST

+ +

Помимо гипертекстового интерфейса магазин представляет веб-службу, через которую + доступен функционал магазина. Описание веб-службы находится на странице + REST API.

+ +

Сортировка, фильтрация, разбивка на страницы

+ +

Приложение предоставляет возможность просматривать ресурсы удобным пользователю способом.

+

Не нарушая принципов стиля REST, критерии передаются в параметрах URI. Например, + в панели управления магазином так выглядит запрос отображения таблицы всех имеющихся + в наличии товаров, с сортировкой по возрастанию цены и 5 товаров на странице: + /admin/storage ? available=true & direct=asc & size=5 + (без пробелов).

+

Операции сортировки, фильтрации и постраничного отображения выделены в отдельную + иерархию классов в пакете market.sorting, которая объединяет + методы изменения значений опций, формирования запроса к БД (PageRequest + Spring Data) и добавления всех необходимых данных к модели Model Spring MVC.

+ +

Валидация форм

+ +

Проверка данных всех форм пользовательского и административного интерфейса выполняется + дважды: на стороне пользователя и на стороне сервера.

+
    +
  • Проверка на стороне пользователя осуществляется с использованием jQuery Validate Plugin, + который проверяет данные в момент ввода средствами JavaSript. Для посимвольной проверки строки применены + регулярные выражения (regex). Визуализация дополнена классами Bootstrap.
  • +
  • Проверка на стороне сервера выполняется с использованием пакетов javax.validation и + org.springframework.validation.
  • +
+

Такой подход к валидации форм делает процесс проверки данных комфортным + для пользователя и вместе с тем гарантирует выполнение проверки при отключённом + JavaScript в браузере пользователя.

+ +

Обработка исключений

+ +

В приложении реализована централизованная обработка исключений классом + market.controller.SpringExceptionHandler с аннотацией + @ControllerAdvice, предоставленной Spring.

+

Помимо объединения обработчиков в единый класс, такой подход позволяет + вынести проверку авторизации клиентов веб-сервиса из реализации контроллеров: + права пользователя проверяются перехватчиком + market.interceptor.RestUserCheckInterceptor и в случае неудачной + аутентификации исключение RestNotAuthenticatedException передаётся + обработчику в обход контроллеров, с последующим возвратом клиенту HTTP-состояния + 401 Unauthorized (вместо перенаправления на страницу входа, как в случае + гипертекстового интерфейса магазина). При удачной аутентификации запрос + передаётся соответствующему контроллеру.

+ +

Модель базы данных

+ +

База данных приложения состоит из 13 связанных таблиц, отображаемых средствами Hibernate в 14 классов.

+ +
+ + схема базы данных + +
+
+

Слой доступа к данным на первоначальном этапе разработки был представлен классами DAO, + а с введением функций разбивки на страницы и сортировки реализован + с помощью репозитория Spring Data JPA.

+ +

Пользовательские классы Spring

+ +

Функционал фреймворков Spring MVC и Spring Security расширен следующими классами:

+
    +
  • UserDetailsServiceImpl реализует интерфейс UserDetailsService + и обеспечивает извлечение профиля пользователя из базы данных;
  • +
  • CustomAuthenticationSuccessHandler реализует интерфейс + AuthenticationSuccessHandler и обрабатывает событие успешной аутентификации пользователя;
  • +
  • SpringExceptionHandler осуществляет централизованную обработку исключений;
  • +
  • SessionCartInterceptor реализует интерфейс HandlerInterceptorAdapter + и проверяет до обработки запроса контроллерами, существует ли в модели атрибут корзины покупателя; + при отсутствии создатся новая корзина; такое решение позволяет централизовать создание корзины, + в том числе для случая, когда браузер пользователя не принимает cookies и поэтому + не поддерживает хранение параметров сессии;
  • +
  • RestUserCheckInterceptor реализует интерфейс HandlerInterceptorAdapter + и используется для проверки прав пользователя при доступе к веб-службе.
  • +
+ +

Веб-служба REST

+ +

REST-интерфейс приложения предоставляет доступ к ресурсам магазина: позволяет + регистрировать покупателей, изменять их контактные данные, запрашивать + сведения о товарах, добавлять товары в корзину и оформлять заказы.

+

Обмен данными между клиентом и веб-службой магазина осуществляется в формате JSON, + аутентификация выполняется средствами Basic Authentication.

+

В соответствии со стилем REST, взаимодействие между клиентом и сервером лишено + сеансового состояния, поэтому веб-служба магазина не предоставляет гостевую + корзину: информация о товарах доступна всем клиентам, но заказ может быть + собран и оформлен только зарегистрированным пользователем.

+

Ниже приводится интерфейс веб-службы и примеры взаимодействия.

+ +

Взаимодействие с веб-службой

+ +

Доступ к ресурсам магазина можно получить с использованием любого HTTP-клиента, + поддерживающего Basic-аутентификацию, например RESTClient для Mozilla Firefox.

+

Так обращение к ресурсу http://46.101.111.55:8080/rest/products + возвращает список всех товаров:

+
[
{
"productId": 1,
"distillery": "Ardbeg",
"name": "Ten",
"price": 4030,
"links": [{
"rel": "self",
"href": "http://46.101.111.55:8080/rest/products/1"}]
},
{
"productId": 2,
"distillery": "Balvenie",
"name": "12 y.o. Doublewood",
"price": 4100,
"links": [{
"rel": "self",
"href": "http://46.101.111.55:8080/rest/products/2" }]
}

...
]
+

Сведения об отдельном товаре доступны по его идентификатору. + Ресурс /products/2:

+
{
"id": 2,
"distillery": "Balvenie",
"name": "12 y.o. Doublewood",
"price": 4100,
"age": 12,
"volume": 700,
"alcohol": 40,
"description": "Помимо стандартных бочек выдерживается в бочках
из-под хереса. Гармоничный аромат свежего мёда, ванили,
воска, дуба и цветочной пыльцы с сухой горчинкой.",
"inStock": true
}
+ +
+
Регистрация покупателя
+ +

Регистрация нового покупателя осуществляется отправкой запроса POST /signup:

+
заголовок:   Content-Type: application/json; charset=UTF-8
тело: {
"email": "ivan.petrov@yandex.ru",
"name": "Иван Петров",
"password": "Иван Петров",
"phone": "+7 123 456 67 89",
"address": "ул. Итальянская, д. 7"
}
+

Ответ приложения при успешном создании аккаунта: 200 Ok.

+

Если покупатель с указанным адресом электронной почты уже существует в магазине, + либо если возникли нарушения при валидации переданных данных, клиенту будет возвращено + HTTP-состояние 406 Not Acceptable с соответствующими пояснениями.

+

Например, об ошибке в имени "name": "name@#$%^" + сервер уведомит ответом:

+
{
"fieldErrors":
[{
"field": "name",
"message": "В имени допустимы только буквы, пробел, дефис и апостроф."
}]
}
+

Получить или изменить контактные данные можно обращением к ресурсу соответственно + /customer/contacts запросами GET или PUT.

+ +
+
Формирование заказа
+ +

Зарегистрированный покупатель может добавить товар в корзину запросом PUT /cart:

+
заголовок:   Content-Type: application/json; charset=UTF-8
Authorization: Basic aXZhbi5wZXRyb3ZAeWFuZGV4LnJ10nBldHJvdg==
тело: {"productId": 2, "quantity": 1}
+

В ответе приложение вернёт изменённую корзину:

+
заголовок:   Status Code: 200 Ok
Content-Type: application/json; charset=UTF-8
тело: {
"user": "ivan.petrov@yandex.ru",
"items":
[
{
"productId": 2,
"quantity": 1,
"links": [{
"rel": "self",
"href": "http://46.101.111.55:8080/rest/products/2" }]
}
],
"totalItems": 1,
"productsCost": 4100,
"deliveryCost": 400,
"deliveryIncluded": true,
"totalCost": 4500,
"links":
[
{
"rel": "Customer contacts",
"href": "http://46.101.111.55:8080/rest/customer/contacts"
},
{
"rel": "Payment",
"href": "http://46.101.111.55:8080/rest/cart/payment"
}
]
}
+

Опция доставки может быть изменена запросом PUT /cart/delivery/{boolean}.

+
+

Для оформления заказа следует отправить номер банковской карты, с которой + будет оплачен заказ, запросом POST /cart/payment:

+
заголовок:   Content-Type: application/json; charset=UTF-8
Authorization: Basic aXZhbi5wZXRyb3ZAeWFuZGV4LnJ10nBldHJvdg==
тело: {"number": "4444 3333 2222 1111"}
+

Приложение вернёт подтверждение об оплате и приёме заказа:

+
заголовок:   Status Code: 201 Created
Location: http://46.101.111.55:8080/rest/customer/orders/13
Content-Type: application/json; charset=UTF-8
тело: {
"id": 13,
"user": "ivan.petrov@yandex.ru",
"billNumber": 525553712,
"dateCreated": 1397559589798,
"productsCost": 4100,
"deliveryCost": 400,
"deliveryIncluded": true,
"totalCost": 4500,
"payed": true,
"executed": false
}
+

Получить перечень всех оставленных покупателем заказов можно обратившись к ресурсу + /customer/orders.

+
+ +

Операции с ресурсами

+ +

Сведения о товарах

+ + + + + + + + + + + + + + + + + + + + +
ресурсописаниестатусы ответа
GET /productsВозвращает перечень всех товаров магазина200
GET /products/:idВозвращает товар с указанным id200 — товар возвращён в теле ответа,
+ 404 — товара с таким id не существует
+ +

Корзина покупателя (требует авторизации)

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ресурсописаниестатусы ответа
GET /cartВозвращает корзину покупателя200
PUT /cartДобавляет товар в корзину200 — товар добавлен, обновлённая корзина находится в теле ответа,
+ 406 — товара с запрошенным id не существует; пояснения в теле ответа
DELETE /cartУдаляет из корзины все товары200 — корзина очищена, обновлённая корзина находится в теле ответа
PUT /cart/delivery/:booleanВключает в заказ доставку200 — опция доставки изменена, обновлённая корзина находится в теле ответа
POST /cart/paymentПодтверждает заказ и оплачивает его картой с указанным номером201 — заказ оплачен и принят, ссылка на заказ находится в заголовке, детали заказа — в теле ответа,
+ 406 — некорректный формат номера карты, либо корзина пуста; пояснения в теле ответа
+ +

Регистрация нового покупателя

+ + + + + + + + + + + + + + + +
ресурсописаниестатусы ответа
POST /signupРегистрирует нового покупателя200 — покупатель зарегистрирован и возвращён в теле ответа,
+ 406 — некорректный формат полученных данных; пояснения в теле ответа
+ +

Профиль покупателя (требует авторизации)

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ресурсописаниестатусы ответа
GET /customer/contactsВозвращает контактные данные покупателя200 — контактные данные возвращены в теле ответа
PUT /customer/contactsИзменяет контактные данные покупателя200 — данные изменены, обновлённые возвращены в теле ответа,
+ 406 — некорректный формат полученных данных; пояснения в теле ответа
GET /customer/ordersВозвращает перечень заказов покупателя200 — перечень заказов возвращён в теле ответа
GET /customer/orders/:idВозвращает заказ с указанным id200 — заказ возвращён в теле ответа,
+ 404 — заказ с таким id у авторизованного пользователя не существует
diff --git a/sut-market/database-model.mwb b/sut-market/database-model.mwb new file mode 100644 index 0000000..0c06621 Binary files /dev/null and b/sut-market/database-model.mwb differ diff --git a/sut-market/docker-compose-jenkins-workspace.sh b/sut-market/docker-compose-jenkins-workspace.sh new file mode 100644 index 0000000..40cfdec --- /dev/null +++ b/sut-market/docker-compose-jenkins-workspace.sh @@ -0,0 +1,19 @@ +# La forma natural de hacer el bind de los ficheros sql de setup de la bd +# seria usar en el docker compose la variable de entorno WORKSPACE que pone jenkins. +# Esto asigna el path corecto a los bindings (si se examina el container con inspect) +# pero luego en tiempo de ejecucion no se localizan. +# Puede ser debido a que la localizacion del workspace ya es un bind en el slave +# y que esto causa un doble bind? + +# La solucion sera este shell que modifica el docker compose: +# - Obtiene WORKSPACE_FOLDER_VAR como el ultimo componente del workspace +# - En el docker compose se hara el bind con la ruta absoluta al workspace general del slave, +# anyadiendo WORKSPACE_FOLDER_VAR y luego la ruta de los ficheros. + +# WORKSPACE="/ab/cd/efxxxxxxxxxx" + +echo "Replacing workspace location in docker compose file docker-compose-jenkins.yaml" +WORKSPACE_FOLDER_VAR=`echo $WORKSPACE | awk -F/ '{print $NF}'` +echo "Workspace folder is $WORKSPACE_FOLDER_VAR" + +sed -i "s/WORKSPACE_FOLDER/${WORKSPACE_FOLDER_VAR}/" docker-compose-jenkins.yaml diff --git a/sut-market/docker-compose-jenkins.yaml b/sut-market/docker-compose-jenkins.yaml new file mode 100644 index 0000000..86ec92a --- /dev/null +++ b/sut-market/docker-compose-jenkins.yaml @@ -0,0 +1,40 @@ +version: '3' +services: + db: + container_name: swagger-market-db + image: postgres:9.6 + #ports: + # - 8384:5432 + environment: + - POSTGRES_DB=market + - POSTGRES_USER=market + - POSTGRES_PASSWORD=market + - WORKSPACE + volumes: + # No se puede usar la variable de entorno del workspace, ver docker-compose-jenkins-workspace.sh + #- ${WORKSPACE}/swagger-market-fork/market-core/src/main/resources/schema.sql:/docker-entrypoint-initdb.d/1_schema.sql + #- ${WORKSPACE}/swagger-market-fork/market-core/src/main/resources/data.sql:/docker-entrypoint-initdb.d/2_data.sql + - /t2/cdat/jenkins-slave-xa/workspace/WORKSPACE_FOLDER/swagger-market-fork/market-core/src/main/resources/schema.sql:/docker-entrypoint-initdb.d/1_schema.sql + - /t2/cdat/jenkins-slave-xa/workspace/WORKSPACE_FOLDER/swagger-market-fork/market-core/src/main/resources/data.sql:/docker-entrypoint-initdb.d/2_data.sql + - postgres-data:/var/lib/postgresql/data + networks: + - net-swagger-test + market-rest: + container_name: swagger-market-rest + image: swagger-market-rest + depends_on: + - db + ports: + - 8383:8081 + environment: + - SPRING_PROFILES_ACTIVE=prod + - DB_HOST=db + networks: + - net-swagger-test +volumes: + postgres-data: + driver: local +networks: + # all containers are in this network, same as the dedicated jenkins agent + net-swagger-test: + external: true diff --git a/sut-market/docker-compose-rest.yaml b/sut-market/docker-compose-rest.yaml new file mode 100644 index 0000000..0403192 --- /dev/null +++ b/sut-market/docker-compose-rest.yaml @@ -0,0 +1,28 @@ +version: '3' +services: + db: + container_name: db + image: postgres:9.6 + ports: + - 8084:5432 + environment: + - POSTGRES_DB=market + - POSTGRES_USER=market + - POSTGRES_PASSWORD=market + volumes: + - ./market-core/src/main/resources/schema.sql:/docker-entrypoint-initdb.d/1_schema.sql + - ./market-core/src/main/resources/data.sql:/docker-entrypoint-initdb.d/2_data.sql + - postgres-data:/var/lib/postgresql/data + market-rest: + container_name: market-rest + image: alukyanets/market-rest + depends_on: + - db + ports: + - 8083:8081 + environment: + - SPRING_PROFILES_ACTIVE=prod + - DB_HOST=db +volumes: + postgres-data: + driver: local \ No newline at end of file diff --git a/sut-market/docker-compose-web.yaml b/sut-market/docker-compose-web.yaml new file mode 100644 index 0000000..181e594 --- /dev/null +++ b/sut-market/docker-compose-web.yaml @@ -0,0 +1,28 @@ +version: '3' +services: + db: + container_name: db + image: postgres:9.6 + ports: + - 8084:5432 + environment: + - POSTGRES_DB=market + - POSTGRES_USER=market + - POSTGRES_PASSWORD=market + volumes: + - ./market-core/src/main/resources/schema.sql:/docker-entrypoint-initdb.d/1_schema.sql + - ./market-core/src/main/resources/data.sql:/docker-entrypoint-initdb.d/2_data.sql + - postgres-data:/var/lib/postgresql/data + market-web: + container_name: market-web + image: alukyanets/market-web + depends_on: + - db + ports: + - 8082:8080 + environment: + - SPRING_PROFILES_ACTIVE=prod + - DB_HOST=db +volumes: + postgres-data: + driver: local \ No newline at end of file diff --git a/sut-market/docker-compose.yaml b/sut-market/docker-compose.yaml new file mode 100644 index 0000000..4e9a491 --- /dev/null +++ b/sut-market/docker-compose.yaml @@ -0,0 +1,38 @@ +version: '3' +services: + db: + container_name: db + image: postgres:9.6 + ports: + - 8084:5432 + environment: + - POSTGRES_DB=market + - POSTGRES_USER=market + - POSTGRES_PASSWORD=market + volumes: + - ./market-core/src/main/resources/schema.sql:/docker-entrypoint-initdb.d/1_schema.sql + - ./market-core/src/main/resources/data.sql:/docker-entrypoint-initdb.d/2_data.sql + - postgres-data:/var/lib/postgresql/data + market-web: + container_name: market-web + image: market-web + depends_on: + - db + ports: + - 8082:8080 + environment: + - SPRING_PROFILES_ACTIVE=prod + - DB_HOST=db + market-rest: + container_name: market-rest + image: market-rest + depends_on: + - db + ports: + - 8083:8081 + environment: + - SPRING_PROFILES_ACTIVE=prod + - DB_HOST=db +volumes: + postgres-data: + driver: local \ No newline at end of file diff --git a/sut-market/market-core/.gitignore b/sut-market/market-core/.gitignore new file mode 100644 index 0000000..b83d222 --- /dev/null +++ b/sut-market/market-core/.gitignore @@ -0,0 +1 @@ +/target/ diff --git a/sut-market/market-core/pom.xml b/sut-market/market-core/pom.xml new file mode 100644 index 0000000..2c7b218 --- /dev/null +++ b/sut-market/market-core/pom.xml @@ -0,0 +1,111 @@ + + + + market + net.lukyanets + 0.1.2 + .. + + 4.0.0 + + market-core + 0.1.2 + + market-core + + + UTF-8 + + + + + + org.apache.maven.plugins + maven-deploy-plugin + 2.8.2 + + true + + + + org.jacoco + jacoco-maven-plugin + ${jacoco.version} + + + + prepare-agent + + + + report + prepare-package + + report + + + + + + **/*market/dto/** + **/*market/exception/** + + + + + + + + + + com.fasterxml.jackson.core + jackson-annotations + 2.16.1 + + + com.h2database + h2 + runtime + + + org.postgresql + postgresql + 42.3.2 + + + javax.servlet + javax.servlet-api + 3.1.0 + + + + org.springframework.hateoas + spring-hateoas + + + + org.springframework.boot + spring-boot-starter-data-jpa + + + org.springframework.boot + spring-boot-starter-jdbc + + + org.springframework.boot + spring-boot-starter-security + + + org.springframework.boot + spring-boot-starter-validation + + + + org.springframework.boot + spring-boot-starter-test + + + + \ No newline at end of file diff --git a/sut-market/market-core/src/main/java/market/DataConfig.java b/sut-market/market-core/src/main/java/market/DataConfig.java new file mode 100644 index 0000000..41cac1b --- /dev/null +++ b/sut-market/market-core/src/main/java/market/DataConfig.java @@ -0,0 +1,65 @@ +package market; + +import com.zaxxer.hikari.HikariDataSource; +import org.springframework.boot.autoconfigure.domain.EntityScan; +import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Primary; +import org.springframework.dao.annotation.PersistenceExceptionTranslationPostProcessor; +import org.springframework.orm.jpa.JpaTransactionManager; +import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean; +import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter; +import org.springframework.transaction.PlatformTransactionManager; +import org.springframework.transaction.annotation.EnableTransactionManagement; + +import javax.persistence.EntityManagerFactory; +import javax.sql.DataSource; + +@Configuration +@EntityScan(basePackages="market.domain") +@EnableTransactionManagement +@ComponentScan(basePackages = { + "market.properties", + "market.service" +}) +public class DataConfig { + + @Bean + @Primary + @ConfigurationProperties("spring.datasource") + public DataSourceProperties dataSourceProperties() { + return new DataSourceProperties(); + } + + @Bean // with Spring Boot this is actually not necessary but leaving it to note things explicitly + @Primary + @ConfigurationProperties("spring.datasource.configuration") + public HikariDataSource dataSource() { + DataSourceProperties properties = dataSourceProperties(); + return properties.initializeDataSourceBuilder().type(HikariDataSource.class).build(); + } + + @Bean + public LocalContainerEntityManagerFactoryBean entityManagerFactory(DataSource dataSource) { + LocalContainerEntityManagerFactoryBean em = new LocalContainerEntityManagerFactoryBean(); // todo: EntityManagerFactoryBuilder ? + em.setDataSource(dataSource); + em.setPackagesToScan("market.domain"); + em.setJpaVendorAdapter(new HibernateJpaVendorAdapter()); + return em; + } + + @Bean + public PlatformTransactionManager transactionManager(EntityManagerFactory emf) { + JpaTransactionManager transactionManager = new JpaTransactionManager(); + transactionManager.setEntityManagerFactory(emf); + return transactionManager; + } + + @Bean + public PersistenceExceptionTranslationPostProcessor exceptionTranslation() { + return new PersistenceExceptionTranslationPostProcessor(); + } +} diff --git a/sut-market/market-core/src/main/java/market/FixturesFactory.java b/sut-market/market-core/src/main/java/market/FixturesFactory.java new file mode 100644 index 0000000..d63f0f4 --- /dev/null +++ b/sut-market/market-core/src/main/java/market/FixturesFactory.java @@ -0,0 +1,116 @@ +package market; + +import market.domain.*; + +import java.util.Date; + +public class FixturesFactory { + private static final String DISTILLERY_TITLE = "distillery_title"; + private static final String DISTILLERY_DESCRIPTION = "distillery_description"; + + private static final String REGION_NAME = "region name"; + private static final String REGION_SUBTITLE = "region subtitle"; + private static final String REGION_DESCRIPTION = "region description"; + private static final String REGION_COLOR = "#ffffff"; + + private static final double PRODUCT_PRICE = 100.0; + private static final String PRODUCT_NAME = "product_name"; + private static final int PRODUCT_AGE = 10; + private static final float PRODUCT_ALCOHOL = 40; + private static final int PRODUCT_VOLUME = 700; + private static final String PRODUCT_DESCRIPTION = "product_description"; + private static final boolean PRODUCT_AVAILABLE = true; + + private static final String ACCOUNT_EMAIL = "email@domain.com"; + private static final String ACCOUNT_PASSWORD = "password"; + private static final String ACCOUNT_NAME = "Name"; + private static final boolean ACCOUNT_ACTIVE = true; + + private static final String CONTACTS_PHONE = "+97211234567"; + private static final String CONTACTS_ADDRESS = "some_address"; + + private static final int ORDERED_PRODUCT_QUANTITY = 3; + + private static final String BILL_CARD_NUMBER = "1111222233334444"; + + private static long regionId = 123L; + private static long distilleryId = 234L; + private static long productId = 10L; + private static long accountId = 50L; + private static long orderId = 3000L; + private static int billId = 400; + + public static Region.Builder region() { + return new Region.Builder() + .setId(++regionId) + .setName(REGION_NAME + regionId) + .setSubtitle(REGION_SUBTITLE) + .setDescription(REGION_DESCRIPTION) + .setColor(REGION_COLOR); + } + + public static Distillery.Builder distillery(Region region) { + return new Distillery.Builder() + .setId(++distilleryId) + .setRegion(region) + .setTitle(DISTILLERY_TITLE + distilleryId) + .setDescription(DISTILLERY_DESCRIPTION); + } + + public static Product.Builder product(Distillery distillery) { + return new Product.Builder() + .setId(++productId) + .setDistillery(distillery) + .setName(PRODUCT_NAME + productId) + .setAge(PRODUCT_AGE) + .setAlcohol(PRODUCT_ALCOHOL) + .setPrice(PRODUCT_PRICE) + .setVolume(PRODUCT_VOLUME) + .setDescription(PRODUCT_DESCRIPTION) + .setAvailable(PRODUCT_AVAILABLE); + } + + public static UserAccount.Builder account(Cart cart) { + return account() + .setCart(cart); + } + + public static UserAccount.Builder account() { + return new UserAccount.Builder() + .setId(++accountId) + .setEmail(ACCOUNT_EMAIL) + .setPassword(ACCOUNT_PASSWORD) + .setName(ACCOUNT_NAME) + .setActive(ACCOUNT_ACTIVE); + } + + public static Contacts.Builder contacts() { + return new Contacts.Builder() + .setPhone(CONTACTS_PHONE) + .setAddress(CONTACTS_ADDRESS); + } + + public static Order.Builder order(UserAccount userAccount) { + return new Order.Builder() + .setId(++orderId) + .setUserAccount(userAccount) + .setDateCreated(new Date()); + } + + public static OrderedProduct.Builder orderedProduct(Order order, Product product) { + return new OrderedProduct.Builder() + .setProduct(product) + .setOrder(order) + .setQuantity(ORDERED_PRODUCT_QUANTITY); + } + + public static Bill.Builder bill(Order order) { + return new Bill.Builder() + .setOrder(order) + .setNumber(++billId) + .setTotalCost(order.getProductsCost() + order.getDeliveryCost()) + .setPayed(true) + .setDateCreated(new Date()) + .setCcNumber(BILL_CARD_NUMBER); + } +} diff --git a/sut-market/market-core/src/main/java/market/SecurityConfigBase.java b/sut-market/market-core/src/main/java/market/SecurityConfigBase.java new file mode 100644 index 0000000..33f2488 --- /dev/null +++ b/sut-market/market-core/src/main/java/market/SecurityConfigBase.java @@ -0,0 +1,63 @@ +package market; + +import market.security.AuthenticationService; +import market.security.UserDetailsServiceImpl; +import market.service.UserAccountService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.authentication.dao.DaoAuthenticationProvider; +import org.springframework.security.config.BeanIds; +import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; +import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import org.springframework.security.crypto.password.PasswordEncoder; + +@Configuration +@EnableWebSecurity +@ComponentScan(basePackages = {"market.security"}) +@EnableGlobalMethodSecurity(securedEnabled = true) +public class SecurityConfigBase extends WebSecurityConfigurerAdapter { + + @Autowired + private DaoAuthenticationProvider daoAuthenticationProvider; + + @Bean + public AuthenticationService authenticationService(AuthenticationManager authenticationManager) { + return new AuthenticationService(authenticationManager); + } + + @Bean(name = BeanIds.AUTHENTICATION_MANAGER) + @Override + public AuthenticationManager authenticationManagerBean() throws Exception { + return super.authenticationManagerBean(); + } + + protected void configure(AuthenticationManagerBuilder auth) { + auth.authenticationProvider(daoAuthenticationProvider); + } + + @Bean + public DaoAuthenticationProvider daoAuthenticationProvider( + UserDetailsServiceImpl customUserDetailsService, PasswordEncoder passwordEncoder) + { + DaoAuthenticationProvider provider = new DaoAuthenticationProvider(); + provider.setUserDetailsService(customUserDetailsService); + provider.setPasswordEncoder(passwordEncoder); + return provider; + } + + @Bean + public UserDetailsServiceImpl customUserDetailsService(UserAccountService userAccountService) { + return new UserDetailsServiceImpl(userAccountService); + } + + @Bean + public PasswordEncoder passwordEncoder() { + return new BCryptPasswordEncoder(); + } +} diff --git a/sut-market/market-core/src/main/java/market/dao/CartDAO.java b/sut-market/market-core/src/main/java/market/dao/CartDAO.java new file mode 100644 index 0000000..dd8e9e9 --- /dev/null +++ b/sut-market/market-core/src/main/java/market/dao/CartDAO.java @@ -0,0 +1,9 @@ +package market.dao; + +import market.domain.Cart; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.repository.CrudRepository; + +public interface CartDAO extends CrudRepository, JpaRepository { + +} diff --git a/sut-market/market-core/src/main/java/market/dao/ContactsDAO.java b/sut-market/market-core/src/main/java/market/dao/ContactsDAO.java new file mode 100644 index 0000000..7397097 --- /dev/null +++ b/sut-market/market-core/src/main/java/market/dao/ContactsDAO.java @@ -0,0 +1,11 @@ +package market.dao; + +import market.domain.Contacts; +import market.domain.UserAccount; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.repository.CrudRepository; + +public interface ContactsDAO extends CrudRepository, JpaRepository { + + Contacts findByUserAccount(UserAccount userAccount); +} diff --git a/sut-market/market-core/src/main/java/market/dao/DistilleryDAO.java b/sut-market/market-core/src/main/java/market/dao/DistilleryDAO.java new file mode 100644 index 0000000..b430497 --- /dev/null +++ b/sut-market/market-core/src/main/java/market/dao/DistilleryDAO.java @@ -0,0 +1,15 @@ +package market.dao; + +import market.domain.Distillery; +import market.domain.Region; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.repository.CrudRepository; + +import java.util.List; + +public interface DistilleryDAO extends CrudRepository, JpaRepository { + + List findByRegionOrderByTitleAsc(Region region); + + Distillery findByTitle(String title); +} diff --git a/sut-market/market-core/src/main/java/market/dao/OrderDAO.java b/sut-market/market-core/src/main/java/market/dao/OrderDAO.java new file mode 100644 index 0000000..95f059e --- /dev/null +++ b/sut-market/market-core/src/main/java/market/dao/OrderDAO.java @@ -0,0 +1,22 @@ +package market.dao; + +import market.domain.Order; +import market.domain.UserAccount; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.repository.CrudRepository; + +import java.util.Date; +import java.util.List; + +public interface OrderDAO extends CrudRepository, JpaRepository { + + List findByUserAccountOrderByDateCreatedDesc(UserAccount userAccount); + + Page findByExecuted(boolean stored, Pageable pageable); + + Page findByDateCreatedGreaterThan(Date created, Pageable pageable); + + Page findByExecutedAndDateCreatedGreaterThan(boolean executed, Date created, Pageable pageable); +} diff --git a/sut-market/market-core/src/main/java/market/dao/OrderedProductDAO.java b/sut-market/market-core/src/main/java/market/dao/OrderedProductDAO.java new file mode 100644 index 0000000..b484e49 --- /dev/null +++ b/sut-market/market-core/src/main/java/market/dao/OrderedProductDAO.java @@ -0,0 +1,9 @@ +package market.dao; + +import market.domain.OrderedProduct; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.repository.CrudRepository; + +public interface OrderedProductDAO extends CrudRepository, JpaRepository { + +} diff --git a/sut-market/market-core/src/main/java/market/dao/ProductDAO.java b/sut-market/market-core/src/main/java/market/dao/ProductDAO.java new file mode 100644 index 0000000..e4276d2 --- /dev/null +++ b/sut-market/market-core/src/main/java/market/dao/ProductDAO.java @@ -0,0 +1,21 @@ +package market.dao; + +import market.domain.Distillery; +import market.domain.Product; +import market.domain.Region; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.CrudRepository; +import org.springframework.data.repository.query.Param; + +public interface ProductDAO extends CrudRepository, JpaRepository { + + Page findByDistilleryOrderByName(Distillery distillery, Pageable request); + + @Query(value = "SELECT p FROM Product p WHERE p.distillery IN (SELECT d FROM Distillery d WHERE d.region = :region) order by p.name") + Page findByRegionOrderByName(@Param("region") Region region, Pageable request); + + Page findByAvailableOrderByName(boolean available, Pageable request); +} diff --git a/sut-market/market-core/src/main/java/market/dao/RegionDAO.java b/sut-market/market-core/src/main/java/market/dao/RegionDAO.java new file mode 100644 index 0000000..5b89859 --- /dev/null +++ b/sut-market/market-core/src/main/java/market/dao/RegionDAO.java @@ -0,0 +1,13 @@ +package market.dao; + +import market.domain.Region; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.repository.CrudRepository; + +import java.util.Optional; + +public interface RegionDAO extends CrudRepository, JpaRepository { + + Optional findByName(String regionName); + +} diff --git a/sut-market/market-core/src/main/java/market/dao/UserAccountDAO.java b/sut-market/market-core/src/main/java/market/dao/UserAccountDAO.java new file mode 100644 index 0000000..9ca336b --- /dev/null +++ b/sut-market/market-core/src/main/java/market/dao/UserAccountDAO.java @@ -0,0 +1,10 @@ +package market.dao; + +import market.domain.UserAccount; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.repository.CrudRepository; + +public interface UserAccountDAO extends CrudRepository, JpaRepository { + + UserAccount findByEmail(String email); +} diff --git a/sut-market/market-core/src/main/java/market/domain/Bill.java b/sut-market/market-core/src/main/java/market/domain/Bill.java new file mode 100644 index 0000000..a65ff6c --- /dev/null +++ b/sut-market/market-core/src/main/java/market/domain/Bill.java @@ -0,0 +1,171 @@ +package market.domain; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.OneToOne; +import javax.persistence.PrimaryKeyJoinColumn; +import javax.persistence.Table; +import javax.persistence.Temporal; +import javax.validation.constraints.NotEmpty; +import javax.validation.constraints.Pattern; + +import com.fasterxml.jackson.annotation.JsonBackReference; + +import java.io.Serializable; +import java.util.Date; + +import static javax.persistence.TemporalType.TIMESTAMP; + +/** + * Bill of the {@link Order}. + */ +@Entity +@Table(name = "bill") +public class Bill implements Serializable { + private static final long serialVersionUID = 3689283961628876802L; + + @Id + @Column(name = "id", unique = true, nullable = false) + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + // // Test: anyadido para evitar recursividad en json con order + @JsonBackReference + @OneToOne + @PrimaryKeyJoinColumn + private Order order; + + @Column(name = "number", nullable = false) + private int number; + + @Column(name = "total_cost", nullable = false) + private double totalCost; + + @Column(name = "payed", nullable = false) + private boolean payed = false; + + @Column(name = "cc_number", nullable = false) + @NotEmpty + @Pattern(regexp = "\\b(?:\\d[ -]*?){13,16}\\b") + private String ccNumber; + + @Column(name = "date_created", nullable = false) + @Temporal(TIMESTAMP) + private Date dateCreated; + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getCcNumber() { + return ccNumber; + } + + public void setCcNumber(String ccNumber) { + this.ccNumber = ccNumber; + } + + public Order getOrder() { + return order; + } + + public void setOrder(Order order) { + this.order = order; + } + + public int getNumber() { + return number; + } + + public void setNumber(int number) { + this.number = number; + } + + public Date getDateCreated() { + return dateCreated; + } + + public void setDateCreated(Date dateCreated) { + this.dateCreated = dateCreated; + } + + public double getTotalCost() { + return totalCost; + } + + public void setTotalCost(double totalCost) { + this.totalCost = totalCost; + } + + public boolean isPayed() { + return payed; + } + + public void setPayed(boolean payed) { + this.payed = payed; + } + + public static class Builder { + private Long id; + private Order order; + private int number; + private double totalCost; + private boolean payed = false; + private String ccNumber; + private Date dateCreated; + + public Bill build() { + Bill bill = new Bill(); + bill.id = id; + bill.order = order; + bill.number = number; + bill.totalCost = totalCost; + bill.payed = payed; + bill.ccNumber = ccNumber; + bill.dateCreated = dateCreated; + return bill; + } + + public Builder setId(Long id) { + this.id = id; + return this; + } + + public Builder setOrder(Order order) { + this.order = order; + return this; + } + + public Builder setNumber(int number) { + this.number = number; + return this; + } + + public Builder setTotalCost(double totalCost) { + this.totalCost = totalCost; + return this; + } + + public Builder setPayed(boolean payed) { + this.payed = payed; + return this; + } + + public Builder setCcNumber(String ccNumber) { + this.ccNumber = ccNumber; + return this; + } + + public Builder setDateCreated(Date dateCreated) { + this.dateCreated = dateCreated; + return this; + } + } +} diff --git a/sut-market/market-core/src/main/java/market/domain/Cart.java b/sut-market/market-core/src/main/java/market/domain/Cart.java new file mode 100644 index 0000000..e81c9da --- /dev/null +++ b/sut-market/market-core/src/main/java/market/domain/Cart.java @@ -0,0 +1,194 @@ +package market.domain; + +import org.hibernate.annotations.GenericGenerator; +import org.hibernate.annotations.Parameter; + +import com.fasterxml.jackson.annotation.JsonManagedReference; +import com.fasterxml.jackson.annotation.JsonPropertyOrder; + +import javax.persistence.*; +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Objects; + +/** + * Cart of the {@link UserAccount}. + */ + +/* + * Tests: incluido orden de columnas para serializar en Json + */ +@Entity +@Table(name = "cart") +@JsonPropertyOrder({"id", "userAccount", "cartItems", "deliveryIncluded", "itemsCost", "itemsCount", "empty"}) +public class Cart implements Serializable { + private static final long serialVersionUID = -6884843696895527904L; + + @Id + @Column(name = "id", unique = true, nullable = false) + @GeneratedValue(generator = "gen") + @GenericGenerator(name = "gen", strategy = "foreign", parameters = @Parameter(name = "property", value = "userAccount")) + private long id; + + // Test: anyadido para evitar recursividad en json con UserAccount + @JsonManagedReference + @OneToOne + @PrimaryKeyJoinColumn + private UserAccount userAccount; + + @OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL, orphanRemoval = true, + targetEntity = CartItem.class, mappedBy = "cart") + private List cartItems = new ArrayList<>(0); + + @Column(name = "delivery_included", nullable = false) + private boolean deliveryIncluded = true; + + public Cart() { + this(null); + } + + public Cart(UserAccount userAccount) { + this.userAccount = userAccount; + } + + public boolean isEmpty() { + return cartItems.isEmpty(); + } + + public CartItem update(Product product, int newQuantity) { + if (product == null) + return null; + + CartItem updatedItem = null; + if (newQuantity > 0) { + CartItem existingItem = findItem(product.getId()); + if (existingItem == null) { + CartItem newItem = new CartItem(this, product, newQuantity); + cartItems.add(newItem); + updatedItem = newItem; + } else { + existingItem.setQuantity(newQuantity); + updatedItem = existingItem; + } + } else { + removeItem(product.getId()); + } + return updatedItem; + } + + private void removeItem(long productId) { + cartItems.removeIf(item -> item.getProduct().getId() == productId); + } + + private CartItem findItem(long productId) { + for (CartItem existingItem : cartItems) { + if (existingItem.getProduct().getId() == productId) + return existingItem; + } + return null; + } + + private double calculateItemsCost() { + return cartItems.stream() + .mapToDouble(CartItem::calculateCost) + .sum(); + } + + public void clear() { + cartItems.clear(); + } + + public long getId() { + return id; + } + + public void setId(long id) { + this.id = id; + } + + public UserAccount getUserAccount() { + return userAccount; + } + + public void setUserAccount(UserAccount userAccount) { + this.userAccount = userAccount; + } + + public List getCartItems() { + return Collections.unmodifiableList(cartItems); + } + + public boolean isDeliveryIncluded() { + return deliveryIncluded; + } + + public void setDeliveryIncluded(boolean deliveryIncluded) { + this.deliveryIncluded = deliveryIncluded; + } + + public int getItemsCount() { + return cartItems.size(); + } + + public double getItemsCost() { + return calculateItemsCost(); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + Cart cart = (Cart) o; + return deliveryIncluded == cart.deliveryIncluded && + Objects.equals(id, cart.id) && + Objects.equals(cartItems, cart.cartItems); + } + + @Override + public int hashCode() { + return Objects.hash(id, cartItems, deliveryIncluded); + } + + public static class Builder { + private long id; + private UserAccount userAccount; + private List cartItems = new ArrayList<>(0); + private boolean deliveryIncluded = true; + + public Builder() { + } + + public Builder(Cart cart) { + id = cart.id; + userAccount = cart.userAccount; + cartItems = cart.cartItems; + deliveryIncluded = cart.deliveryIncluded; + } + + public Cart build() { + Cart cart = new Cart(); + cart.id = id; + cart.userAccount = userAccount; + cart.cartItems = cartItems; + cart.deliveryIncluded = deliveryIncluded; + return cart; + } + + public Builder setId(long id) { + this.id = id; + return this; + } + + public Builder setUserAccount(UserAccount userAccount) { + this.userAccount = userAccount; + return this; + } + + public Builder setDeliveryIncluded(boolean deliveryIncluded) { + this.deliveryIncluded = deliveryIncluded; + return this; + } + } +} diff --git a/sut-market/market-core/src/main/java/market/domain/CartItem.java b/sut-market/market-core/src/main/java/market/domain/CartItem.java new file mode 100644 index 0000000..2c4b2cd --- /dev/null +++ b/sut-market/market-core/src/main/java/market/domain/CartItem.java @@ -0,0 +1,82 @@ +package market.domain; + +import javax.persistence.Column; +import javax.persistence.EmbeddedId; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; +import javax.persistence.MapsId; +import javax.persistence.Table; +import java.io.Serializable; +import java.util.Objects; + +@Entity +@Table(name = "cart_item") +public class CartItem implements Serializable { + private static final long serialVersionUID = -3995571478236070123L; + + @EmbeddedId + private CartItemId pk = new CartItemId(); + + @MapsId("cartId") + @JoinColumn(name = "cart_id", referencedColumnName = "id") + @ManyToOne(optional = false, fetch = FetchType.LAZY) + private Cart cart; + + @MapsId("productId") + @JoinColumn(name = "product_id", referencedColumnName = "id") + @ManyToOne(optional = false, fetch = FetchType.EAGER) + private Product product; + + @Column(name = "quantity") + private int quantity; + + public CartItem() { + } + + public CartItem(Cart cart, Product product, int quantity) { + this.pk = new CartItemId(cart.getId(), product.getId()); + this.cart = cart; + this.product = product; + this.quantity = quantity; + } + + public double calculateCost() { + return quantity * product.getPrice(); + } + + public int getQuantity() { + return quantity; + } + + public void setQuantity(int quantity) { + this.quantity = quantity; + } + + public Product getProduct() { + return product; + } + + public void setProduct(Product product) { + this.product = product; + pk.setProduct(product.getId()); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + CartItem that = (CartItem) o; + return Objects.equals(pk, that.pk); + } + + @Override + public int hashCode() { + return (pk != null ? pk.hashCode() : 0); + } +} diff --git a/sut-market/market-core/src/main/java/market/domain/CartItemId.java b/sut-market/market-core/src/main/java/market/domain/CartItemId.java new file mode 100644 index 0000000..daae66f --- /dev/null +++ b/sut-market/market-core/src/main/java/market/domain/CartItemId.java @@ -0,0 +1,54 @@ +package market.domain; + +import javax.persistence.Embeddable; +import java.io.Serializable; +import java.util.Objects; + +/** + * Primary key of the {@link CartItem}. + */ +@Embeddable +public class CartItemId implements Serializable { + private static final long serialVersionUID = -1255025293895062037L; + + private long cartId; + private long productId; + + public CartItemId() { + } + + public CartItemId(long cartId, long productId) { + this.cartId = cartId; + this.productId = productId; + } + + public long getCart() { + return cartId; + } + + public void setCart(long orderId) { + this.cartId = orderId; + } + + public long getProduct() { + return productId; + } + + public void setProduct(long productId) { + this.productId = productId; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + CartItemId that = (CartItemId) o; + return cartId == that.cartId && + productId == that.productId; + } + + @Override + public int hashCode() { + return Objects.hash(cartId, productId); + } +} diff --git a/sut-market/market-core/src/main/java/market/domain/Contacts.java b/sut-market/market-core/src/main/java/market/domain/Contacts.java new file mode 100644 index 0000000..e6a8977 --- /dev/null +++ b/sut-market/market-core/src/main/java/market/domain/Contacts.java @@ -0,0 +1,160 @@ +package market.domain; + +import org.hibernate.annotations.GenericGenerator; +import org.hibernate.annotations.Parameter; + +import com.fasterxml.jackson.annotation.JsonManagedReference; + +import javax.persistence.*; +import java.io.Serializable; +import java.util.Objects; + +/** + * Contacts of the {@link UserAccount}. + */ +@Entity +@Table(name = "contacts") +public class Contacts implements Serializable { + private static final long serialVersionUID = 582080671801480110L; + + @Id + @Column(name = "id", unique = true, nullable = false) + @GeneratedValue(generator = "gen") + @GenericGenerator(name = "gen", strategy = "foreign", parameters = @Parameter(name = "property", value = "userAccount")) + private Long id; + + // Test: anyadido para evitar recursividad en json con UserAccount + @JsonManagedReference + @OneToOne + @PrimaryKeyJoinColumn + private UserAccount userAccount; // todo: change to 'String userLogin' + + @Column(name = "phone", nullable = false) + private String phone; + + @Column(name = "address", nullable = false) + private String address; + + @Column(name = "city_region", nullable = false) + private String cityAndRegion; + + public Contacts() { + } + + public Contacts(UserAccount userAccount, String phone, String address/*, String cityAndRegion*/) { + this.userAccount = userAccount; + this.phone = phone; + this.address = address; + this.cityAndRegion = "13"; + } + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public UserAccount getUserAccount() { + return userAccount; + } + + public void setUserAccount(UserAccount userAccount) { + this.userAccount = userAccount; + } + + public String getPhone() { + return phone; + } + + public void setPhone(String phone) { + this.phone = phone; + } + + public String getAddress() { + return address; + } + + public void setAddress(String address) { + this.address = address; + } + + public String getCityAndRegion() { + return cityAndRegion; + } + + public void setCityAndRegion(String cityAndRegion) { + this.cityAndRegion = cityAndRegion; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + Contacts contacts = (Contacts) o; + return Objects.equals(id, contacts.id) && + Objects.equals(phone, contacts.phone) && + Objects.equals(address, contacts.address) && + Objects.equals(cityAndRegion, contacts.cityAndRegion); + } + + @Override + public int hashCode() { + return Objects.hash(id, phone, address, cityAndRegion); + } + + public static class Builder { + private Long id; + private UserAccount userAccount; + private String phone; + private String address; + private String cityAndRegion; + + public Builder() { + } + + public Builder(Contacts contacts) { + id = contacts.id; + userAccount = contacts.userAccount; + phone = contacts.phone; + address = contacts.address; + cityAndRegion = contacts.cityAndRegion; + } + + public Contacts build() { + Contacts contacts = new Contacts(); + contacts.id = id; + contacts.userAccount = userAccount; + contacts.phone = phone; + contacts.address = address; + contacts.cityAndRegion = cityAndRegion; + return contacts; + } + + public Builder setId(Long id) { + this.id = id; + return this; + } + + public Builder setUserAccount(UserAccount userAccount) { + this.userAccount = userAccount; + return this; + } + + public Builder setPhone(String phone) { + this.phone = phone; + return this; + } + + public Builder setAddress(String address) { + this.address = address; + return this; + } + + public Builder setCityAndRegion(String cityAndRegion) { + this.cityAndRegion = cityAndRegion; + return this; + } + } +} diff --git a/sut-market/market-core/src/main/java/market/domain/Distillery.java b/sut-market/market-core/src/main/java/market/domain/Distillery.java new file mode 100644 index 0000000..eeba07d --- /dev/null +++ b/sut-market/market-core/src/main/java/market/domain/Distillery.java @@ -0,0 +1,132 @@ +package market.domain; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; +import javax.persistence.Table; +import javax.validation.constraints.NotEmpty; +import javax.validation.constraints.Pattern; +import java.io.Serializable; +import java.util.Objects; + +@Entity +@Table(name = "distillery") +public class Distillery implements Serializable { + private static final long serialVersionUID = -1491932412037172392L; + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "id", insertable = false, updatable = false, nullable = false) + private Long id; + + @Column(name = "title", nullable = false) + @NotEmpty + @Pattern(regexp = "^[^#$%^*()']*$") + private String title; + + @ManyToOne(fetch = FetchType.EAGER) + @JoinColumn(name = "region_id", nullable = false) + private Region region; + + @Column(name = "description") + private String description; + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public Region getRegion() { + return region; + } + + public void setRegion(Region region) { + this.region = region; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + Distillery that = (Distillery) o; + return Objects.equals(id, that.id) && + Objects.equals(title, that.title) && + Objects.equals(region, that.region) && + Objects.equals(description, that.description); + } + + @Override + public int hashCode() { + return Objects.hash(id, title, region, description); + } + + public static class Builder { + private Long id; + private String title; + private Region region; + private String description; + + public Builder() { + } + + public Builder(Distillery source) { + id = source.id; + title = source.title; + region = source.region; + description = source.description; + } + + public Distillery build() { + Distillery distillery = new Distillery(); + distillery.id = id; + distillery.title = title; + distillery.region = region; + distillery.description = description; + return distillery; + } + + public Builder setId(Long id) { + this.id = id; + return this; + } + + public Builder setTitle(String title) { + this.title = title; + return this; + } + + public Builder setRegion(Region region) { + this.region = region; + return this; + } + + public Builder setDescription(String description) { + this.description = description; + return this; + } + } +} diff --git a/sut-market/market-core/src/main/java/market/domain/Order.java b/sut-market/market-core/src/main/java/market/domain/Order.java new file mode 100644 index 0000000..c6750c6 --- /dev/null +++ b/sut-market/market-core/src/main/java/market/domain/Order.java @@ -0,0 +1,229 @@ +package market.domain; + +import org.hibernate.annotations.GenericGenerator; +import org.hibernate.annotations.Parameter; + +import com.fasterxml.jackson.annotation.JsonPropertyOrder; + +import javax.persistence.*; +import java.io.Serializable; +import java.util.Date; +import java.util.HashSet; +import java.util.Objects; +import java.util.Set; + +import static javax.persistence.TemporalType.TIMESTAMP; + +/** + * Order of the {@link UserAccount}. + */ + +/* + * Tests: incluido orden de columnas para serializar en Json + */ +@Entity +@Table(name = "customer_order") +@JsonPropertyOrder({"id", "userAccount", "orderedProducts", "bill", "productsCost", "dateCreated", "deliveryCost", "deliveryIncluded", "executed"}) +public class Order implements Serializable { + private static final long serialVersionUID = -8328584058042877489L; + + @Id + @Column(name = "id", insertable = false, updatable = false, nullable = false) + @GeneratedValue(generator = "gen") + @GenericGenerator(name = "gen", strategy = "foreign", parameters = @Parameter(name = "property", value = "bill")) + private Long id; + + @ManyToOne(fetch = FetchType.EAGER) + @JoinColumn(name = "user_account_id") + private UserAccount userAccount; + + @OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER, + targetEntity = OrderedProduct.class, mappedBy = "order") +// @OneToMany(fetch = FetchType.LAZY, mappedBy ="pk.order", cascade = +// {CascadeType.PERSIST, CascadeType.MERGE}) +// @Cascade({CascadeType.SAVE_UPDATE, CascadeType.DELETE_ORPHAN}) + private Set orderedProducts = new HashSet<>(0); + + @OneToOne(mappedBy = "order", cascade = CascadeType.ALL, fetch = FetchType.EAGER) + private Bill bill; + + @Column(name = "products_cost", nullable = false) + private double productsCost; + + @Column(name = "date_created", nullable = false) + @Temporal(TIMESTAMP) + private Date dateCreated; + + @Column(name = "delivery_cost") + private int deliveryCost; + + @Column(name = "delivery_included", nullable = false) + private boolean deliveryIncluded; + + @Column(name = "executed", nullable = false) + private boolean executed; + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public double getProductsCost() { + return productsCost; + } + + public void setProductsCost(double amount) { + this.productsCost = amount; + } + + public Date getDateCreated() { + return dateCreated; + } + + public void setDateCreated(Date name) { + this.dateCreated = name; + } + + public UserAccount getUserAccount() { + return userAccount; + } + + public void setUserAccount(UserAccount userAccount) { + this.userAccount = userAccount; + } + + public Set getOrderedProducts() { + return orderedProducts; + } + + public void setOrderedProducts(Set products) { + this.orderedProducts = products; + } + + public int getDeliveryCost() { + return deliveryCost; + } + + public void setDeliveryCost(int deliveryСost) { + this.deliveryCost = deliveryСost; + } + + public boolean isDeliveryIncluded() { + return deliveryIncluded; + } + + public void setDeliveryIncluded(boolean deliveryIncluded) { + this.deliveryIncluded = deliveryIncluded; + } + + public Bill getBill() { + return bill; + } + + public void setBill(Bill bill) { + this.bill = bill; + } + + public boolean isExecuted() { + return executed; + } + + public void setExecuted(boolean executed) { + this.executed = executed; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + Order order = (Order) o; + return Double.compare(order.productsCost, productsCost) == 0 && + deliveryCost == order.deliveryCost && + deliveryIncluded == order.deliveryIncluded && + executed == order.executed && + Objects.equals(id, order.id) && + Objects.equals(userAccount, order.userAccount) && + Objects.equals(orderedProducts, order.orderedProducts) && + Objects.equals(bill, order.bill) && + Objects.equals(dateCreated, order.dateCreated); + } + + @Override + public int hashCode() { + return Objects.hash(id, userAccount, orderedProducts, bill, productsCost, dateCreated, deliveryCost, deliveryIncluded, executed); + } + + public static class Builder { + private Long id; + private UserAccount userAccount; + private Set orderedProducts = new HashSet<>(); + private Bill bill; + private double productsCost; + private Date dateCreated; + private int deliveryCost; + private boolean deliveryIncluded; + private boolean executed; + + public Order build() { + Order order = new Order(); + order.id = id; + order.userAccount = userAccount; + order.orderedProducts = orderedProducts; + order.bill = bill; + order.productsCost = productsCost; + order.dateCreated = dateCreated; + order.deliveryCost = deliveryCost; + order.deliveryIncluded = deliveryIncluded; + order.executed = executed; + return order; + } + + public Builder setId(Long id) { + this.id = id; + return this; + } + + public Builder setUserAccount(UserAccount userAccount) { + this.userAccount = userAccount; + return this; + } + + public Builder setOrderedProducts(Set orderedProducts) { + this.orderedProducts = orderedProducts; + return this; + } + + public Builder setBill(Bill bill) { + this.bill = bill; + return this; + } + + public Builder setProductsCost(double productsCost) { + this.productsCost = productsCost; + return this; + } + + public Builder setDateCreated(Date dateCreated) { + this.dateCreated = dateCreated; + return this; + } + + public Builder setDeliveryCost(int deliveryCost) { + this.deliveryCost = deliveryCost; + return this; + } + + public Builder setDeliveryIncluded(boolean deliveryIncluded) { + this.deliveryIncluded = deliveryIncluded; + return this; + } + + public Builder setExecuted(boolean executed) { + this.executed = executed; + return this; + } + } +} diff --git a/sut-market/market-core/src/main/java/market/domain/OrderedProduct.java b/sut-market/market-core/src/main/java/market/domain/OrderedProduct.java new file mode 100644 index 0000000..447165d --- /dev/null +++ b/sut-market/market-core/src/main/java/market/domain/OrderedProduct.java @@ -0,0 +1,119 @@ +package market.domain; + +import javax.persistence.*; + +import com.fasterxml.jackson.annotation.JsonBackReference; +import com.fasterxml.jackson.annotation.JsonPropertyOrder; + +import java.io.Serializable; +import java.util.Objects; + +/** + * {@link Product} of the {@link Order}. + */ + +/* + * Tests: incluido orden de columnas para serializar en Json + */ +@Entity +@Table(name = "ordered_product") +@JsonPropertyOrder({"pk", "order", "product", "quantity"}) +public class OrderedProduct implements Serializable { + private static final long serialVersionUID = -2055528467252485472L; + + @EmbeddedId + private OrderedProductId pk = new OrderedProductId(); + + // Test: anyadido para evitar recursividad en json con Order + @JsonBackReference + @MapsId("orderId") + @JoinColumn(name = "customer_order_id", referencedColumnName = "id") + @ManyToOne(optional = false, fetch = FetchType.LAZY) + private Order order; + + @MapsId("productId") + @JoinColumn(name = "product_id", referencedColumnName = "id") + @ManyToOne(optional = false, fetch = FetchType.EAGER) + private Product product; + + @Column(name = "quantity") + private int quantity; + + public OrderedProductId getPk() { + return pk; + } + + public void setPk(OrderedProductId pk) { + this.pk = pk; + } + + public int getQuantity() { + return quantity; + } + + public void setQuantity(int quantity) { + this.quantity = quantity; + } + + public Order getOrder() { + return order; + } + + public void setOrder(Order order) { + this.order = order; + getPk().setCustomerOrder(order.getId()); + } + + public Product getProduct() { + return product; + } + + public void setProduct(Product product) { + this.product = product; + getPk().setProduct(product.getId()); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + OrderedProduct that = (OrderedProduct) o; + return quantity == that.quantity && + Objects.equals(order.getId(), that.order.getId()) && + Objects.equals(product.getId(), that.product.getId()); + } + + @Override + public int hashCode() { + return Objects.hash(order.getId(), product.getId(), quantity); + } + + public static class Builder { + private Order order; + private Product product; + private int quantity; + + public OrderedProduct build() { + OrderedProduct orderedProduct = new OrderedProduct(); + orderedProduct.setOrder(order); + orderedProduct.setProduct(product); + orderedProduct.setQuantity(quantity); + return orderedProduct; + } + + public Builder setOrder(Order order) { + this.order = order; + return this; + } + + public Builder setProduct(Product product) { + this.product = product; + return this; + } + + public Builder setQuantity(int quantity) { + this.quantity = quantity; + return this; + } + } +} diff --git a/sut-market/market-core/src/main/java/market/domain/OrderedProductId.java b/sut-market/market-core/src/main/java/market/domain/OrderedProductId.java new file mode 100644 index 0000000..beac55d --- /dev/null +++ b/sut-market/market-core/src/main/java/market/domain/OrderedProductId.java @@ -0,0 +1,60 @@ +package market.domain; + +import javax.persistence.*; + +import com.fasterxml.jackson.annotation.JsonPropertyOrder; + +import java.io.*; +import java.util.*; + +/** + * Primary key of a {@link Product} inside the {@link Order}. + */ + +/* + * Tests: incluido orden de columnas para serializar en Json + */ +@JsonPropertyOrder({"product","customerOrder"}) + +@Embeddable +public class OrderedProductId implements Serializable { + private static final long serialVersionUID = 5368453186150127449L; + + private Long orderId; + private Long productId; + + public Long getCustomerOrder() { + return orderId; + } + + public void setCustomerOrder(Long orderId) { + this.orderId = orderId; + } + + public Long getProduct() { + return productId; + } + + public void setProduct(Long productId) { + this.productId = productId; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + OrderedProductId that = (OrderedProductId) o; + + if (!Objects.equals(orderId, that.orderId)) return false; + return Objects.equals(productId, that.productId); + } + + @Override + public int hashCode() { + int result; + result = (orderId != null ? orderId.hashCode() : 0); + result = 31 * result + (productId != null ? productId.hashCode() : 0); + return result; + } +} diff --git a/sut-market/market-core/src/main/java/market/domain/Product.java b/sut-market/market-core/src/main/java/market/domain/Product.java new file mode 100644 index 0000000..2a9618a --- /dev/null +++ b/sut-market/market-core/src/main/java/market/domain/Product.java @@ -0,0 +1,241 @@ +package market.domain; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; +import javax.persistence.Table; +import javax.validation.constraints.Max; +import javax.validation.constraints.Min; +import javax.validation.constraints.NotEmpty; +import javax.validation.constraints.NotNull; +import javax.validation.constraints.Pattern; +import java.io.Serializable; +import java.util.Objects; + +@Entity +@Table(name = "product") +public class Product implements Serializable { + private static final long serialVersionUID = -5637368176838137416L; + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "id", insertable = false, updatable = false, nullable = false) + private Long id; + + @ManyToOne(fetch = FetchType.EAGER) + @JoinColumn(name = "distillery_id", nullable = false) + private Distillery distillery; + + @Column(name = "name", nullable = false) + @NotEmpty + @Pattern(regexp = "^[^#$%^&*()']*$") + private String name; + + @Column(name = "price", nullable = false) + @NotNull + private Double price; + + @Column(name = "description") + private String description; + + @Column(name = "volume") + @NotNull + private Integer volume; + + @Column(name = "alcohol") + @NotNull + @Min(value = 1) + @Max(value = 96) + private Float alcohol; + + @Column(name = "age") + @Max(value = 2000) + private Integer age; + + @Column(name = "available", nullable = false) + private boolean available = true; + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public @NotNull Double getPrice() { + return price; + } + + public void setPrice(@NotNull Double price) { + this.price = price; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public Integer getVolume() { + return volume; + } + + public void setVolume(Integer volume) { + this.volume = volume; + } + + public Float getAlcohol() { + return alcohol; + } + + public void setAlcohol(Float alcohol) { + this.alcohol = alcohol; + } + + public Integer getAge() { + return age; + } + + public void setAge(Integer age) { + this.age = age; + } + + public Distillery getDistillery() { + return distillery; + } + + public void setDistillery(Distillery distillery) { + this.distillery = distillery; + } + + public boolean isAvailable() { + return available; + } + + public void setAvailable(boolean available) { + this.available = available; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + Product product = (Product) o; + return id == product.id && + available == product.available && + Objects.equals(distillery, product.distillery) && + Objects.equals(name, product.name) && + Objects.equals(price, product.price) && + Objects.equals(description, product.description) && + Objects.equals(volume, product.volume) && + Objects.equals(alcohol, product.alcohol) && + Objects.equals(age, product.age); + } + + @Override + public int hashCode() { + return Objects.hash(id, distillery, name, price, description, volume, alcohol, age, available); + } + + public static class Builder { + private Long id; + private Distillery distillery; + private String name; + private Double price; + private String description; + private Integer volume; + private Float alcohol; + private Integer age; + private boolean available = true; + + public Builder() { + } + + public Builder(Product product) { + id = product.id; + distillery = product.distillery; + name = product.name; + price = product.price; + description = product.description; + volume = product.volume; + alcohol = product.alcohol; + age = product.age; + available = product.available; + } + + public Product build() { + Product product = new Product(); + product.id = id; + product.distillery = distillery; + product.name = name; + product.price = price; + product.description = description; + product.volume = volume; + product.alcohol = alcohol; + product.age = age; + product.available = available; + return product; + } + + public Builder setId(Long id) { + this.id = id; + return this; + } + + public Builder setDistillery(Distillery distillery) { + this.distillery = distillery; + return this; + } + + public Builder setName(String name) { + this.name = name; + return this; + } + + public Builder setPrice(@NotNull Double price) { + this.price = price; + return this; + } + + public Builder setDescription(String description) { + this.description = description; + return this; + } + + public Builder setVolume(Integer volume) { + this.volume = volume; + return this; + } + + public Builder setAlcohol(Float alcohol) { + this.alcohol = alcohol; + return this; + } + + public Builder setAge(Integer age) { + this.age = age; + return this; + } + + public Builder setAvailable(boolean available) { + this.available = available; + return this; + } + } +} diff --git a/sut-market/market-core/src/main/java/market/domain/Region.java b/sut-market/market-core/src/main/java/market/domain/Region.java new file mode 100644 index 0000000..01cfe88 --- /dev/null +++ b/sut-market/market-core/src/main/java/market/domain/Region.java @@ -0,0 +1,158 @@ +package market.domain; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.Table; +import javax.validation.constraints.NotEmpty; +import javax.validation.constraints.Pattern; +import java.io.Serializable; +import java.util.Objects; + +@Entity +@Table(name = "region") +public class Region implements Serializable { + public static final Region NULL; + private static final long serialVersionUID = 5413261502059862627L; + + static { + NULL = new Region.Builder() + .setId(0L) + .setName("null region") + .build(); + } + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "id", insertable = false, updatable = false, nullable = false) + private Long id; + + @Column(name = "name", nullable = false) + @NotEmpty + @Pattern(regexp = "^[^#$%^&*()']*$") + private String name; + + @Column(name = "subtitle") + @Pattern(regexp = "^[^#$%^*()']*$") + private String subtitle; + + @Column(name = "description") + private String description; + + @Column(name = "color") + @Pattern(regexp = "^(a-z|A-Z|0-9-)*[^#$%^&*()']*$") + private String color; + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getSubtitle() { + return subtitle; + } + + public void setSubtitle(String subtitle) { + this.subtitle = subtitle; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public String getColor() { + return color; + } + + public void setColor(String color) { + this.color = color; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + Region region = (Region) o; + return Objects.equals(id, region.id) && + Objects.equals(name, region.name) && + Objects.equals(subtitle, region.subtitle) && + Objects.equals(description, region.description) && + Objects.equals(color, region.color); + } + + @Override + public int hashCode() { + return Objects.hash(id, name, subtitle, description, color); + } + + public static class Builder { + private Long id; + private String name; + private String subtitle; + private String description; + private String color; + + public Builder() { + } + + public Builder(Region region) { + id = region.id; + name = region.name; + subtitle = region.subtitle; + description = region.description; + color = region.color; + } + + public Region build() { + Region region = new Region(); + region.id = id; + region.name = name; + region.subtitle = subtitle; + region.description = description; + region.color = color; + return region; + } + + public Builder setId(Long id) { + this.id = id; + return this; + } + + public Builder setName(String name) { + this.name = name; + return this; + } + + public Builder setSubtitle(String subtitle) { + this.subtitle = subtitle; + return this; + } + + public Builder setDescription(String description) { + this.description = description; + return this; + } + + public Builder setColor(String color) { + this.color = color; + return this; + } + } +} diff --git a/sut-market/market-core/src/main/java/market/domain/Role.java b/sut-market/market-core/src/main/java/market/domain/Role.java new file mode 100644 index 0000000..e540008 --- /dev/null +++ b/sut-market/market-core/src/main/java/market/domain/Role.java @@ -0,0 +1,60 @@ +package market.domain; + +import javax.persistence.*; + +import com.fasterxml.jackson.annotation.JsonBackReference; + +import java.io.*; +import java.util.*; + +/** + * Role of a {@link UserAccount}. + */ +@Entity +@Table(name = "role") +public class Role implements Serializable { + private static final long serialVersionUID = -1387354647838847103L; + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "id", insertable = false, updatable = false, nullable = false) + private Long id; + + @Column(name = "title", nullable = false) + private String title; + + // Test: anyadido para evitar recursividad en json con UserAccount + @JsonBackReference + @ManyToMany(mappedBy = "roles") + private Set users = new HashSet<>(); + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public Set getUsers() { + return users; + } + + public void setUsers(Set users) { + this.users = users; + } + + enum Roles { + ROLE_ADMIN, + ROLE_STAFF, + ROLE_USER + } +} diff --git a/sut-market/market-core/src/main/java/market/domain/UserAccount.java b/sut-market/market-core/src/main/java/market/domain/UserAccount.java new file mode 100644 index 0000000..d45486e --- /dev/null +++ b/sut-market/market-core/src/main/java/market/domain/UserAccount.java @@ -0,0 +1,218 @@ +package market.domain; + +import javax.persistence.*; + +import com.fasterxml.jackson.annotation.JsonBackReference; + +import java.io.Serializable; +import java.util.HashSet; +import java.util.Objects; +import java.util.Set; + +@Entity +@Table(name = "user_account") +public class UserAccount implements Serializable { + private static final long serialVersionUID = -8278943418573848966L; + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "id", insertable = false, updatable = false, nullable = false) + private Long id; + + @Column(name = "email", nullable = false) + private String email; + + @Column(name = "password", nullable = false) + private String password; + + @Column(name = "name", nullable = false) + private String name; + + @Column(name = "active", nullable = false) + private boolean active; + + // Test: anyadido para evitar recursividad en json con Contacts + @JsonBackReference + @OneToOne(mappedBy = "userAccount", cascade = CascadeType.ALL) + private Contacts contacts; + + + // Test: anyadido para evitar recursividad en json con Cart + @JsonBackReference + @OneToOne(mappedBy = "userAccount", cascade = CascadeType.ALL) + private Cart cart; + + @ManyToMany(fetch = FetchType.EAGER, cascade = {CascadeType.ALL}) + @JoinTable(name = "user_role", + joinColumns = { + @JoinColumn(name = "user_id")}, + inverseJoinColumns = { + @JoinColumn(name = "role_id")}) + private Set roles = new HashSet<>(); + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getEmail() { + return email; + } + + public void setEmail(String email) { + this.email = email; + } + + public String getPassword() { + return password; + } + + public void setPassword(String password) { + this.password = password; + } + + public boolean isActive() { + return active; + } + + public void setActive(boolean active) { + this.active = active; + } + + public Contacts getContacts() { + return contacts; + } + + public void setContacts(Contacts contacts) { + this.contacts = contacts; + } + + public Cart getCart() { + return cart; + } + + public void setCart(Cart cart) { + this.cart = cart; + } + + public Set getRoles() { + return roles; + } + + public void setRoles(Set roles) { + this.roles = roles; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + UserAccount account = (UserAccount) o; + return active == account.active && + Objects.equals(id, account.id) && + Objects.equals(email, account.email) && + Objects.equals(password, account.password) && + Objects.equals(name, account.name) && + Objects.equals(contacts, account.contacts) && + Objects.equals(cart, account.cart) && + Objects.equals(roles, account.roles); + } + + @Override + public int hashCode() { + return Objects.hash(id, email, password, name, active, contacts, cart, roles); + } + + public static class Builder { + private Long id; + private String email; + private String password; + private String name; + private boolean active; + private Contacts contacts; + private Cart cart; + private Set roles = new HashSet<>(); + + public Builder() { + } + + public Builder(UserAccount account) { + this.id = account.id; + this.email = account.email; + this.password = account.password; + this.name = account.name; + this.active = account.active; + this.contacts = account.contacts; + this.cart = account.cart; + this.roles = account.roles; + } + + public UserAccount build() { + UserAccount account = new UserAccount(); + account.id = id; + account.email = email; + account.password = password; + account.name = name; + account.active = active; + account.contacts = contacts; + account.cart = cart; + account.roles = roles; + return account; + } + + public Builder setId(Long id) { + this.id = id; + return this; + } + + public Builder setEmail(String email) { + this.email = email; + return this; + } + + public Builder setPassword(String password) { + this.password = password; + return this; + } + + public Builder setName(String name) { + this.name = name; + return this; + } + + public Builder setActive(boolean active) { + this.active = active; + return this; + } + + public Builder setContacts(Contacts contacts) { + this.contacts = contacts; + return this; + } + + public Builder setCart(Cart cart) { + this.cart = cart; + return this; + } + + public Builder setRoles(Set roles) { + this.roles = roles; + return this; + } + + public Long getId() { + return id; + } + } +} diff --git a/sut-market/market-core/src/main/java/market/dto/BillDTO.java b/sut-market/market-core/src/main/java/market/dto/BillDTO.java new file mode 100644 index 0000000..98503c1 --- /dev/null +++ b/sut-market/market-core/src/main/java/market/dto/BillDTO.java @@ -0,0 +1,80 @@ +package market.dto; + +import org.springframework.hateoas.RepresentationModel; + +import java.util.Objects; + +public class BillDTO extends RepresentationModel { + private Long id; + private int number; + private double totalCost; + private boolean payed; + private String ccNumber; + private String dateCreated; + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public int getNumber() { + return number; + } + + public void setNumber(int number) { + this.number = number; + } + + public double getTotalCost() { + return totalCost; + } + + public void setTotalCost(double totalCost) { + this.totalCost = totalCost; + } + + public boolean isPayed() { + return payed; + } + + public void setPayed(boolean payed) { + this.payed = payed; + } + + public String getCcNumber() { + return ccNumber; + } + + public void setCcNumber(String ccNumber) { + this.ccNumber = ccNumber; + } + + public String getDateCreated() { + return dateCreated; + } + + public void setDateCreated(String dateCreated) { + this.dateCreated = dateCreated; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + BillDTO billDTO = (BillDTO) o; + return number == billDTO.number && + Double.compare(billDTO.totalCost, totalCost) == 0 && + payed == billDTO.payed && + Objects.equals(id, billDTO.id) && + Objects.equals(ccNumber, billDTO.ccNumber) && + Objects.equals(dateCreated, billDTO.dateCreated); + } + + @Override + public int hashCode() { + return Objects.hash(id, number, totalCost, payed, ccNumber, dateCreated); + } +} diff --git a/sut-market/market-core/src/main/java/market/dto/CartDTO.java b/sut-market/market-core/src/main/java/market/dto/CartDTO.java new file mode 100644 index 0000000..605aded --- /dev/null +++ b/sut-market/market-core/src/main/java/market/dto/CartDTO.java @@ -0,0 +1,119 @@ +package market.dto; + +import org.springframework.hateoas.RepresentationModel; + +import java.util.Collections; +import java.util.List; +import java.util.Objects; + +/** + * Адаптер корзины. + */ +public class CartDTO extends RepresentationModel { + + private String user; + private int totalItems; + private double productsCost; + private int deliveryCost; + private boolean deliveryIncluded; + private double totalCost; + private List cartItems = Collections.emptyList(); + + public boolean containsProductId(long targetProductId) { + return cartItems.stream() + .map(CartItemDTO::getProductId) + .anyMatch(id -> id == targetProductId); + } + + public boolean isEmpty() { + return cartItems.isEmpty(); + } + + public String getUser() { + return user; + } + + public void setUser(String user) { + this.user = user; + } + + public List getCartItems() { + return cartItems; + } + + public void setCartItems(List cartItems) { + this.cartItems = cartItems; + } + + public boolean isDeliveryIncluded() { + return deliveryIncluded; + } + + public void setDeliveryIncluded(boolean deliveryIncluded) { + this.deliveryIncluded = deliveryIncluded; + } + + public double getProductsCost() { + return productsCost; + } + + public void setProductsCost(double productsCost) { + this.productsCost = productsCost; + } + + public double getTotalCost() { + return totalCost; + } + + public void setTotalCost(double totalCost) { + this.totalCost = totalCost; + } + + public int getDeliveryCost() { + return deliveryCost; + } + + public void setDeliveryCost(int deliveryCost) { + this.deliveryCost = deliveryCost; + } + + public int getTotalItems() { + return totalItems; + } + + public void setTotalItems(int totalItems) { + this.totalItems = totalItems; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + CartDTO cartDTO = (CartDTO) o; + return totalItems == cartDTO.totalItems && + Double.compare(cartDTO.productsCost, productsCost) == 0 && + deliveryCost == cartDTO.deliveryCost && + deliveryIncluded == cartDTO.deliveryIncluded && + Double.compare(cartDTO.totalCost, totalCost) == 0 && + Objects.equals(user, cartDTO.user) && + Objects.equals(cartItems, cartDTO.cartItems); + } + + @Override + public int hashCode() { + return Objects.hash(user, cartItems, totalItems, productsCost, deliveryCost, deliveryIncluded, totalCost); + } + + @Override + public String toString() { + return "CartDTO{" + + "user='" + user + '\'' + + ", totalItems=" + totalItems + + ", productsCost=" + productsCost + + ", deliveryCost=" + deliveryCost + + ", deliveryIncluded=" + deliveryIncluded + + ", totalCost=" + totalCost + + ", cartItems=" + cartItems + + '}'; + } +} diff --git a/sut-market/market-core/src/main/java/market/dto/CartItemDTO.java b/sut-market/market-core/src/main/java/market/dto/CartItemDTO.java new file mode 100644 index 0000000..b740df1 --- /dev/null +++ b/sut-market/market-core/src/main/java/market/dto/CartItemDTO.java @@ -0,0 +1,59 @@ +package market.dto; + +import org.springframework.format.annotation.NumberFormat; +import org.springframework.hateoas.RepresentationModel; + +import javax.validation.constraints.Positive; +import java.util.Objects; + +/** + * Адаптер элемента корзины. + */ +public class CartItemDTO extends RepresentationModel { + + @Positive + @NumberFormat + private long productId; + + @Positive + @NumberFormat + private int quantity; + + public long getProductId() { + return productId; + } + + public void setProductId(Long productId) { + this.productId = productId; + } + + public int getQuantity() { + return quantity; + } + + public void setQuantity(int quantity) { + this.quantity = quantity; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + CartItemDTO that = (CartItemDTO) o; + return productId == that.productId && + quantity == that.quantity; + } + + @Override + public int hashCode() { + return Objects.hash(productId, quantity); + } + + @Override + public String toString() { + return "CartItemDTO{" + + "productId=" + productId + + ", quantity=" + quantity + + '}'; + } +} diff --git a/sut-market/market-core/src/main/java/market/dto/ContactsDTO.java b/sut-market/market-core/src/main/java/market/dto/ContactsDTO.java new file mode 100644 index 0000000..1bee8fd --- /dev/null +++ b/sut-market/market-core/src/main/java/market/dto/ContactsDTO.java @@ -0,0 +1,62 @@ +package market.dto; + +import org.springframework.hateoas.RepresentationModel; + +import javax.validation.constraints.NotEmpty; +import javax.validation.constraints.Pattern; +import javax.validation.constraints.Size; +import java.util.Objects; + +/** + * Контактные данные пользователя. + */ +public class ContactsDTO extends RepresentationModel { + + @NotEmpty + @Size(max = 20) + @Pattern(regexp = UserDTO.USER_PHONE_REGEX) + private String phone; + + @NotEmpty + @Size(max = 100) + @Pattern(regexp = UserDTO.USER_ADDRESS_REGEX) + private String address; + + public String getPhone() { + return phone; + } + + public void setPhone(String phone) { + this.phone = phone; + } + + public String getAddress() { + return address; + } + + public void setAddress(String address) { + this.address = address; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + ContactsDTO that = (ContactsDTO) o; + return Objects.equals(phone, that.phone) && + Objects.equals(address, that.address); + } + + @Override + public int hashCode() { + return Objects.hash(phone, address); + } + + @Override + public String toString() { + return "ContactsDTO{" + + "phone='" + phone + '\'' + + ", address='" + address + '\'' + + '}'; + } +} diff --git a/sut-market/market-core/src/main/java/market/dto/CreditCardDTO.java b/sut-market/market-core/src/main/java/market/dto/CreditCardDTO.java new file mode 100644 index 0000000..eaa57e3 --- /dev/null +++ b/sut-market/market-core/src/main/java/market/dto/CreditCardDTO.java @@ -0,0 +1,36 @@ +package market.dto; + +import org.hibernate.validator.constraints.CreditCardNumber; + +import javax.validation.constraints.NotEmpty; +import javax.validation.constraints.Pattern; + +public class CreditCardDTO { + + @NotEmpty + @CreditCardNumber + @Pattern(regexp = "\\b(?:\\d[ -]*?){13,16}\\b") + private String ccNumber; + + public CreditCardDTO() { + } + + public CreditCardDTO(String number) { + this.ccNumber = number; + } + + public String getCcNumber() { + return ccNumber; + } + + public void setCcNumber(String ccNumber) { + this.ccNumber = ccNumber; + } + + @Override + public String toString() { + return "CreditCardDTO{" + + "ccNumber='" + ccNumber + '\'' + + '}'; + } +} diff --git a/sut-market/market-core/src/main/java/market/dto/DistilleryDTO.java b/sut-market/market-core/src/main/java/market/dto/DistilleryDTO.java new file mode 100644 index 0000000..9a0faea --- /dev/null +++ b/sut-market/market-core/src/main/java/market/dto/DistilleryDTO.java @@ -0,0 +1,71 @@ +package market.dto; + +import org.springframework.hateoas.RepresentationModel; + +import java.util.Objects; + +public class DistilleryDTO extends RepresentationModel { + + private Long id; + private String title; + private String region; + private String description; + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public String getRegion() { + return region; + } + + public void setRegion(String region) { + this.region = region; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + DistilleryDTO that = (DistilleryDTO) o; + return Objects.equals(id, that.id) && + Objects.equals(title, that.title) && + Objects.equals(region, that.region) && + Objects.equals(description, that.description); + } + + @Override + public int hashCode() { + return Objects.hash(id, title, region, description); + } + + @Override + public String toString() { + return "DistilleryDTO{" + + "id=" + id + + ", title='" + title + '\'' + + ", region='" + region + '\'' + + ", description='" + description + '\'' + + '}'; + } +} diff --git a/sut-market/market-core/src/main/java/market/dto/OrderDTO.java b/sut-market/market-core/src/main/java/market/dto/OrderDTO.java new file mode 100644 index 0000000..e3b5e0b --- /dev/null +++ b/sut-market/market-core/src/main/java/market/dto/OrderDTO.java @@ -0,0 +1,130 @@ +package market.dto; + +import org.springframework.hateoas.RepresentationModel; + +import java.util.Date; +import java.util.Objects; + +public class OrderDTO extends RepresentationModel { + + private String userAccount; + private long id; + private int billNumber; + private Date dateCreated; + private double productsCost; + private int deliveryCost; + private boolean deliveryIncluded; + private double totalCost; + private boolean payed; + private boolean executed; + + public String getUserAccount() { + return userAccount; + } + + public void setUserAccount(String userAccount) { + this.userAccount = userAccount; + } + + public int getBillNumber() { + return billNumber; + } + + public void setBillNumber(int billNumber) { + this.billNumber = billNumber; + } + + public double getProductsCost() { + return productsCost; + } + + public void setProductsCost(double productsCost) { + this.productsCost = productsCost; + } + + public Date getDateCreated() { + return dateCreated; + } + + public void setDateCreated(Date dateCreated) { + this.dateCreated = dateCreated; + } + + public int getDeliveryCost() { + return deliveryCost; + } + + public void setDeliveryCost(int deliveryCost) { + this.deliveryCost = deliveryCost; + } + + public double getTotalCost() { + return totalCost; + } + + public void setTotalCost(double totalCost) { + this.totalCost = totalCost; + } + + public boolean isDeliveryIncluded() { + return deliveryIncluded; + } + + public void setDeliveryIncluded(boolean deliveryIncluded) { + this.deliveryIncluded = deliveryIncluded; + } + + public boolean isExecuted() { + return executed; + } + + public void setExecuted(boolean executed) { + this.executed = executed; + } + + public long getId() { + return id; + } + + public void setId(long id) { + this.id = id; + } + + public boolean isPayed() { + return payed; + } + + public void setPayed(boolean payed) { + this.payed = payed; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + OrderDTO orderDTO = (OrderDTO) o; + return id == orderDTO.id && + billNumber == orderDTO.billNumber && + Double.compare(orderDTO.productsCost, productsCost) == 0 && + deliveryCost == orderDTO.deliveryCost && + deliveryIncluded == orderDTO.deliveryIncluded && + Double.compare(orderDTO.totalCost, totalCost) == 0 && + payed == orderDTO.payed && + executed == orderDTO.executed && + Objects.equals(userAccount, orderDTO.userAccount) && + Objects.equals(dateCreated, orderDTO.dateCreated); + } + + @Override + public int hashCode() { + return Objects.hash(userAccount, id, billNumber, dateCreated, productsCost, deliveryCost, deliveryIncluded, totalCost, payed, executed); + } + + @Override + public String toString() { + return "OrderDTO{" + + "userAccount='" + userAccount + '\'' + + ", id=" + id + + '}'; + } +} diff --git a/sut-market/market-core/src/main/java/market/dto/OrderedProductDTO.java b/sut-market/market-core/src/main/java/market/dto/OrderedProductDTO.java new file mode 100644 index 0000000..7a87aff --- /dev/null +++ b/sut-market/market-core/src/main/java/market/dto/OrderedProductDTO.java @@ -0,0 +1,59 @@ +package market.dto; + +import org.springframework.hateoas.RepresentationModel; + +import java.util.Objects; + +public class OrderedProductDTO extends RepresentationModel { + private long orderId; + private int quantity; + private long productId; + + public long getOrderId() { + return orderId; + } + + public void setOrderId(long orderId) { + this.orderId = orderId; + } + + public int getQuantity() { + return quantity; + } + + public void setQuantity(int quantity) { + this.quantity = quantity; + } + + public long getProductId() { + return productId; + } + + public void setProductId(long productId) { + this.productId = productId; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + OrderedProductDTO that = (OrderedProductDTO) o; + return orderId == that.orderId && + quantity == that.quantity && + productId == that.productId; + } + + @Override + public int hashCode() { + return Objects.hash(orderId, quantity, productId); + } + + @Override + public String toString() { + return "OrderedProductDTO{" + + "orderId=" + orderId + + ", quantity=" + quantity + + ", productId=" + productId + + '}'; + } +} diff --git a/sut-market/market-core/src/main/java/market/dto/ProductDTO.java b/sut-market/market-core/src/main/java/market/dto/ProductDTO.java new file mode 100644 index 0000000..c4a4d88 --- /dev/null +++ b/sut-market/market-core/src/main/java/market/dto/ProductDTO.java @@ -0,0 +1,146 @@ +package market.dto; + +import org.springframework.hateoas.RepresentationModel; + +import javax.validation.constraints.Max; +import javax.validation.constraints.Min; +import javax.validation.constraints.NotEmpty; +import javax.validation.constraints.NotNull; +import javax.validation.constraints.Pattern; +import java.util.Objects; + +public class ProductDTO extends RepresentationModel { + + private Long productId; + + private String distillery; + + @NotEmpty + @Pattern(regexp = "^[^#$%^&*()']*$") + private String name; + + @NotNull + private Double price; + + @Max(value = 2000) + private Integer age; + + @NotNull + private Integer volume; + + @NotNull + @Min(value = 1) + @Max(value = 96) + private Float alcohol; + + private String description; + private boolean available; + + public Long getProductId() { + return productId; + } + + public void setProductId(Long productId) { + this.productId = productId; + } + + public String getDistillery() { + return distillery; + } + + public void setDistillery(String distillery) { + this.distillery = distillery; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public Double getPrice() { + return price; + } + + public void setPrice(@NotNull Double price) { + this.price = price; + } + + public Integer getAge() { + return age; + } + + public void setAge(Integer age) { + this.age = age; + } + + public Integer getVolume() { + return volume; + } + + public void setVolume(Integer volume) { + this.volume = volume; + } + + public Float getAlcohol() { + return alcohol; + } + + public void setAlcohol(Float alcohol) { + this.alcohol = alcohol; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public boolean isAvailable() { + return available; + } + + public void setAvailable(boolean available) { + this.available = available; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + ProductDTO that = (ProductDTO) o; + return available == that.available && + Objects.equals(productId, that.productId) && + Objects.equals(distillery, that.distillery) && + Objects.equals(name, that.name) && + Objects.equals(price, that.price) && + Objects.equals(age, that.age) && + Objects.equals(volume, that.volume) && + Objects.equals(alcohol, that.alcohol) && + Objects.equals(description, that.description); + } + + @Override + public int hashCode() { + return Objects.hash(productId, distillery, name, price, age, volume, alcohol, description, available); + } + + @Override + public String toString() { + return "ProductDTO{" + + "productId=" + productId + + ", distillery='" + distillery + '\'' + + ", name='" + name + '\'' + + ", price=" + price + + ", age=" + age + + ", volume=" + volume + + ", alcohol=" + alcohol + + ", description='" + description + '\'' + + ", available=" + available + + '}'; + } +} diff --git a/sut-market/market-core/src/main/java/market/dto/ProductPreviewDTO.java b/sut-market/market-core/src/main/java/market/dto/ProductPreviewDTO.java new file mode 100644 index 0000000..62435f3 --- /dev/null +++ b/sut-market/market-core/src/main/java/market/dto/ProductPreviewDTO.java @@ -0,0 +1,66 @@ +package market.dto; + +import org.springframework.hateoas.RepresentationModel; + +/** + * Адаптер товара. + */ +public class ProductPreviewDTO extends RepresentationModel { + + private long productId; + private String region; + private String distillery; + private String name; + private Double price; + + public long getProductId() { + return productId; + } + + public void setProductId(long productId) { + this.productId = productId; + } + + public String getRegion() { + return region; + } + + public void setRegion(String region) { + this.region = region; + } + + public String getDistillery() { + return distillery; + } + + public void setDistillery(String distillery) { + this.distillery = distillery; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public Double getPrice() { + return price; + } + + public void setPrice(Double price) { + this.price = price; + } + + @Override + public String toString() { + return "ProductPreviewDTO{" + + "productId=" + productId + + ", region='" + region + '\'' + + ", distillery='" + distillery + '\'' + + ", name='" + name + '\'' + + ", price=" + price + + '}'; + } +} diff --git a/sut-market/market-core/src/main/java/market/dto/RegionDTO.java b/sut-market/market-core/src/main/java/market/dto/RegionDTO.java new file mode 100644 index 0000000..8d08ab8 --- /dev/null +++ b/sut-market/market-core/src/main/java/market/dto/RegionDTO.java @@ -0,0 +1,82 @@ +package market.dto; + +import org.springframework.hateoas.RepresentationModel; + +import java.util.Objects; + +public class RegionDTO extends RepresentationModel { + + private Long id; + private String name; + private String subtitle; + private String description; + private String color; + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getSubtitle() { + return subtitle; + } + + public void setSubtitle(String subtitle) { + this.subtitle = subtitle; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public String getColor() { + return color; + } + + public void setColor(String color) { + this.color = color; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + RegionDTO regionDTO = (RegionDTO) o; + return Objects.equals(id, regionDTO.id) && + Objects.equals(name, regionDTO.name) && + Objects.equals(subtitle, regionDTO.subtitle) && + Objects.equals(description, regionDTO.description) && + Objects.equals(color, regionDTO.color); + } + + @Override + public int hashCode() { + return Objects.hash(id, name, subtitle, description, color); + } + + @Override + public String toString() { + return "RegionDTO{" + + "id=" + id + + ", name='" + name + '\'' + + ", subtitle='" + subtitle + '\'' + + ", description='" + description + '\'' + + ", color='" + color + '\'' + + '}'; + } +} diff --git a/sut-market/market-core/src/main/java/market/dto/UserDTO.java b/sut-market/market-core/src/main/java/market/dto/UserDTO.java new file mode 100644 index 0000000..bfcee6a --- /dev/null +++ b/sut-market/market-core/src/main/java/market/dto/UserDTO.java @@ -0,0 +1,109 @@ +package market.dto; + +import org.springframework.hateoas.RepresentationModel; + +import javax.validation.constraints.NotEmpty; +import javax.validation.constraints.Pattern; +import javax.validation.constraints.Size; +import java.util.Objects; + +public class UserDTO extends RepresentationModel { + public static final String ALPHANUMERIC = "^[a-zA-Z0-9]+$"; + public static final String EMAIL_REGEX = "^[\\w-]+(\\.[\\w-]+)*@([\\w-]+\\.)+[a-zA-Z]+$"; + public static final String USER_PHONE_REGEX = "^\\+[1-9][0-9]?[\\s]*\\(?\\d{3}\\)?[-\\s]?\\d{3}[-\\s]?\\d{2}[-\\s]?\\d{2}$"; + public static final String USER_ADDRESS_REGEX = "^[^#$%^*()']*$"; + + public static final String HIDDEN_PASSWORD = "hidden"; + + @NotEmpty + @Size(max = 50) + @Pattern(regexp = EMAIL_REGEX) + private String email; + + @Size(min = 6, max = 50) + @Pattern(regexp = ALPHANUMERIC) + private String password; + + @NotEmpty + @Size(max = 50) + @Pattern(regexp = "^[\\pL '-]+$") + private String name; + + @NotEmpty + @Size(max = 20) + @Pattern(regexp = USER_PHONE_REGEX) + private String phone; + + @NotEmpty + @Size(max = 100) + @Pattern(regexp = USER_ADDRESS_REGEX) + private String address; + + public String getEmail() { + return email; + } + + public void setEmail(String email) { + this.email = email; + } + + public String getPassword() { + return password; + } + + public void setPassword(String password) { + this.password = password; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getPhone() { + return phone; + } + + public void setPhone(String phone) { + this.phone = phone; + } + + public String getAddress() { + return address; + } + + public void setAddress(String address) { + this.address = address; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + UserDTO userDTO = (UserDTO) o; + return Objects.equals(email, userDTO.email) && + Objects.equals(password, userDTO.password) && + Objects.equals(name, userDTO.name) && + Objects.equals(phone, userDTO.phone) && + Objects.equals(address, userDTO.address); + } + + @Override + public int hashCode() { + return Objects.hash(email, password, name, phone, address); + } + + @Override + public String toString() { + return "UserDTO{" + + "email='" + email + '\'' + + ", password='" + HIDDEN_PASSWORD + '\'' + + ", name='" + name + '\'' + + ", phone='" + phone + '\'' + + ", address='" + address + '\'' + + '}'; + } +} diff --git a/sut-market/market-core/src/main/java/market/dto/assembler/BillDtoAssembler.java b/sut-market/market-core/src/main/java/market/dto/assembler/BillDtoAssembler.java new file mode 100644 index 0000000..6ad857f --- /dev/null +++ b/sut-market/market-core/src/main/java/market/dto/assembler/BillDtoAssembler.java @@ -0,0 +1,20 @@ +package market.dto.assembler; + +import market.domain.Bill; +import market.dto.BillDTO; +import org.springframework.hateoas.server.RepresentationModelAssembler; + +public class BillDtoAssembler implements RepresentationModelAssembler { + + @Override + public BillDTO toModel(Bill bill) { + BillDTO dto = new BillDTO(); + dto.setId(bill.getId()); + dto.setNumber(bill.getNumber()); + dto.setDateCreated(bill.getDateCreated().toString()); + dto.setTotalCost(bill.getTotalCost()); + dto.setPayed(bill.isPayed()); + dto.setCcNumber(bill.getCcNumber()); + return dto; + } +} diff --git a/sut-market/market-core/src/main/java/market/dto/assembler/CartDtoAssembler.java b/sut-market/market-core/src/main/java/market/dto/assembler/CartDtoAssembler.java new file mode 100644 index 0000000..1cec2cd --- /dev/null +++ b/sut-market/market-core/src/main/java/market/dto/assembler/CartDtoAssembler.java @@ -0,0 +1,74 @@ +package market.dto.assembler; + +import market.domain.Cart; +import market.domain.CartItem; +import market.domain.Product; +import market.dto.CartDTO; +import market.dto.CartItemDTO; +import market.properties.MarketProperties; +import market.service.ProductService; +import org.springframework.hateoas.server.RepresentationModelAssembler; + +import java.util.List; +import java.util.Optional; +import java.util.stream.Collectors; + +public class CartDtoAssembler implements RepresentationModelAssembler { + + private final MarketProperties marketProperties; + + public CartDtoAssembler(MarketProperties marketProperties) { + this.marketProperties = marketProperties; + } + + @Override + public CartDTO toModel(Cart cart) { + CartDTO dto = toAnonymousResource(cart); + dto.setUser(cart.getUserAccount().getEmail()); + return dto; + } + + public CartDTO toAnonymousResource(Cart cart) { + int deliveryCost = marketProperties.getDeliveryCost(); + + CartDTO dto = new CartDTO(); + dto.setDeliveryIncluded(cart.isDeliveryIncluded()); + dto.setProductsCost(cart.getItemsCost()); + dto.setDeliveryCost(deliveryCost); + dto.setTotalCost(cart.getItemsCost() + deliveryCost); + dto.setTotalItems(cart.getItemsCount()); + + List cartItemsDto = cart.getCartItems().stream() + .map(this::toCartItemDto) + .collect(Collectors.toList()); + dto.setCartItems(cartItemsDto); + + return dto; + } + + public CartItemDTO toCartItemDto(CartItem cartItem) { + Long productId = cartItem.getProduct().getId(); + + CartItemDTO dto = new CartItemDTO(); + dto.setProductId(productId); + dto.setQuantity(cartItem.getQuantity()); + return dto; + } + + /** + * @return domain cart created from DTO + */ + public Cart toDomain(CartDTO cartDTO, ProductService productService) { // todo: avoid passing service here, pass a map + Cart cart = new Cart(); + cart.setDeliveryIncluded(cartDTO.isDeliveryIncluded()); + for (CartItemDTO cartItemDto : cartDTO.getCartItems()) { + Optional productOptional = productService.findById(cartItemDto.getProductId()); + if (productOptional.isPresent()) { + Product product = productOptional.get(); + if (product.isAvailable()) + cart.update(product, cartItemDto.getQuantity()); + } + } + return cart; + } +} diff --git a/sut-market/market-core/src/main/java/market/dto/assembler/ContactsDtoAssembler.java b/sut-market/market-core/src/main/java/market/dto/assembler/ContactsDtoAssembler.java new file mode 100644 index 0000000..ea00a42 --- /dev/null +++ b/sut-market/market-core/src/main/java/market/dto/assembler/ContactsDtoAssembler.java @@ -0,0 +1,23 @@ +package market.dto.assembler; + +import market.domain.Contacts; +import market.dto.ContactsDTO; +import org.springframework.hateoas.server.RepresentationModelAssembler; + +public class ContactsDtoAssembler implements RepresentationModelAssembler { + + @Override + public ContactsDTO toModel(Contacts contacts) { + ContactsDTO dto = new ContactsDTO(); + dto.setPhone(contacts.getPhone()); + dto.setAddress(contacts.getAddress()); + return dto; + } + + public Contacts toDomain(ContactsDTO dto) { + Contacts contacts = new Contacts(); + contacts.setAddress(dto.getAddress()); + contacts.setPhone(dto.getPhone()); + return contacts; + } +} diff --git a/sut-market/market-core/src/main/java/market/dto/assembler/DistilleryDtoAssembler.java b/sut-market/market-core/src/main/java/market/dto/assembler/DistilleryDtoAssembler.java new file mode 100644 index 0000000..6f5a607 --- /dev/null +++ b/sut-market/market-core/src/main/java/market/dto/assembler/DistilleryDtoAssembler.java @@ -0,0 +1,31 @@ +package market.dto.assembler; + +import market.domain.Distillery; +import market.dto.DistilleryDTO; +import org.springframework.hateoas.server.RepresentationModelAssembler; + +import java.util.List; + +public class DistilleryDtoAssembler implements RepresentationModelAssembler { + + @Override + public DistilleryDTO toModel(Distillery distillery) { + DistilleryDTO dto = new DistilleryDTO(); + dto.setId(distillery.getId()); + dto.setTitle(distillery.getTitle()); + dto.setDescription(distillery.getDescription()); + dto.setRegion(distillery.getRegion().getName()); + return dto; + } + + public DistilleryDTO[] toDtoArray(List items) { + return toCollectionModel(items).getContent().toArray(new DistilleryDTO[items.size()]); + } + + public Distillery toDomain(DistilleryDTO dto) { + return new Distillery.Builder() + .setTitle(dto.getTitle()) + .setDescription(dto.getDescription()) + .build(); + } +} diff --git a/sut-market/market-core/src/main/java/market/dto/assembler/OrderDtoAssembler.java b/sut-market/market-core/src/main/java/market/dto/assembler/OrderDtoAssembler.java new file mode 100644 index 0000000..07e1649 --- /dev/null +++ b/sut-market/market-core/src/main/java/market/dto/assembler/OrderDtoAssembler.java @@ -0,0 +1,37 @@ +package market.dto.assembler; + +import market.domain.Order; +import market.dto.OrderDTO; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageImpl; +import org.springframework.hateoas.server.RepresentationModelAssembler; + +import java.util.List; + +public class OrderDtoAssembler implements RepresentationModelAssembler { + + @Override + public OrderDTO toModel(Order order) { + OrderDTO dto = new OrderDTO(); + dto.setId(order.getId()); + dto.setUserAccount(order.getUserAccount().getEmail()); + dto.setBillNumber(order.getBill().getNumber()); + dto.setProductsCost(order.getProductsCost()); + dto.setDateCreated(order.getDateCreated()); + dto.setDeliveryCost(order.getDeliveryCost()); + dto.setTotalCost(order.isDeliveryIncluded() ? (order.getProductsCost() + order.getDeliveryCost()) : order.getProductsCost()); + dto.setDeliveryIncluded(order.isDeliveryIncluded()); + dto.setPayed(order.getBill().isPayed()); + dto.setExecuted(order.isExecuted()); + return dto; + } + + public PageImpl toModel(Page page) { + List dtoList = page.map(this::toModel).toList(); + return new PageImpl<>(dtoList, page.getPageable(), page.getTotalElements()); + } + + public OrderDTO[] toDtoArray(List items) { + return toCollectionModel(items).getContent().toArray(new OrderDTO[items.size()]); + } +} diff --git a/sut-market/market-core/src/main/java/market/dto/assembler/OrderedProductDtoAssembler.java b/sut-market/market-core/src/main/java/market/dto/assembler/OrderedProductDtoAssembler.java new file mode 100644 index 0000000..a0095ee --- /dev/null +++ b/sut-market/market-core/src/main/java/market/dto/assembler/OrderedProductDtoAssembler.java @@ -0,0 +1,17 @@ +package market.dto.assembler; + +import market.domain.OrderedProduct; +import market.dto.OrderedProductDTO; +import org.springframework.hateoas.server.RepresentationModelAssembler; + +public class OrderedProductDtoAssembler implements RepresentationModelAssembler { + + @Override + public OrderedProductDTO toModel(OrderedProduct orderedProduct) { + OrderedProductDTO dto = new OrderedProductDTO(); + dto.setOrderId(orderedProduct.getOrder().getId()); + dto.setQuantity(orderedProduct.getQuantity()); + dto.setProductId(orderedProduct.getProduct().getId()); + return dto; + } +} diff --git a/sut-market/market-core/src/main/java/market/dto/assembler/ProductDtoAssembler.java b/sut-market/market-core/src/main/java/market/dto/assembler/ProductDtoAssembler.java new file mode 100644 index 0000000..c4df732 --- /dev/null +++ b/sut-market/market-core/src/main/java/market/dto/assembler/ProductDtoAssembler.java @@ -0,0 +1,44 @@ +package market.dto.assembler; + +import market.domain.Product; +import market.dto.ProductDTO; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageImpl; +import org.springframework.hateoas.server.RepresentationModelAssembler; + +import java.util.List; + +public class ProductDtoAssembler implements RepresentationModelAssembler { + + @Override + public ProductDTO toModel(Product product) { + ProductDTO dto = new ProductDTO(); + dto.setProductId(product.getId()); + dto.setDistillery(product.getDistillery() == null ? null : product.getDistillery().getTitle()); + dto.setName(product.getName()); + dto.setAge(product.getAge()); + dto.setAlcohol(product.getAlcohol()); + dto.setPrice(product.getPrice()); + dto.setVolume(product.getVolume()); + dto.setDescription(product.getDescription()); + dto.setAvailable(product.isAvailable()); + return dto; + } + + public PageImpl toModel(Page page) { + List dtoList = page.map(this::toModel).toList(); + return new PageImpl<>(dtoList, page.getPageable(), page.getTotalElements()); + } + + public Product dtoDomain(ProductDTO dto) { + return new Product.Builder() + .setName(dto.getName()) + .setAge(dto.getAge()) + .setAlcohol(dto.getAlcohol()) + .setPrice(dto.getPrice()) + .setVolume(dto.getVolume()) + .setDescription(dto.getDescription()) + .setAvailable(dto.isAvailable()) + .build(); + } +} diff --git a/sut-market/market-core/src/main/java/market/dto/assembler/ProductPreviewAssembler.java b/sut-market/market-core/src/main/java/market/dto/assembler/ProductPreviewAssembler.java new file mode 100644 index 0000000..f90bb39 --- /dev/null +++ b/sut-market/market-core/src/main/java/market/dto/assembler/ProductPreviewAssembler.java @@ -0,0 +1,19 @@ +package market.dto.assembler; + +import market.domain.Product; +import market.dto.ProductPreviewDTO; +import org.springframework.hateoas.server.RepresentationModelAssembler; + +public class ProductPreviewAssembler implements RepresentationModelAssembler { + + @Override + public ProductPreviewDTO toModel(Product product) { + ProductPreviewDTO dto = new ProductPreviewDTO(); + dto.setProductId(product.getId()); + dto.setRegion(product.getDistillery().getRegion().getName()); + dto.setDistillery(product.getDistillery().getTitle()); + dto.setName(product.getName()); + dto.setPrice(product.getPrice()); + return dto; + } +} diff --git a/sut-market/market-core/src/main/java/market/dto/assembler/RegionDtoAssembler.java b/sut-market/market-core/src/main/java/market/dto/assembler/RegionDtoAssembler.java new file mode 100644 index 0000000..60360e6 --- /dev/null +++ b/sut-market/market-core/src/main/java/market/dto/assembler/RegionDtoAssembler.java @@ -0,0 +1,35 @@ +package market.dto.assembler; + +import market.domain.Region; +import market.dto.RegionDTO; +import org.springframework.hateoas.server.RepresentationModelAssembler; + +import java.util.List; + +public class RegionDtoAssembler implements RepresentationModelAssembler { + + @Override + public RegionDTO toModel(Region region) { + RegionDTO dto = new RegionDTO(); + dto.setId(region.getId()); + dto.setName(region.getName()); + dto.setSubtitle(region.getSubtitle()); + dto.setColor(region.getColor()); + dto.setDescription(region.getDescription()); + return dto; + } + + public RegionDTO[] toDtoArray(List items) { + return toCollectionModel(items).getContent().toArray(new RegionDTO[items.size()]); + } + + public Region toDomain(RegionDTO dto) { + return new Region.Builder() + .setId(dto.getId()) + .setName(dto.getName()) + .setSubtitle(dto.getSubtitle()) + .setColor(dto.getColor()) + .setDescription(dto.getDescription()) + .build(); + } +} diff --git a/sut-market/market-core/src/main/java/market/dto/assembler/UserAccountDtoAssembler.java b/sut-market/market-core/src/main/java/market/dto/assembler/UserAccountDtoAssembler.java new file mode 100644 index 0000000..2ddb073 --- /dev/null +++ b/sut-market/market-core/src/main/java/market/dto/assembler/UserAccountDtoAssembler.java @@ -0,0 +1,36 @@ +package market.dto.assembler; + +import market.domain.Contacts; +import market.domain.UserAccount; +import market.dto.UserDTO; +import org.springframework.hateoas.server.RepresentationModelAssembler; + +public class UserAccountDtoAssembler implements RepresentationModelAssembler { + + @Override + public UserDTO toModel(UserAccount userAccount) { + UserDTO dto = new UserDTO(); + dto.setEmail(userAccount.getEmail()); + dto.setPassword(UserDTO.HIDDEN_PASSWORD); + dto.setName(userAccount.getName()); + dto.setPhone(userAccount.getContacts().getPhone()); + dto.setAddress(userAccount.getContacts().getAddress()); + return dto; + } + + public UserAccount toDomain(UserDTO user) { + UserAccount userAccount = new UserAccount.Builder() + .setEmail(user.getEmail()) + .setPassword(user.getPassword()) + .setName(user.getName()) + .setActive(true) + .build(); + Contacts contacts = new Contacts.Builder() + .setUserAccount(userAccount) + .setPhone(user.getPhone()) + .setAddress(user.getAddress()) + .build(); + userAccount.setContacts(contacts); + return userAccount; + } +} diff --git a/sut-market/market-core/src/main/java/market/dto/exception/FieldErrorDTO.java b/sut-market/market-core/src/main/java/market/dto/exception/FieldErrorDTO.java new file mode 100644 index 0000000..f4e254a --- /dev/null +++ b/sut-market/market-core/src/main/java/market/dto/exception/FieldErrorDTO.java @@ -0,0 +1,31 @@ +package market.dto.exception; + +/** + * Нарушение ограничения поля при валидации. + */ +public class FieldErrorDTO { + + private final String field; + private final String message; + + public FieldErrorDTO(String field, String message) { + this.field = field; + this.message = message; + } + + public String getField() { + return field; + } + + public String getMessage() { + return message; + } + + @Override + public String toString() { + return "{" + + "field='" + field + '\'' + + ", message='" + message + '\'' + + '}'; + } +} diff --git a/sut-market/market-core/src/main/java/market/dto/exception/ValidationErrorDTO.java b/sut-market/market-core/src/main/java/market/dto/exception/ValidationErrorDTO.java new file mode 100644 index 0000000..2e36d28 --- /dev/null +++ b/sut-market/market-core/src/main/java/market/dto/exception/ValidationErrorDTO.java @@ -0,0 +1,21 @@ +package market.dto.exception; + +import java.util.ArrayList; +import java.util.List; + +/** + * Список нашенных при валидации ограничений. + */ +public class ValidationErrorDTO { + + private final List fieldErrors = new ArrayList<>(); + + public void addFieldError(String path, String message) { + FieldErrorDTO error = new FieldErrorDTO(path, message); + fieldErrors.add(error); + } + + public List getFieldErrors() { + return fieldErrors; + } +} diff --git a/sut-market/market-core/src/main/java/market/exception/CustomNotValidException.java b/sut-market/market-core/src/main/java/market/exception/CustomNotValidException.java new file mode 100644 index 0000000..492bf3f --- /dev/null +++ b/sut-market/market-core/src/main/java/market/exception/CustomNotValidException.java @@ -0,0 +1,38 @@ +package market.exception; + +import org.springframework.validation.FieldError; + +import java.util.Collections; +import java.util.List; + +/** + * + */ +public class CustomNotValidException extends RuntimeException { + private static final long serialVersionUID = 4461435547254498698L; + + private final String eventType; + private final String entityType; + private final String field; + + public CustomNotValidException(String eventType, String entityType, String field) { + super(); + this.eventType = eventType; + this.entityType = entityType; + this.field = field; + } + + public FieldError getFieldError() { + String[] codes = {eventType + "." + field, eventType + "." + entityType + "." + field}; + Object[] arguments = {field}; + return new FieldError(entityType, field, "", false, codes, arguments, ""); + } + + public List getFieldErrors() { + return Collections.singletonList(getFieldError()); + } + + public String getEntityType() { + return entityType; + } +} diff --git a/sut-market/market-core/src/main/java/market/exception/EmailExistsException.java b/sut-market/market-core/src/main/java/market/exception/EmailExistsException.java new file mode 100644 index 0000000..9aacd2f --- /dev/null +++ b/sut-market/market-core/src/main/java/market/exception/EmailExistsException.java @@ -0,0 +1,11 @@ +package market.exception; + +/** + * Пользователь с указанным адресом уже существует. + */ +public class EmailExistsException extends CustomNotValidException { + + public EmailExistsException(Class clazz) { + super("Exists", clazz.getSimpleName(), "email"); + } +} diff --git a/sut-market/market-core/src/main/java/market/exception/EmptyCartException.java b/sut-market/market-core/src/main/java/market/exception/EmptyCartException.java new file mode 100644 index 0000000..95df36e --- /dev/null +++ b/sut-market/market-core/src/main/java/market/exception/EmptyCartException.java @@ -0,0 +1,11 @@ +package market.exception; + +/** + * Заказ не может быть оформлен: корзина пуста. + */ +public class EmptyCartException extends CustomNotValidException { + + public EmptyCartException() { + super("NotEmpty", "cart", "items"); + } +} diff --git a/sut-market/market-core/src/main/java/market/exception/UnknownEntityException.java b/sut-market/market-core/src/main/java/market/exception/UnknownEntityException.java new file mode 100644 index 0000000..558d19d --- /dev/null +++ b/sut-market/market-core/src/main/java/market/exception/UnknownEntityException.java @@ -0,0 +1,29 @@ +package market.exception; + +/** + * Попытка сослаться на неизвестный товар. + */ +public class UnknownEntityException extends CustomNotValidException { + private static final long serialVersionUID = 4827971686664741607L; + + private final String idField; + private final long idValue; + + public UnknownEntityException(Class clazz, long idValue) { + this(clazz, "id", idValue); + } + + public UnknownEntityException(Class clazz, String idField, long idValue) { + super("NotExist", clazz.getSimpleName(), idField); + this.idField = idField; + this.idValue = idValue; + } + + public String getIdField() { + return idField; + } + + public long getIdValue() { + return idValue; + } +} diff --git a/sut-market/market-core/src/main/java/market/properties/MarketProperties.java b/sut-market/market-core/src/main/java/market/properties/MarketProperties.java new file mode 100644 index 0000000..35d6b0f --- /dev/null +++ b/sut-market/market-core/src/main/java/market/properties/MarketProperties.java @@ -0,0 +1,17 @@ +package market.properties; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +@Component +public class MarketProperties { + private final int deliveryCost; + + public MarketProperties(@Value("${deliveryCost}") int deliveryCost) { + this.deliveryCost = deliveryCost; + } + + public int getDeliveryCost() { + return deliveryCost; + } +} diff --git a/sut-market/market-core/src/main/java/market/security/AuthenticationService.java b/sut-market/market-core/src/main/java/market/security/AuthenticationService.java new file mode 100644 index 0000000..ff2e676 --- /dev/null +++ b/sut-market/market-core/src/main/java/market/security/AuthenticationService.java @@ -0,0 +1,27 @@ +package market.security; + +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.authentication.BadCredentialsException; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; + +public class AuthenticationService { + + private final AuthenticationManager authenticationManager; + + public AuthenticationService(AuthenticationManager authenticationManager) { + this.authenticationManager = authenticationManager; + } + + public boolean authenticate(String login, String password) { + try { + Authentication auth = authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(login, password)); + SecurityContextHolder.getContext().setAuthentication(auth); + return auth.isAuthenticated(); + } catch (BadCredentialsException ex) { + // todo + return false; + } + } +} diff --git a/sut-market/market-core/src/main/java/market/security/UserDetailsServiceImpl.java b/sut-market/market-core/src/main/java/market/security/UserDetailsServiceImpl.java new file mode 100644 index 0000000..1e73197 --- /dev/null +++ b/sut-market/market-core/src/main/java/market/security/UserDetailsServiceImpl.java @@ -0,0 +1,58 @@ +package market.security; + +import market.domain.Role; +import market.domain.UserAccount; +import market.service.UserAccountService; +import org.springframework.dao.DataAccessException; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.security.core.userdetails.User; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.security.core.userdetails.UsernameNotFoundException; +import org.springframework.transaction.annotation.Transactional; + +import java.util.ArrayList; +import java.util.Collection; + +/** + * Реализация сервиса извлечения аккаунта пользователя из БД. + */ +public class UserDetailsServiceImpl implements UserDetailsService { + + private final UserAccountService userAccountService; + + public UserDetailsServiceImpl(UserAccountService userAccountService) { + this.userAccountService = userAccountService; + } + + @Override + @Transactional + public UserDetails loadUserByUsername(String login) throws UsernameNotFoundException, DataAccessException + { + UserAccount userEntity = userAccountService.findByEmail(login); + if (userEntity == null) { + throw new UsernameNotFoundException("user not found"); + } + return buildUser(userEntity); + } + + private User buildUser(UserAccount account) { + String login = account.getEmail(); + String password = account.getPassword(); + boolean enabled = account.isActive(); + boolean accountNonExpired = account.isActive(); + boolean credentialsNonExpired = account.isActive(); + boolean accountNonLocked = account.isActive(); + + Collection authorities = new ArrayList<>(); + for (Role role : account.getRoles()) { + authorities.add(new SimpleGrantedAuthority(role.getTitle())); + } + authorities.add(new SimpleGrantedAuthority("ROLE_USER")); + + User user = new User(login, password, enabled, + accountNonExpired, credentialsNonExpired, accountNonLocked, authorities); + return user; + } +} diff --git a/sut-market/market-core/src/main/java/market/service/CartService.java b/sut-market/market-core/src/main/java/market/service/CartService.java new file mode 100644 index 0000000..fb21407 --- /dev/null +++ b/sut-market/market-core/src/main/java/market/service/CartService.java @@ -0,0 +1,38 @@ +package market.service; + +import market.domain.Cart; +import market.domain.CartItem; + +import java.util.List; + +public interface CartService { + + /** + * Returns existing or creates new cart of the specified user. + */ + Cart getCartOrCreate(String userEmail); + + /** + * Adds new item into the specified user cart and saves cart. + * @return updated cart + */ + Cart addToCart(String userEmail, long productId, int quantity); + + /** + * Adds all the listed items into the specified user cart and saves cart. + * @return updated cart + */ + Cart addAllToCart(String userEmail, List itemsToCopy); + + /** + * Changes delivery option of the specified user cart. + * @return updated cart + */ + Cart setDelivery(String userEmail, boolean deliveryIncluded); + + /** + * Clears the specified user cart. + * @return updated cart + */ + Cart clearCart(String userEmail); +} diff --git a/sut-market/market-core/src/main/java/market/service/ContactsService.java b/sut-market/market-core/src/main/java/market/service/ContactsService.java new file mode 100644 index 0000000..7f1e757 --- /dev/null +++ b/sut-market/market-core/src/main/java/market/service/ContactsService.java @@ -0,0 +1,17 @@ +package market.service; + +import market.domain.Contacts; + +public interface ContactsService { + + /** + * @return contacts of the specified user + */ + Contacts getContacts(String userLogin); + + /** + * Updates contacts of the specified user. + */ + Contacts updateUserContacts(Contacts changedContacts, String userLogin); + +} diff --git a/sut-market/market-core/src/main/java/market/service/DistilleryService.java b/sut-market/market-core/src/main/java/market/service/DistilleryService.java new file mode 100644 index 0000000..4bf4c73 --- /dev/null +++ b/sut-market/market-core/src/main/java/market/service/DistilleryService.java @@ -0,0 +1,44 @@ +package market.service; + +import market.domain.Distillery; +import market.domain.Region; + +import java.util.List; + +public interface DistilleryService { + + /** + * @return all the distilleries sorted by title + */ + List findAll(); + + /** + * @return all the distilleries of the specified region sorted by title + */ + List findByRegion(Region region); + + /** + * @return distillery with the specified id + */ + Distillery findById(long distilleryId); + + /** + * @return distillery with the specified title + */ + Distillery findByTitle(String title); + + /** + * Creates new distillery. + */ + void create(Distillery newDistillery, String regionName); + + /** + * Updates existing distillery. + */ + void update(long distilleryId, Distillery changedDistillery, String regionTitle); + + /** + * Removes distillery. + */ + void delete(long distilleryId); +} diff --git a/sut-market/market-core/src/main/java/market/service/OrderService.java b/sut-market/market-core/src/main/java/market/service/OrderService.java new file mode 100644 index 0000000..1250c3a --- /dev/null +++ b/sut-market/market-core/src/main/java/market/service/OrderService.java @@ -0,0 +1,42 @@ +package market.service; + +import market.domain.Order; +import market.exception.EmptyCartException; +import market.exception.UnknownEntityException; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; + +import java.util.List; +import java.util.Optional; + +public interface OrderService { + + /** + * @return all the orders of the specified user + */ + List getUserOrders(String userLogin); + + /** + * @return order of the specified user and id + * @throws UnknownEntityException if the requested order does not exist + */ + Optional getUserOrder(String userLogin, long orderId); + + /** + * @return orders filtered according to the passed parameters + */ + Page fetchFiltered(String executed, String created, PageRequest request); + + /** + * Creates new order for the specified user. + * + * @return newly created order + * @throws EmptyCartException if the specified user cart is empty + */ + Order createUserOrder(String userLogin, int deliveryCost, String cardNumber); + + /** + * Updates a state of the order with the specified id + */ + void updateStatus(long orderId, boolean executed); +} diff --git a/sut-market/market-core/src/main/java/market/service/ProductService.java b/sut-market/market-core/src/main/java/market/service/ProductService.java new file mode 100644 index 0000000..44a6a3b --- /dev/null +++ b/sut-market/market-core/src/main/java/market/service/ProductService.java @@ -0,0 +1,73 @@ +package market.service; + +import market.domain.Distillery; +import market.domain.Product; +import market.domain.Region; +import market.exception.UnknownEntityException; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; + +import java.util.List; +import java.util.Map; +import java.util.Optional; + +public interface ProductService { + + /** + * @return all the products, sorted by title + */ + List findAll(); + + /** + * @return all the products, sorted by title and paged + */ + Page findAll(PageRequest request); + + /** + * @return all the products of the specified distillery, sorted by title + */ + Page findByDistillery(Distillery distillery, PageRequest request); + + /** + * @return all the products of the specified region, sorted by title + */ + Page findByRegion(Region region, PageRequest request); + + /** + * @return all the available products, sorted by title + */ + Page findByAvailability(String available, PageRequest request); + + /** + * @return product with the specified id + * @throws UnknownEntityException if product does not exist + */ + Product getProduct(long productId); + + /** + * @return product with the specified id + */ + Optional findById(long productId); + + /** + * Creates new product. + */ + void create(Product product, String distilleryTitle); + + /** + * Updates existing product. + * + * @throws UnknownEntityException if product does not exist + */ + void update(long productId, Product product, String distilleryTitle); + + /** + * Updates availability of the specified product. + */ + void updateAvailability(Map> productIdsByAvailability); + + /** + * Removes distillery. + */ + void delete(long product); +} diff --git a/sut-market/market-core/src/main/java/market/service/RegionService.java b/sut-market/market-core/src/main/java/market/service/RegionService.java new file mode 100644 index 0000000..136cb0c --- /dev/null +++ b/sut-market/market-core/src/main/java/market/service/RegionService.java @@ -0,0 +1,39 @@ +package market.service; + +import market.domain.Region; + +import java.util.List; + +public interface RegionService { + + /** + * @return all the existing regions sorted by region name + */ + List findAll(); + + /** + * @return region with the specified id + */ + Region findOne(long regionId); + + /** + * @return region with the specified name + */ + Region findByName(String regionName); + + /** + * Creates new region. + */ + void create(Region newRegion); + + /** + * Updates existing region. + */ + void update(long regionId, Region changedRegion); + + /** + * Removes region. + */ + void delete(long regionId); + +} diff --git a/sut-market/market-core/src/main/java/market/service/TestService.java b/sut-market/market-core/src/main/java/market/service/TestService.java new file mode 100644 index 0000000..86c47cc --- /dev/null +++ b/sut-market/market-core/src/main/java/market/service/TestService.java @@ -0,0 +1,10 @@ +package market.service; + +import java.sql.SQLException; + +// Add a service for test: clean the database (deleteAll) and get all data (getAll) + +public interface TestService { + public void deleteAll() throws SQLException; + public Object getAll(); +} diff --git a/sut-market/market-core/src/main/java/market/service/UserAccountService.java b/sut-market/market-core/src/main/java/market/service/UserAccountService.java new file mode 100644 index 0000000..ae0394a --- /dev/null +++ b/sut-market/market-core/src/main/java/market/service/UserAccountService.java @@ -0,0 +1,20 @@ +package market.service; + +import market.domain.UserAccount; +import market.exception.EmailExistsException; + +public interface UserAccountService { + + /** + * @return user account associated with the specified email + */ + UserAccount findByEmail(String email); + + /** + * Creates new account. + * @return newly created account + * @throws EmailExistsException if some account is already associated with the specified email + */ + UserAccount create(UserAccount userAccount); + +} diff --git a/sut-market/market-core/src/main/java/market/service/impl/CartServiceImpl.java b/sut-market/market-core/src/main/java/market/service/impl/CartServiceImpl.java new file mode 100644 index 0000000..e9f57c5 --- /dev/null +++ b/sut-market/market-core/src/main/java/market/service/impl/CartServiceImpl.java @@ -0,0 +1,91 @@ +package market.service.impl; + +import market.dao.CartDAO; +import market.domain.Cart; +import market.domain.CartItem; +import market.domain.Product; +import market.domain.UserAccount; +import market.service.CartService; +import market.service.ProductService; +import market.service.UserAccountService; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Propagation; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; +import java.util.Optional; + +@Service +public class CartServiceImpl implements CartService { + private static final Logger log = LogManager.getLogger(CartServiceImpl.class); + + private final CartDAO cartDAO; + private final UserAccountService userAccountService; + private final ProductService productService; + + public CartServiceImpl(CartDAO cartDAO, UserAccountService userAccountService, ProductService productService) { + this.cartDAO = cartDAO; + this.userAccountService = userAccountService; + this.productService = productService; + } + + @Transactional(propagation = Propagation.SUPPORTS) + @Override + public Cart getCartOrCreate(String userEmail) { + UserAccount account = userAccountService.findByEmail(userEmail); // todo: check if this user exists + Optional cartOptional = cartDAO.findById(account.getId()); + return cartOptional.orElseGet(() -> createCart(account)); + } + + private Cart createCart(UserAccount account) { + if (log.isDebugEnabled()) + log.debug("Creating new cart for account #" + account.getId()); + return cartDAO.save(new Cart(account)); + } + + @Transactional + @Override + public Cart addToCart(String userEmail, long productId, int quantity) { + Cart cart = getCartOrCreate(userEmail); + Product product = productService.getProduct(productId); + if (product.isAvailable()) { + cart.update(product, quantity); + return cartDAO.save(cart); + } else { + return cart; + } + } + + @Transactional + @Override + public Cart addAllToCart(String userEmail, List itemsToAdd) { + Cart cart = getCartOrCreate(userEmail); + boolean updated = false; + for (CartItem item : itemsToAdd) { + Optional product = productService.findById(item.getProduct().getId()); + if (product.isPresent() && product.get().isAvailable()) { + cart.update(product.get(), item.getQuantity()); + updated = true; + } + } + return updated ? cartDAO.save(cart) : cart; + } + + @Transactional + @Override + public Cart setDelivery(String userEmail, boolean deliveryIncluded) { + Cart cart = getCartOrCreate(userEmail); + cart.setDeliveryIncluded(deliveryIncluded); + return cartDAO.save(cart); + } + + @Transactional + @Override + public Cart clearCart(String userEmail) { + Cart cart = getCartOrCreate(userEmail); + cart.clear(); + return cartDAO.save(cart); + } +} diff --git a/sut-market/market-core/src/main/java/market/service/impl/ContactsServiceImpl.java b/sut-market/market-core/src/main/java/market/service/impl/ContactsServiceImpl.java new file mode 100644 index 0000000..4b74127 --- /dev/null +++ b/sut-market/market-core/src/main/java/market/service/impl/ContactsServiceImpl.java @@ -0,0 +1,41 @@ +package market.service.impl; + +import market.dao.ContactsDAO; +import market.domain.Contacts; +import market.domain.UserAccount; +import market.exception.CustomNotValidException; +import market.service.ContactsService; +import market.service.UserAccountService; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +public class ContactsServiceImpl implements ContactsService { + private final ContactsDAO contactsDAO; + private final UserAccountService userAccountService; + + public ContactsServiceImpl(ContactsDAO contactsDAO, UserAccountService userAccountService) { + this.contactsDAO = contactsDAO; + this.userAccountService = userAccountService; + } + + @Transactional(readOnly = true) + @Override + public Contacts getContacts(String userLogin) { + UserAccount account = userAccountService.findByEmail(userLogin); + return contactsDAO.findByUserAccount(account); + } + + @Transactional + @Override + public Contacts updateUserContacts(Contacts changedContacts, String userLogin) { + Contacts originalContacts = getContacts(userLogin); + if (originalContacts == null) + throw new CustomNotValidException("NotExist", "userLogin", "userLogin"); // todo: some custom exception + + originalContacts.setPhone(changedContacts.getPhone()); + originalContacts.setAddress(changedContacts.getAddress()); + contactsDAO.save(originalContacts); + return originalContacts; + } +} diff --git a/sut-market/market-core/src/main/java/market/service/impl/DbTestUtil.java b/sut-market/market-core/src/main/java/market/service/impl/DbTestUtil.java new file mode 100644 index 0000000..0aa786e --- /dev/null +++ b/sut-market/market-core/src/main/java/market/service/impl/DbTestUtil.java @@ -0,0 +1,29 @@ +package market.service.impl; + +import org.springframework.context.ApplicationContext; +import javax.sql.DataSource; +import java.sql.Connection; +import java.sql.SQLException; +import java.sql.Statement; + +/* + * New class for test: reset the autoincrement sequences in tables of database + */ +public final class DbTestUtil { + + private DbTestUtil() {} + + public static void resetAutoIncrementColumns(ApplicationContext applicationContext, + String... tableNames) throws SQLException { + DataSource dataSource = applicationContext.getBean(DataSource.class); + Connection dbConnection = dataSource.getConnection(); + + //Create and invoke SQL statements that reset the auto increment columns + for (String resetSqlArgument: tableNames) { + Statement statement = dbConnection.createStatement(); + String resetSql = "TRUNCATE TABLE "+resetSqlArgument+" RESTART IDENTITY"; + statement.execute(resetSql); + } + dbConnection.close(); + } + } \ No newline at end of file diff --git a/sut-market/market-core/src/main/java/market/service/impl/DistilleryServiceImpl.java b/sut-market/market-core/src/main/java/market/service/impl/DistilleryServiceImpl.java new file mode 100644 index 0000000..db0d4c7 --- /dev/null +++ b/sut-market/market-core/src/main/java/market/service/impl/DistilleryServiceImpl.java @@ -0,0 +1,80 @@ +package market.service.impl; + +import market.dao.DistilleryDAO; +import market.domain.Distillery; +import market.domain.Region; +import market.service.DistilleryService; +import market.service.RegionService; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.Comparator; +import java.util.List; +import java.util.Optional; +import java.util.stream.Collectors; + +@Service +public class DistilleryServiceImpl implements DistilleryService { + private final RegionService regionService; + private final DistilleryDAO distilleryDAO; + + public DistilleryServiceImpl(RegionService regionService, DistilleryDAO distilleryDAO) { + this.regionService = regionService; + this.distilleryDAO = distilleryDAO; + } + + @Transactional(readOnly = true) + @Override + public List findAll() { + return distilleryDAO.findAll().stream() + .sorted(Comparator.comparing(Distillery::getTitle)) + .collect(Collectors.toList()); + } + + @Transactional(readOnly = true) + @Override + public List findByRegion(Region region) { + return distilleryDAO.findByRegionOrderByTitleAsc(region); + } + + @Transactional(readOnly = true) + @Override + public Distillery findById(long distilleryId) { + return distilleryDAO.findById(distilleryId).orElse(null); + } + + @Transactional(readOnly = true) + @Override + public Distillery findByTitle(String title) { + return distilleryDAO.findByTitle(title); + } + + @Transactional + @Override + public void create(Distillery newDistillery, String regionName) { + saveInternal(newDistillery, regionName); + } + + @Override + public void update(long distilleryId, Distillery changedDistillery, String regionName) { + Optional originalDistillery = distilleryDAO.findById(distilleryId); + if (originalDistillery.isPresent()) { + changedDistillery.setId(originalDistillery.get().getId()); + saveInternal(changedDistillery, regionName); + } + } + + private void saveInternal(Distillery distillery, String regionName) { + Region region = regionService.findByName(regionName); + if (region != null) { + distillery.setRegion(region); + distilleryDAO.save(distillery); + } + } + + @Transactional + @Override + public void delete(long distilleryId) { + distilleryDAO.deleteById(distilleryId); + } +} diff --git a/sut-market/market-core/src/main/java/market/service/impl/OrderServiceImpl.java b/sut-market/market-core/src/main/java/market/service/impl/OrderServiceImpl.java new file mode 100644 index 0000000..a48199b --- /dev/null +++ b/sut-market/market-core/src/main/java/market/service/impl/OrderServiceImpl.java @@ -0,0 +1,143 @@ +package market.service.impl; + +import market.dao.OrderDAO; +import market.domain.Bill; +import market.domain.Cart; +import market.domain.CartItem; +import market.domain.Order; +import market.domain.OrderedProduct; +import market.domain.UserAccount; +import market.exception.EmptyCartException; +import market.service.CartService; +import market.service.OrderService; +import market.service.UserAccountService; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.Calendar; +import java.util.Date; +import java.util.List; +import java.util.Optional; +import java.util.Random; +import java.util.Set; + +import static java.util.stream.Collectors.toSet; + +@Service +public class OrderServiceImpl implements OrderService { + + private final OrderDAO orderDAO; + private final UserAccountService userAccountService; + private final CartService cartService; + + public OrderServiceImpl(OrderDAO orderDAO, UserAccountService userAccountService, CartService cartService) { + this.orderDAO = orderDAO; + this.userAccountService = userAccountService; + this.cartService = cartService; + } + + @Transactional(readOnly = true) + @Override + public List getUserOrders(String userLogin) { + UserAccount account = userAccountService.findByEmail(userLogin); + return orderDAO.findByUserAccountOrderByDateCreatedDesc(account); + } + + @Transactional(readOnly = true) + @Override + public Optional getUserOrder(String userLogin, long orderId) { + // todo: add user check + return orderDAO.findById(orderId); + } + + @Transactional(readOnly = true) + @Override + public Page fetchFiltered(String executed, String orderAgeInDays, PageRequest request) { + Date startTime = new Date(); + if (!"all".equals(orderAgeInDays)) { + int days = Integer.parseInt(orderAgeInDays); + Calendar c = Calendar.getInstance(); + c.setTime(new Date()); + c.add(Calendar.HOUR_OF_DAY, -(days * 24)); + startTime = c.getTime(); + } + if (!"all".equals(executed) && !"all".equals(orderAgeInDays)) { + boolean executedState = Boolean.parseBoolean(executed); + return orderDAO.findByExecutedAndDateCreatedGreaterThan(executedState, startTime, request); + } else if (!"all".equals(executed)) { + boolean executedState = Boolean.parseBoolean(executed); + return orderDAO.findByExecuted(executedState, request); + } else if (!"all".equals(orderAgeInDays)) { + return orderDAO.findByDateCreatedGreaterThan(startTime, request); + } else { + return orderDAO.findAll(request); + } + } + + @Transactional + @Override + public Order createUserOrder(String userLogin, int deliveryCost, String cardNumber) { + Cart cart = cartService.getCartOrCreate(userLogin); + if (cart.isEmpty()) + throw new EmptyCartException(); + + Order order = createNewOrder(userLogin, cart, deliveryCost); + Bill bill = createBill(order, cardNumber); + order.setBill(bill); + orderDAO.saveAndFlush(order); + + fillOrderItems(cart, order); + orderDAO.save(order); + cartService.clearCart(userLogin); + + return order; + } + + @Override + public void updateStatus(long orderId, boolean executed) { + Order order = orderDAO.findById(orderId).orElse(null); + if (order != null) { + order.setExecuted(executed); + orderDAO.save(order); + } + } + + private Order createNewOrder(String userLogin, Cart cart, int deliveryCost) { + return new Order.Builder() + .setDeliveryIncluded(cart.isDeliveryIncluded()) + .setDeliveryCost(cart.isDeliveryIncluded() ? deliveryCost : 0) + .setUserAccount(userAccountService.findByEmail(userLogin)) + .setProductsCost(cart.getItemsCost()) + .setDateCreated(new Date()) + .setExecuted(false) + .build(); + } + + private Bill createBill(Order order, String cardNumber) { + return new Bill.Builder() + .setOrder(order) + .setNumber(new Random().nextInt(999999999)) + .setTotalCost(order.getProductsCost() + order.getDeliveryCost()) + .setPayed(true) + .setDateCreated(new Date()) + .setCcNumber(cardNumber) + .build(); + } + + private void fillOrderItems(Cart cart, Order order) { + Set ordered = cart.getCartItems().stream() + .map(item -> createOrderedProduct(order, item)) + .collect(toSet()); + order.setOrderedProducts(ordered); + } + + private OrderedProduct createOrderedProduct(Order order, CartItem item) { + return new OrderedProduct.Builder() + .setProduct(item.getProduct()) + .setOrder(order) + .setQuantity(item.getQuantity()) + .build(); + } +} diff --git a/sut-market/market-core/src/main/java/market/service/impl/ProductServiceImpl.java b/sut-market/market-core/src/main/java/market/service/impl/ProductServiceImpl.java new file mode 100644 index 0000000..2cab3f2 --- /dev/null +++ b/sut-market/market-core/src/main/java/market/service/impl/ProductServiceImpl.java @@ -0,0 +1,130 @@ +package market.service.impl; + +import market.dao.ProductDAO; +import market.domain.Distillery; +import market.domain.Product; +import market.domain.Region; +import market.exception.UnknownEntityException; +import market.service.DistilleryService; +import market.service.ProductService; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.Comparator; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.stream.Collectors; + +@Service +public class ProductServiceImpl implements ProductService { + + private final ProductDAO productDAO; + private final DistilleryService distilleryService; + + public ProductServiceImpl(ProductDAO productDAO, DistilleryService distilleryService) { + this.productDAO = productDAO; + this.distilleryService = distilleryService; + } + + @Transactional(readOnly = true) + @Override + public List findAll() { + return productDAO.findAll().stream() + .sorted(Comparator.comparing(Product::getName)) + .collect(Collectors.toList()); + } + + @Transactional(readOnly = true) + @Override + public Page findAll(PageRequest request) { + return productDAO.findAll(request); + } + + @Transactional(readOnly = true) + @Override + public Page findByDistillery(Distillery distillery, PageRequest request) { + return productDAO.findByDistilleryOrderByName(distillery, request); + } + + @Transactional(readOnly = true) + @Override + public Page findByRegion(Region region, PageRequest request) { + return productDAO.findByRegionOrderByName(region, request); + } + + @Transactional(readOnly = true) + @Override + public Page findByAvailability(String available, PageRequest request) { + Page pagedList; + if ("all".equals(available)) { + pagedList = productDAO.findAll(request); + } else { + boolean availability = Boolean.parseBoolean(available); + pagedList = productDAO.findByAvailableOrderByName(availability, request); + } + return pagedList; + } + + @Transactional(readOnly = true) + @Override + public Product getProduct(long productId) { + return productDAO.findById(productId) + .orElseThrow(() -> new UnknownEntityException(Product.class, productId)); + } + + @Transactional(readOnly = true) + @Override + public Optional findById(long productId) { + return productDAO.findById(productId); + } + + @Transactional + @Override + public void create(Product product, String distilleryTitle) { + // Change for test: use isAvailable() instead of true + saveInternal(product, distilleryTitle, product.isAvailable()); + } + + @Transactional + @Override + public void update(long productId, Product product, String distilleryTitle) { + Product original = getProduct(productId); + product.setId(original.getId()); + saveInternal(product, distilleryTitle, original.isAvailable()); // keep original availability + } + + private void saveInternal(Product changed, String distilleryTitle, boolean available) { + Distillery distillery = distilleryService.findByTitle(distilleryTitle); + if (distillery != null) { + changed.setDistillery(distillery); + changed.setAvailable(available); + productDAO.save(changed); + } + } + + @Override + public void updateAvailability(Map> productIdsByAvailability) { + for (Map.Entry> e : productIdsByAvailability.entrySet()) { + Boolean targetAvailability = e.getKey(); + List productsToUpdate = e.getValue().stream() + .map(this::findById) + .filter(Optional::isPresent) + .map(Optional::get) + .filter(product -> product.isAvailable() != targetAvailability) + .collect(Collectors.toList()); + for (Product product : productsToUpdate) { + product.setAvailable(targetAvailability); + productDAO.save(product); + } + } + } + + @Transactional + @Override + public void delete(long product) { + productDAO.deleteById(product); + } +} diff --git a/sut-market/market-core/src/main/java/market/service/impl/RegionServiceImpl.java b/sut-market/market-core/src/main/java/market/service/impl/RegionServiceImpl.java new file mode 100644 index 0000000..aa84d3f --- /dev/null +++ b/sut-market/market-core/src/main/java/market/service/impl/RegionServiceImpl.java @@ -0,0 +1,62 @@ +package market.service.impl; + +import market.dao.RegionDAO; +import market.domain.Region; +import market.service.RegionService; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.Comparator; +import java.util.List; +import java.util.Optional; +import java.util.stream.Collectors; + +@Service +public class RegionServiceImpl implements RegionService { + private final RegionDAO regionDAO; + + public RegionServiceImpl(RegionDAO regionDAO) { + this.regionDAO = regionDAO; + } + + @Transactional(readOnly = true) + @Override + public List findAll() { + return regionDAO.findAll().stream() + .sorted(Comparator.comparing(Region::getName)) + .collect(Collectors.toList()); + } + + @Transactional(readOnly = true) + @Override + public Region findOne(long regionId) { + return regionDAO.findById(regionId).orElse(null); + } + + @Transactional(readOnly = true) + @Override + public Region findByName(String regionName) { + return regionDAO.findByName(regionName).orElse(null); + } + + @Transactional + @Override + public void create(Region newRegion) { + regionDAO.save(newRegion); + } + + @Override + public void update(long regionId, Region changedRegion) { + Optional originalOptional = regionDAO.findById(regionId); + if (originalOptional.isPresent()) { + changedRegion.setId(originalOptional.get().getId()); + regionDAO.save(changedRegion); + } + } + + @Transactional + @Override + public void delete(long regionId) { + regionDAO.deleteById(regionId); + } +} diff --git a/sut-market/market-core/src/main/java/market/service/impl/TestServiceImpl.java b/sut-market/market-core/src/main/java/market/service/impl/TestServiceImpl.java new file mode 100644 index 0000000..77bb885 --- /dev/null +++ b/sut-market/market-core/src/main/java/market/service/impl/TestServiceImpl.java @@ -0,0 +1,112 @@ +package market.service.impl; + +import java.io.Serializable; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.List; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ApplicationContext; +import org.springframework.stereotype.Service; + +import market.dao.CartDAO; +import market.dao.ContactsDAO; +import market.dao.DistilleryDAO; +import market.dao.OrderDAO; +import market.dao.OrderedProductDAO; +import market.dao.ProductDAO; +import market.dao.RegionDAO; +import market.dao.UserAccountDAO; +import market.domain.Cart; +import market.domain.Contacts; +import market.domain.Distillery; +import market.domain.Order; +import market.domain.OrderedProduct; +import market.domain.Product; +import market.domain.Region; +import market.domain.UserAccount; +import market.service.TestService; + +/* + * New class for test: clean the database (deleteAll) and get all data (getAll) + */ + +@Service +public class TestServiceImpl implements TestService { + + @Autowired + private ApplicationContext applicationContext; + + + public class AllData implements Serializable { + private static final long serialVersionUID = 1L; + public List cart=new ArrayList<>(); + public List contacts=new ArrayList<>(); + public List distillery=new ArrayList<>(); + public List order=new ArrayList<>(); + public List orderedProduct=new ArrayList<>(); + public List product=new ArrayList<>(); + public List region=new ArrayList<>(); + public List userAccount=new ArrayList<>(); + } + + private final CartDAO cartDAO; + private final ContactsDAO contactsDAO; + private final DistilleryDAO distilleryDAO; + private final OrderDAO orderDAO; + private final OrderedProductDAO orderedProductDAO; + private final ProductDAO productDAO; + private final RegionDAO regionDAO; + private final UserAccountDAO userAccountDAO; + + public TestServiceImpl(CartDAO cartDAO, ContactsDAO contactsDAO, DistilleryDAO distilleryDAO, OrderDAO orderDAO, OrderedProductDAO orderedProductDAO, + ProductDAO productDAO,RegionDAO regionDAO, UserAccountDAO userAccountDAO) { + this.cartDAO= cartDAO; + this.contactsDAO= contactsDAO; + this.distilleryDAO= distilleryDAO; + this.orderDAO=orderDAO; + this.orderedProductDAO=orderedProductDAO; + this.productDAO=productDAO; + this.regionDAO= regionDAO; + this.userAccountDAO= userAccountDAO; + } + + @Override + public void deleteAll() throws SQLException { + orderedProductDAO.deleteAll(); + orderDAO.deleteAll(); + cartDAO.deleteAll(); + contactsDAO.deleteAll(); + userAccountDAO.deleteAll(); + productDAO.deleteAll(); + distilleryDAO.deleteAll(); + regionDAO.deleteAll(); + DbTestUtil.resetAutoIncrementColumns(applicationContext, + "bill", + "ordered_product", + "customer_order", + "cart_item", + "cart", + "contacts", + "user_account", + "product", + "distillery", + "region"); + } + + @Override + public Object getAll() { + AllData data = new AllData(); + + data.region=regionDAO.findAll(); + data.distillery=distilleryDAO.findAll(); + data.product=productDAO.findAll(); + data.userAccount=userAccountDAO.findAll(); + data.contacts=contactsDAO.findAll(); + data.cart= cartDAO.findAll(); + data.order=orderDAO.findAll(); + data.orderedProduct=orderedProductDAO.findAll(); + + return data; + } +} diff --git a/sut-market/market-core/src/main/java/market/service/impl/UserAccountServiceImpl.java b/sut-market/market-core/src/main/java/market/service/impl/UserAccountServiceImpl.java new file mode 100644 index 0000000..75417af --- /dev/null +++ b/sut-market/market-core/src/main/java/market/service/impl/UserAccountServiceImpl.java @@ -0,0 +1,40 @@ +package market.service.impl; + +import market.dao.UserAccountDAO; +import market.domain.Cart; +import market.domain.UserAccount; +import market.exception.EmailExistsException; +import market.service.UserAccountService; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +public class UserAccountServiceImpl implements UserAccountService { + private final UserAccountDAO userAccountDAO; + + public UserAccountServiceImpl(UserAccountDAO userAccountDAO) { + this.userAccountDAO = userAccountDAO; + } + + @Transactional(readOnly = true) + @Override + public UserAccount findByEmail(String email) { // todo: return optional + return userAccountDAO.findByEmail(email); + } + + @Transactional + @Override + public UserAccount create(UserAccount userAccount) { + if (findByEmail(userAccount.getEmail()) != null) + throw new EmailExistsException(UserAccount.class); + + BCryptPasswordEncoder encoder = new BCryptPasswordEncoder(); + String hashedPassword = encoder.encode(userAccount.getPassword()); + userAccount.setPassword(hashedPassword); + userAccount.setCart(new Cart(userAccount)); + userAccountDAO.save(userAccount); + + return userAccount; + } +} diff --git a/sut-market/market-core/src/main/resources/data.sql b/sut-market/market-core/src/main/resources/data.sql new file mode 100644 index 0000000..d9d05b0 --- /dev/null +++ b/sut-market/market-core/src/main/resources/data.sql @@ -0,0 +1,90 @@ +INSERT INTO bill (id, number, date_created, total_cost, payed, cc_number) VALUES (1, 271320540, '2019-12-27', 8527, true, '1111222233334444'); + +INSERT INTO cart (id, total_items, products_cost, delivery_included) VALUES (5, 0, 0, true); +INSERT INTO cart (id, total_items, products_cost, delivery_included) VALUES (6, 0, 0, true); +INSERT INTO cart (id, total_items, products_cost, delivery_included) VALUES (7, 0, 0, true); +INSERT INTO cart (id, total_items, products_cost, delivery_included) VALUES (4, 1, 6517, true); + +INSERT INTO cart_item (cart_id, product_id, quantity) VALUES (4, 5, 1); + +INSERT INTO contacts (phone, address, id, city_region) VALUES ('+7 123 456 78 90', 'Riesstrasse 18', 4, '13'); +INSERT INTO contacts (phone, address, id, city_region) VALUES ('+79211234567', 'sdf', 7, '13'); + +INSERT INTO customer_order (id, user_account_id, date_created, executed, products_cost, delivery_included, delivery_cost) VALUES (1, 4, '2019-12-27', false, 8127, true, 400); + +INSERT INTO distillery (id, title, region_id, description) VALUES (2, 'Balvenie', 6, 'balvenie'); +INSERT INTO distillery (id, title, region_id, description) VALUES (6, 'Lagavulin', 4, 'lagavulin'); +INSERT INTO distillery (id, title, region_id, description) VALUES (7, 'Laphroaig', 4, 'laphroaig'); +INSERT INTO distillery (id, title, region_id, description) VALUES (1, 'Ardbeg', 4, 'ardbeg'); +INSERT INTO distillery (id, title, region_id, description) VALUES (3, 'Caol Ila', 4, 'caol ila'); +INSERT INTO distillery (id, title, region_id, description) VALUES (4, 'Dalwhinnie', 2, 'dalwhinnie'); +INSERT INTO distillery (id, title, region_id, description) VALUES (5, 'Glenkinchie', 5, 'glenkinchie'); +INSERT INTO distillery (id, title, region_id, description) VALUES (9, 'Talisker', 3, 'talisker'); +INSERT INTO distillery (id, title, region_id, description) VALUES (8, 'Springbank', 1, 'springbank'); + +INSERT INTO ordered_product (customer_order_id, product_id, quantity) VALUES (1, 8, 1); + +INSERT INTO product (id, name, distillery_id, age, alcohol, volume, price, description, available) VALUES (2, 'Uigeadail', 1, 0, 54.200000762939453, 700, 7020, 'Ardbeg Uigedael is named after Loch Uigedael, the lake in the grounds of the distillery Ardbeg which are the water is an important factor in the distilling process. The Uigedael a vatted malt, bottled at 54.2% ABV without cold filtering. Ardbeg Uigedael has no age indication for the expression consists of various malts of different ages. Malts are used partly matured in ex-bourbon and partly on ex-sherry casks.', true); +INSERT INTO product (id, name, distillery_id, age, alcohol, volume, price, description, available) VALUES (10, '10 y.o.', 9, 10, 45.799999237060547, 750, 4683, 'Powerful peat smoke with the salinity of sea water and the moisture of fresh oysters. Full sweetness of dried fruit with smoke and strong aromas of malted barley, warm and intense. Peppery in the mouth. Big, long, warming and peppery on the finish with an attractive sweetness.', true); +INSERT INTO product (id, name, distillery_id, age, alcohol, volume, price, description, available) VALUES (4, '12 y.o.', 3, 12, 43, 700, 4913, 'Smoked ham comes across delicately with citrus and cigar leaves. Fresh and smoky, probably the best part of this whisky. With an almost full body, oil and tar meet subtle smoke. Hints of sweet tastes like honey. Some spices like pepper combined with little sweetness.', true); +INSERT INTO product (id, name, distillery_id, age, alcohol, volume, price, description, available) VALUES (3, '12 y.o. Doublewood', 2, 12, 40, 700, 5403, 'Has clear influences from both bourbon and sherry wood. This malt has only 12 years aged in bourbon casks and then 3 months in young Oloroso casks. The peppery character from the bourbon barrels, penetrates, as it were by the rich and full aroma of Oloroso casks it. The Balvenie Double Wood is therefore a very complex malt.', true); +INSERT INTO product (id, name, distillery_id, age, alcohol, volume, price, description, available) VALUES (7, 'Quarter Cask', 7, 0, 48, 700, 5100, 'A vibrant young Laphroaig whose maturation has been accelerated by ageing in quarter casks. This shows soft sweetness and a velvety feel when first tasted, then the intense peatiness so unique to Laphroaig comes bursting through. A terrific whisky and great value.', true); +INSERT INTO product (id, name, distillery_id, age, alcohol, volume, price, description, available) VALUES (6, '16 y.o.', 6, 16, 43, 750, 6620, 'The Islay representative in the ''Classic Malts'' series is a deep, dry and exceptionally peaty bruiser. Probably the most pungent of all Islay malts, Lagavulin is not for the faint-hearted but inspires fanatical devotion in its many followers.', true); +INSERT INTO product (id, name, distillery_id, age, alcohol, volume, price, description, available) VALUES (8, '12 y.o. Cask Strength Batch 6', 8, 12, 53.099998474121094, 700, 8127, 'Like a storm gathering of the Kintyre coast, dark and ominous, yet tastes so good. The richness comes from the high percentage of sherry casks used in maturation. This is a truly classic Springbank, best enjoyed after dinner, or with your favourite cigar.', true); +INSERT INTO product (id, name, distillery_id, age, alcohol, volume, price, description, available) VALUES (5, '15 y.o.', 4, 15, 43, 750, 6517, 'A good introduction to the delights of single malt whisky – elegant, smooth and medium-bodied, with a light, fruity palate and a whiff of heather on the finish. Part of Diageo''s Classic Malt range. ', true); +INSERT INTO product (id, name, distillery_id, age, alcohol, volume, price, description, available) VALUES (1, 'Ten', 1, 10, 46, 700, 4420, 'Ten Years Old is the basis of the Ardbeg range. After 10 years of maturation in ex-bourbon casks, the whiskey bottled at 46% ABV without cold filtering. The characteristic peat, although clearly present but in perfect balance with the natural sweetness and not predominant in the taste.', false); +INSERT INTO product (id, name, distillery_id, age, alcohol, volume, price, description, available) VALUES (11, '12 y.o.', 5, 12, 43, 700, 4547, 'A light, delicate whiskey; sweet and creamy with a subtle floral aroma. This subtle, refined Lowland is ideal as an aperitif; try it straight from the fridge or freezer.', false); +INSERT INTO product (id, name, distillery_id, age, alcohol, volume, price, description, available) VALUES (9, '18 y.o.', 8, 18, 46, 700, 14490, 'This heavily sherried 18-year-old Springbank has been bottled by Mark Reynier as part of his Renegade series. Distilled in 1995, this sherry hogshead was aged for 10 years in Campbeltown and then moved to Bruichladdich for a further eight years.', true); + +INSERT INTO region (id, name, subtitle, color, description) VALUES (3, 'Island', '', 'blue', 'Scotch produced on the islands surrounding the mainland of Scotland offer a very diverse and different taste, they''re not however recognised by the Scotch Whisky Association but are easily grouped together for geographic reasons as one as they''re all islands. Although diverse in flavours, peat and salinity are found in all of the Islands whiskies, the latter because of the vicinity to the sea. +

    +
  • Number of distilleries: Under 10 +
  • Typical flavours: Smoke, Brine, Oil, Black Pepper and Honey +
'); +INSERT INTO region (id, name, subtitle, color, description) VALUES (4, 'Islay', '', 'black', 'The Scottish island of Islay (pronounced eye-luh) is located to the west of the mainland and is the smallest Whisky region in terms of area coverage in Scotland. Even though it''s a relatively small island, Islay is currently home to 8 distilleries, 3 of which are World famous, Ardbeg, Laphroaig and Lagavulin. The region is known for its peaty single malts and it''s believed that whisky distillation reached Scotland from Ireland via Islay in the 13th century, hence the high number of past and present distilleries on the island. +

    +
  • Number of distilleries: Under 10 +
  • Typical flavours: Seaweed, Brine, Carbolic Soap, Apple, Smoke and Kippers +
'); +INSERT INTO region (id, name, subtitle, color, description) VALUES (1, 'Campbeltown', '', 'purple', 'Campbeltown is part of mainland Scotland but it''s found at the foot of the Mull of Kintyre and was once a thriving whisky hotspot with over 34 distilleries, however it''s now home to just 3. A mixture of improved transportation links to the rival distilleries in the north and a decline in quality as distillers cut corners for mass-production resulting in an inferior product. +

    +
  • Number of distilleries: Under 5 +
  • Typical flavours: Brine, Smoke, Dried Fruit, Vanilla and Toffee +
'); +INSERT INTO region (id, name, subtitle, color, description) VALUES (6, 'Speyside', '', 'green', 'The region of Speyside is located in the north east of Scotland surrounding the River Spey, it''s a sub-region to the neighbouring Highlands because of the high density of distilleries in the area. It''s home to the highest number of distilleries in Scotland with well over 60 at present. Speyside is a protected region for Scotch Whisky distilling under UK Government legislation. +

    +
  • Number of distilleries: Over 60 +
  • Typical flavours: Apple, Vanilla, Oak, Malt, Nutmeg and Dried Fruit +
'); +INSERT INTO region (id, name, subtitle, color, description) VALUES (2, 'Highland', '', 'brown', 'The Highlands is Scotland''s largest whisky producing area, covering anywhere from the north of Glasgow (the Clyde estuary to the River Tay) all the way to Thurso in the north, not to mention the east and west regions excluding Speyside. Due to the large area, whisky in the Highlands is very diverse and offers a vast amount of different flavours so it''s hard to put a certain style on Whisky from this region. +

    +
  • Number of distilleries: Over 25 +
  • Typical flavours: Fruit Cake, Malt, Oak, Heather, Dried Fruit and Smoke +
'); +INSERT INTO region (id, name, subtitle, color, description) VALUES (5, 'Lowland', '', 'yellow', 'Lowlands is the second biggest whisky region in terms of the area it covers, but it''s currently only home to fewer than five distilleries. The Lowlands region covers the south of Scotland up to the north of Glasgow and Edinburgh where it meets the border on the Highlands, the line follows the old county borders running from the Clyde estuary in the west to the River Tay in the east, anything south of this is to the border with England is classified as the ''Lowlands'' in whisky terms. +

    +
  • Number of distilleries: Under 5 +
  • Typical flavours: Grass, Honeysuckle, Cream, Toffee, Toast and Cinnamon +
'); + +INSERT INTO role (id, title) VALUES (0, 'ROLE_ADMIN'); +INSERT INTO role (id, title) VALUES (1, 'ROLE_STAFF'); +INSERT INTO role (id, title) VALUES (2, 'ROLE_USER'); + +INSERT INTO storage (id, available) VALUES (1, true); +INSERT INTO storage (id, available) VALUES (2, true); +INSERT INTO storage (id, available) VALUES (3, true); +INSERT INTO storage (id, available) VALUES (4, true); +INSERT INTO storage (id, available) VALUES (5, true); +INSERT INTO storage (id, available) VALUES (6, true); +INSERT INTO storage (id, available) VALUES (7, true); +INSERT INTO storage (id, available) VALUES (8, true); +INSERT INTO storage (id, available) VALUES (9, true); +INSERT INTO storage (id, available) VALUES (10, true); +INSERT INTO storage (id, available) VALUES (11, true); + +INSERT INTO user_account (id, email, password, name, active) VALUES (1, 'admin', '$2a$10$Cmwx2Xr/PVpkibiiDz0s7eaVGZHPUvAu5ivdVC5BJgSYbp3c06FY6', 'Admin', true); +INSERT INTO user_account (id, email, password, name, active) VALUES (4, 'ivan.petrov@yandex.ru', '$2a$10$LfLg6vp4.wyowWP9ysg3F.yQ/udNKfRhGlHJ298xGCtBLC2dDX.OC', 'Ivan Petrov', true); + +INSERT INTO user_role (user_id, role_id) VALUES (1, 0); + diff --git a/sut-market/market-core/src/main/resources/messages.properties b/sut-market/market-core/src/main/resources/messages.properties new file mode 100644 index 0000000..1c9ef2f --- /dev/null +++ b/sut-market/market-core/src/main/resources/messages.properties @@ -0,0 +1,40 @@ +Error.Validation.Parameter=Argument validation error + +NotEmpty=The value shall not be empty +NotNull=The value cannot be null +NotExist=Requested entity doesn't exist +NotExist.id=No instance with this id + +Exists.email=Account with this email already exists + +NumberFormat=Value shall be a number +Positive=Value shall be a positive number +CreditCardNumber=Not a valid credit card number + +NotEmpty.cart.items=Cannot place the order: cart is empty +NotExist.product.productId=Requested product doesn't exist +NotEnough.product=Requested amount of product is not available + +Pattern.phone=Shall consist of a + sign, a country code, a region code (1-4 digits), and a number (6-7 digits) +Pattern.ccNumber=Card number shall consist of 13-16 digits +Size.phone=Length cannot exceed 20 characters +Size.address=Length cannot exceed 100 characters + +Size.userDTO.name=Length cannot exceed 50 characters +Size.userDTO.email=Length cannot exceed 50 characters +Size.userDTO.password=Length shall be between 6 and 50 characters +Pattern.userDTO.name=Only letters, whitespace, hyphen and apostrophe are allowed +Pattern.userDTO.email=The value shall be in the format of an email address +Pattern.userDTO.password=Password shall consist of Latin letters and numbers +Pattern.userDTO.address=Special characters are not allowed + +Pattern.region.name=Special characters are not allowed +Pattern.region.subtitle=Special characters are not allowed +Pattern.region.color=Only numbers, Latin letters, and hyphens are allowed + +Pattern.distillery.title=Special characters are not allowed + +Pattern.product.name=Special characters are not allowed +Max.product.age=Shall not exceed 2000 +Min.product.alcohol=Minimum volume of alcohol: 1.0% +Max.product.alcohol=Minimum volume of alcohol: 96.0% \ No newline at end of file diff --git a/sut-market/market-core/src/main/resources/messages_ru.properties b/sut-market/market-core/src/main/resources/messages_ru.properties new file mode 100644 index 0000000..5df2407 --- /dev/null +++ b/sut-market/market-core/src/main/resources/messages_ru.properties @@ -0,0 +1,40 @@ +Error.Validation.Parameter=Ошибка валидации параметра + +NotEmpty=Поле не должно быть пустым +NotNull=Значение не может быть null +NotExist=Запрошенный ресурс не существует +NotExist.id=Нет экземпляра с запрошенным идентификатором + +Exists.email=Пользователь с таким адресом электронной почты уже существует + +NumberFormat=Значение должно быть числом +Positive=Значение должно быть положительным числом +CreditCardNumber=Некорректный номер кредитной карты + +NotEmpty.cart.items=Невозможно оформить заказ: корзина пуста +NotExist.product.productId=Запрошенный товар не существует +NotEnough.product=Запрошенное количество товара отсутствует + +Pattern.phone=Должен состоять из знака +, кода страны, кода региона (1-4 цифр) и номера (6-7 цифр) +Pattern.ccNumber=Номер карты должен состоять из 13-16 цифр +Size.phone=Длина не должна превышать 20 символов +Size.address=Длина не должна превышать 100 символов + +Size.userDTO.name=Длина не должна превышать 50 символов +Size.userDTO.email=Длина не должна превышать 50 символов +Size.userDTO.password=Длина должна составлять от 6 до 50 символов +Pattern.userDTO.name=В имени допустимы только буквы, пробел, дефис и апостроф +Pattern.userDTO.email=Значение поля должно иметь формат адреса электронной почты +Pattern.userDTO.password=Пароль должен состоять из цифр и латинских букв +Pattern.userDTO.address=Специальные символы недопустимы + +Pattern.region.name=Специальные символы недопустимы +Pattern.region.subtitle=Специальные символы недопустимы +Pattern.region.color=Допустимы только цифры, латинские буквы и дефис + +Pattern.distillery.title=Специальные символы недопустимы + +Pattern.product.name=Специальные символы недопустимы +Max.product.age=Значение не должно превышать 2000 +Min.product.alcohol=Минимальная объёмная доля алкоголя: 1.0% +Max.product.alcohol=Максимальная объёмная доля алкоголя: 96.0% \ No newline at end of file diff --git a/sut-market/market-core/src/main/resources/schema.sql b/sut-market/market-core/src/main/resources/schema.sql new file mode 100644 index 0000000..6ccc45a --- /dev/null +++ b/sut-market/market-core/src/main/resources/schema.sql @@ -0,0 +1,107 @@ +DROP TABLE IF EXISTS bill; +CREATE TABLE bill ( + id bigserial NOT NULL, + number integer, + date_created date, + total_cost integer, + payed boolean, + cc_number character varying(19) +); + +DROP TABLE IF EXISTS cart; +CREATE TABLE cart ( + id bigserial NOT NULL, + total_items integer, + products_cost integer, + delivery_included boolean +); + +DROP TABLE IF EXISTS cart_item; +CREATE TABLE cart_item ( + cart_id bigint, + product_id bigint, + quantity integer +); + +DROP TABLE IF EXISTS contacts; +CREATE TABLE contacts ( + id bigserial NOT NULL, + phone character varying(20), + address character varying(100), + city_region character varying(50) +); + +DROP TABLE IF EXISTS customer_order; +CREATE TABLE customer_order ( + id bigint NOT NULL, + user_account_id bigint, + date_created date, + executed boolean, + products_cost integer, + delivery_included boolean, + delivery_cost integer +); + +DROP TABLE IF EXISTS distillery; +CREATE TABLE distillery ( + id bigserial NOT NULL, + title character varying(25), + region_id bigint, + description character varying(1000) +); + +DROP TABLE IF EXISTS ordered_product; +CREATE TABLE ordered_product ( + customer_order_id bigint, + product_id bigint, + quantity integer +); + +DROP TABLE IF EXISTS product; +CREATE TABLE product ( + id bigserial NOT NULL, + name character varying(45), + distillery_id bigint, + age smallint, + alcohol double precision, + volume integer, + price double precision, + description character varying(1000), + available boolean +); + +DROP TABLE IF EXISTS region; +CREATE TABLE region ( + id bigserial NOT NULL, + name character varying(20) NOT NULL, + subtitle character varying(20), + color character varying(10), + description character varying(1000) +); + +DROP TABLE IF EXISTS role; +CREATE TABLE role ( + id bigint NOT NULL, + title character varying(20) +); + +DROP TABLE IF EXISTS storage; +CREATE TABLE storage ( + id bigserial NOT NULL, + available boolean +); + +DROP TABLE IF EXISTS user_account; +CREATE TABLE user_account ( + id bigserial NOT NULL, + email character varying(50), + password character varying(255), + name character varying(50), + active boolean +); + +DROP TABLE IF EXISTS user_role; +CREATE TABLE user_role ( + user_id bigint, + role_id bigint +); diff --git a/sut-market/market-core/src/test/java/market/service/CartServiceTest.java b/sut-market/market-core/src/test/java/market/service/CartServiceTest.java new file mode 100644 index 0000000..41284f9 --- /dev/null +++ b/sut-market/market-core/src/test/java/market/service/CartServiceTest.java @@ -0,0 +1,250 @@ +package market.service; + +import market.FixturesFactory; +import market.dao.CartDAO; +import market.domain.Cart; +import market.domain.CartItem; +import market.domain.Distillery; +import market.domain.Product; +import market.domain.Region; +import market.domain.UserAccount; +import market.exception.UnknownEntityException; +import market.service.impl.CartServiceImpl; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import java.util.Collections; +import java.util.List; +import java.util.Optional; + +import static org.hamcrest.CoreMatchers.equalTo; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +public class CartServiceTest { + + @Mock + private CartDAO cartDAO; + @Mock + private UserAccountService userAccountService; + @Mock + private ProductService productService; + + private CartService cartService; + private Cart cart; + private Product product; + private UserAccount userAccount; + + @BeforeEach + public void setUp() { + UserAccount.Builder accountBuilder = FixturesFactory.account(cart); + cart = new Cart.Builder() + .setId(accountBuilder.getId()) + .build(); + Region region = FixturesFactory.region().build(); + Distillery distillery = FixturesFactory.distillery(region).build(); + product = FixturesFactory.product(distillery).build(); + userAccount = accountBuilder.build(); + cart.setUserAccount(userAccount); + + cartService = new CartServiceImpl(cartDAO, userAccountService, productService); + } + + @Test + public void getCartOrCreate_ExistingCart() { + when(userAccountService.findByEmail(userAccount.getEmail())) + .thenReturn(userAccount); + when(cartDAO.findById(userAccount.getId())) + .thenReturn(Optional.of(cart)); + + Cart createdCart = cartService.getCartOrCreate(userAccount.getEmail()); + + verify(cartDAO, never()).save(any(Cart.class)); + assertThat(createdCart, equalTo(cart)); + } + + @Test + public void getCartOrCreate_AbsentCart() { + when(userAccountService.findByEmail(userAccount.getEmail())) + .thenReturn(userAccount); + when(cartDAO.save(any(Cart.class))) + .thenReturn(cart); + when(cartDAO.findById(userAccount.getId())) + .thenReturn(Optional.empty()); + + Cart createdCart = cartService.getCartOrCreate(userAccount.getEmail()); + + verify(cartDAO).save(any(Cart.class)); + assertThat(createdCart, equalTo(cart)); + } + + @Test + public void addToCart_Normal() { + when(userAccountService.findByEmail(userAccount.getEmail())) + .thenReturn(userAccount); + when(cartDAO.save(any(Cart.class))) + .thenReturn(cart); + when(cartDAO.findById(userAccount.getId())) + .thenReturn(Optional.of(cart)); + when(productService.getProduct(product.getId())) + .thenReturn(product); + int quantity = 3; + cart.update(product, quantity); + + Cart updatedCart = cartService.addToCart(userAccount.getEmail(), product.getId(), quantity); + + verify(cartDAO).save(cart); + assertThat(updatedCart.getItemsCount(), equalTo(1)); + assertThat(updatedCart.getItemsCost(), equalTo(quantity * product.getPrice())); + List cartItems = updatedCart.getCartItems(); + assertThat(cartItems.size(), equalTo(1)); + assertThat(cartItems.get(0).getProduct(), equalTo(product)); + assertThat(cartItems.get(0).getQuantity(), equalTo(quantity)); + } + + @Test + public void addToCart_UnavailableProduct() { + when(userAccountService.findByEmail(userAccount.getEmail())) + .thenReturn(userAccount); + when(cartDAO.findById(userAccount.getId())) + .thenReturn(Optional.of(cart)); + when(productService.getProduct(product.getId())) + .thenReturn(product); + product.setAvailable(false); + + Cart updatedCart = cartService.addToCart(userAccount.getEmail(), product.getId(), 3); + + verify(cartDAO, never()).save(any(Cart.class)); + assertThat(updatedCart.isEmpty(), equalTo(true)); + } + + @Test + public void addToCart_AbsentProduct() { + when(userAccountService.findByEmail(userAccount.getEmail())) + .thenReturn(userAccount); + when(cartDAO.findById(userAccount.getId())) + .thenReturn(Optional.of(cart)); + when(productService.getProduct(product.getId())).thenThrow(UnknownEntityException.class); + + assertThrows(UnknownEntityException.class, () -> cartService.addToCart(userAccount.getEmail(), product.getId(), 3)); + verify(cartDAO, never()).save(any(Cart.class)); + } + + @Test + public void addAllToCart_Normal() { + when(userAccountService.findByEmail(userAccount.getEmail())) + .thenReturn(userAccount); + when(cartDAO.save(any(Cart.class))) + .thenReturn(cart); + when(cartDAO.findById(userAccount.getId())) + .thenReturn(Optional.of(cart)); + when(productService.findById(product.getId())) + .thenReturn(Optional.of(product)); + int quantity = 3; + cart.update(product, quantity); + CartItem cartItem = new CartItem(cart, product, quantity); + + Cart updatedCart = cartService.addAllToCart(userAccount.getEmail(), Collections.singletonList(cartItem)); + + verify(cartDAO).save(cart); + assertThat(updatedCart.getItemsCount(), equalTo(1)); + assertThat(updatedCart.getItemsCost(), equalTo(quantity * product.getPrice())); + List cartItems = updatedCart.getCartItems(); + assertThat(cartItems.size(), equalTo(1)); + assertThat(cartItems.get(0).getProduct(), equalTo(product)); + assertThat(cartItems.get(0).getQuantity(), equalTo(quantity)); + } + + @Test + public void addAllToCart_UnavailableProduct() { + when(userAccountService.findByEmail(userAccount.getEmail())) + .thenReturn(userAccount); + when(cartDAO.findById(userAccount.getId())) + .thenReturn(Optional.of(cart)); + when(productService.findById(product.getId())) + .thenReturn(Optional.of(product)); + product.setAvailable(false); + CartItem cartItem = new CartItem(cart, product, 3); + + Cart updatedCart = cartService.addAllToCart(userAccount.getEmail(), Collections.singletonList(cartItem)); + + verify(cartDAO, never()).save(any(Cart.class)); + assertThat(updatedCart.isEmpty(), equalTo(true)); + } + + @Test + public void addAllToCart_AbsentProduct() { + when(userAccountService.findByEmail(userAccount.getEmail())) + .thenReturn(userAccount); + when(cartDAO.findById(userAccount.getId())) + .thenReturn(Optional.of(cart)); + when(productService.findById(product.getId())) + .thenReturn(Optional.empty()); + CartItem cartItem = new CartItem(cart, product, 3); + + Cart updatedCart = cartService.addAllToCart(userAccount.getEmail(), Collections.singletonList(cartItem)); + + verify(cartDAO, never()).save(any(Cart.class)); + assertThat(updatedCart.isEmpty(), equalTo(true)); + } + + @Test + public void setDelivery() { + when(userAccountService.findByEmail(userAccount.getEmail())) + .thenReturn(userAccount); + when(cartDAO.save(any(Cart.class))) + .thenReturn(cart); + when(cartDAO.findById(userAccount.getId())) + .thenReturn(Optional.of(cart)); + + Cart updatedCart = cartService.setDelivery(userAccount.getEmail(), true); + + verify(cartDAO, times(1)).save(any(Cart.class)); + assertThat(updatedCart.isDeliveryIncluded(), equalTo(true)); + + updatedCart = cartService.setDelivery(userAccount.getEmail(), false); + + verify(cartDAO, times(2)).save(any(Cart.class)); + assertThat(updatedCart.isDeliveryIncluded(), equalTo(false)); + } + + @Test + public void clearCart_EmptyCart() { + when(userAccountService.findByEmail(userAccount.getEmail())) + .thenReturn(userAccount); + when(cartDAO.save(any(Cart.class))) + .thenReturn(cart); + when(cartDAO.findById(userAccount.getId())) + .thenReturn(Optional.of(cart)); + + Cart clearedCart = cartService.clearCart(userAccount.getEmail()); + + verify(cartDAO).save(cart); + assertThat(clearedCart.isEmpty(), equalTo(true)); + } + + @Test + public void clearCart_FullCart() { + when(userAccountService.findByEmail(userAccount.getEmail())) + .thenReturn(userAccount); + when(cartDAO.save(any(Cart.class))) + .thenReturn(cart); + when(cartDAO.findById(userAccount.getId())) + .thenReturn(Optional.of(cart)); + cart.update(product, 3); + + Cart clearedCart = cartService.clearCart(userAccount.getEmail()); + + verify(cartDAO).save(cart); + assertThat(clearedCart.isEmpty(), equalTo(true)); + } +} diff --git a/sut-market/market-core/src/test/java/market/service/ContactsServiceTest.java b/sut-market/market-core/src/test/java/market/service/ContactsServiceTest.java new file mode 100644 index 0000000..0bd7cc8 --- /dev/null +++ b/sut-market/market-core/src/test/java/market/service/ContactsServiceTest.java @@ -0,0 +1,74 @@ +package market.service; + +import market.FixturesFactory; +import market.dao.ContactsDAO; +import market.domain.Contacts; +import market.domain.UserAccount; +import market.service.impl.ContactsServiceImpl; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Captor; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import static org.hamcrest.CoreMatchers.equalTo; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.mockito.Mockito.*; + +@ExtendWith(MockitoExtension.class) +public class ContactsServiceTest { + + @Mock + private ContactsDAO contactsDAO; + @Mock + private UserAccountService userAccountService; + + @Captor + private ArgumentCaptor contactsCaptor; + + private ContactsService contactsService; + private Contacts contacts; + private UserAccount userAccount; + + @BeforeEach + public void setUp() { + contacts = FixturesFactory.contacts().build(); + userAccount = FixturesFactory.account() + .setContacts(contacts) + .build(); + contacts.setUserAccount(userAccount); + + when(userAccountService.findByEmail(userAccount.getEmail())) + .thenReturn(userAccount); + + contactsService = new ContactsServiceImpl(contactsDAO, userAccountService); + } + + @Test + public void getContacts() { + when(contactsDAO.findByUserAccount(any(UserAccount.class))) + .thenReturn(contacts); + + Contacts retrievedContacts = contactsService.getContacts(userAccount.getEmail()); + + verify(contactsDAO).findByUserAccount(any(UserAccount.class)); + assertThat(retrievedContacts, equalTo(contacts)); + } + + @Test + public void updateUserContacts() { + Contacts changedContacts = new Contacts.Builder(contacts) + .setPhone(contacts.getPhone() + "_changed") + .setAddress(contacts.getAddress() + "_changed") + .build(); + when(contactsDAO.findByUserAccount(any(UserAccount.class))) + .thenReturn(contacts); + + contactsService.updateUserContacts(changedContacts, userAccount.getEmail()); + + verify(contactsDAO).save(contactsCaptor.capture()); + assertThat(contactsCaptor.getValue(), equalTo(changedContacts)); + } +} diff --git a/sut-market/market-core/src/test/java/market/service/DistilleryServiceTest.java b/sut-market/market-core/src/test/java/market/service/DistilleryServiceTest.java new file mode 100644 index 0000000..05a0263 --- /dev/null +++ b/sut-market/market-core/src/test/java/market/service/DistilleryServiceTest.java @@ -0,0 +1,126 @@ +package market.service; + +import market.FixturesFactory; +import market.dao.DistilleryDAO; +import market.domain.Distillery; +import market.domain.Region; +import market.service.impl.DistilleryServiceImpl; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Captor; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import java.util.Collections; +import java.util.List; +import java.util.Optional; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.contains; +import static org.hamcrest.Matchers.equalTo; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +public class DistilleryServiceTest { + + @Mock + private DistilleryDAO distilleryDAO; + @Mock + private RegionService regionService; + + @Captor + private ArgumentCaptor distilleryCaptor; + @Captor + private ArgumentCaptor longCaptor; + + private DistilleryService distilleryService; + private Distillery distillery; + private Region region; + + @BeforeEach + public void setUp() { + region = FixturesFactory.region().build(); + distillery = FixturesFactory.distillery(region).build(); + distilleryService = new DistilleryServiceImpl(regionService, distilleryDAO); + } + + @Test + public void findAll() { + when(distilleryDAO.findAll()) + .thenReturn(Collections.singletonList(distillery)); + + List retrieved = distilleryService.findAll(); + + assertThat(retrieved, contains(distillery)); + } + + @Test + public void findByRegion() { + when(distilleryDAO.findByRegionOrderByTitleAsc(any(Region.class))) + .thenReturn(Collections.singletonList(distillery)); + + List retrieved = distilleryService.findByRegion(region); + + assertThat(retrieved, contains(distillery)); + } + + @Test + public void findById() { + when(distilleryDAO.findById(distillery.getId())) + .thenReturn(Optional.of(distillery)); + + Distillery retrieved = distilleryService.findById(distillery.getId()); + + assertThat(retrieved, equalTo(distillery)); + } + + @Test + public void findByTitle() { + when(distilleryDAO.findByTitle(distillery.getTitle())) + .thenReturn(distillery); + + Distillery retrieved = distilleryService.findByTitle(distillery.getTitle()); + + assertThat(retrieved, equalTo(distillery)); + } + + @Test + public void create() { + when(regionService.findByName(region.getName())) + .thenReturn(region); + + distilleryService.create(distillery, region.getName()); + + verify(distilleryDAO).save(distilleryCaptor.capture()); + assertThat(distilleryCaptor.getValue(), equalTo(distillery)); + } + + @Test + public void update() { + Distillery changedDistillery = new Distillery.Builder(distillery) + .setTitle(distillery.getTitle() + "_changed") + .setDescription(distillery.getDescription() + "_changed") + .build(); + when(regionService.findByName(region.getName())) + .thenReturn(region); + when(distilleryDAO.findById(distillery.getId())) + .thenReturn(Optional.of(distillery)); + + distilleryService.update(distillery.getId(), changedDistillery, distillery.getRegion().getName()); + + verify(distilleryDAO).save(distilleryCaptor.capture()); + assertThat(distilleryCaptor.getValue(), equalTo(changedDistillery)); + } + + @Test + public void delete() { + distilleryService.delete(distillery.getId()); + + verify(distilleryDAO).deleteById(longCaptor.capture()); + assertThat(longCaptor.getValue(), equalTo(distillery.getId())); + } +} diff --git a/sut-market/market-core/src/test/java/market/service/OrderServiceTest.java b/sut-market/market-core/src/test/java/market/service/OrderServiceTest.java new file mode 100644 index 0000000..e48dea0 --- /dev/null +++ b/sut-market/market-core/src/test/java/market/service/OrderServiceTest.java @@ -0,0 +1,155 @@ +package market.service; + +import market.FixturesFactory; +import market.dao.OrderDAO; +import market.domain.Bill; +import market.domain.Cart; +import market.domain.Distillery; +import market.domain.Order; +import market.domain.OrderedProduct; +import market.domain.Product; +import market.domain.Region; +import market.domain.UserAccount; +import market.service.impl.OrderServiceImpl; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Captor; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import java.time.Instant; +import java.time.LocalDate; +import java.time.ZoneId; +import java.time.ZonedDateTime; +import java.util.Collections; +import java.util.Date; +import java.util.List; +import java.util.Optional; +import java.util.Set; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.contains; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.hasSize; +import static org.hamcrest.Matchers.is; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +public class OrderServiceTest { + private static final String CARD_NUMBER = "1234132412341234"; + + @Mock + private OrderDAO orderDAO; + @Mock + private UserAccountService userAccountService; + @Mock + private CartService cartService; + + @Captor + private ArgumentCaptor orderCaptor; + + private OrderService orderService; + private UserAccount userAccount; + private Order order; + private Cart cart; + private Product product; + + private static LocalDate toDate(Date date) { + long sourceTime = date.getTime(); + ZonedDateTime zdt = ZonedDateTime.ofInstant(Instant.ofEpochMilli(sourceTime), ZoneId.of("GMT")); + return zdt.toLocalDate(); + } + + @BeforeEach + public void setUp() { + userAccount = FixturesFactory.account().build(); + order = FixturesFactory.order(userAccount).build(); + Region region = FixturesFactory.region().build(); + Distillery distillery = FixturesFactory.distillery(region).build(); + product = FixturesFactory.product(distillery).build(); + cart = new Cart.Builder() + .setId(userAccount.getId()) + .setUserAccount(userAccount) + .build(); + orderService = new OrderServiceImpl(orderDAO, userAccountService, cartService); + } + + @Test + public void getUserOrders() { + when(userAccountService.findByEmail(userAccount.getEmail())) + .thenReturn(userAccount); + when(orderDAO.findByUserAccountOrderByDateCreatedDesc(userAccount)) + .thenReturn(Collections.singletonList(order)); + + List retrieved = orderService.getUserOrders(userAccount.getEmail()); + + assertThat(retrieved, contains(order)); + } + + @Test + public void getUserOrder() { + when(orderDAO.findById(order.getId())) + .thenReturn(Optional.of(order)); + + Optional retrieved = orderService.getUserOrder(userAccount.getEmail(), order.getId()); + + assertThat(retrieved.isPresent(), is(true)); + assertThat(retrieved.get(), equalTo(order)); + } + + @Test + public void createUserOrder() { + int quantity = 3; + cart.update(product, quantity); + int deliveryCost = 300; + double productsCost = quantity * product.getPrice(); + double totalCost = productsCost + deliveryCost; + when(cartService.getCartOrCreate(userAccount.getEmail())) + .thenReturn(cart); + when(userAccountService.findByEmail(userAccount.getEmail())) + .thenReturn(userAccount); + + Order createdOrder = orderService.createUserOrder(userAccount.getEmail(), deliveryCost, CARD_NUMBER); + + assertThat(createdOrder.getUserAccount(), equalTo(userAccount)); + assertThat(createdOrder.getProductsCost(), equalTo(productsCost)); + assertThat(createdOrder.isDeliveryIncluded(), equalTo(true)); + assertThat(createdOrder.getDeliveryCost(), equalTo(deliveryCost)); + assertThat(createdOrder.isExecuted(), equalTo(false)); + assertThat(toDate(createdOrder.getDateCreated()), equalTo(LocalDate.now())); + + Bill bill = createdOrder.getBill(); + assertThat(bill.getOrder(), equalTo(createdOrder)); + assertThat(bill.getTotalCost(), equalTo(totalCost)); + assertThat(bill.isPayed(), equalTo(true)); + assertThat(toDate(bill.getDateCreated()), equalTo(LocalDate.now())); + assertThat(bill.getCcNumber(), equalTo(CARD_NUMBER)); + + Set orderedProducts = createdOrder.getOrderedProducts(); + assertThat(orderedProducts, hasSize(1)); + OrderedProduct orderedProduct = orderedProducts.iterator().next(); + assertThat(orderedProduct.getOrder(), equalTo(createdOrder)); + assertThat(orderedProduct.getProduct(), equalTo(product)); + assertThat(orderedProduct.getQuantity(), equalTo(quantity)); + } + + @Test + public void updateStatus() { + when(orderDAO.findById(order.getId())) + .thenReturn(Optional.of(order)); + + orderService.updateStatus(order.getId(), true); + + verify(orderDAO).save(orderCaptor.capture()); + assertThat(orderCaptor.getValue().isExecuted(), equalTo(true)); + + orderService.updateStatus(order.getId(), false); + + verify(orderDAO, times(2)).save(orderCaptor.capture()); + assertThat(orderCaptor.getValue().isExecuted(), equalTo(false)); + } +} diff --git a/sut-market/market-core/src/test/java/market/service/ProductServiceTest.java b/sut-market/market-core/src/test/java/market/service/ProductServiceTest.java new file mode 100644 index 0000000..8d6f249 --- /dev/null +++ b/sut-market/market-core/src/test/java/market/service/ProductServiceTest.java @@ -0,0 +1,188 @@ +package market.service; + +import market.FixturesFactory; +import market.dao.ProductDAO; +import market.domain.Distillery; +import market.domain.Product; +import market.domain.Region; +import market.service.impl.ProductServiceImpl; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Captor; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageImpl; +import org.springframework.data.domain.PageRequest; + +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.contains; +import static org.hamcrest.Matchers.equalTo; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +public class ProductServiceTest { + + @Mock + private ProductDAO productDAO; + @Mock + private DistilleryService distilleryService; + + @Captor + private ArgumentCaptor productCaptor; + @Captor + private ArgumentCaptor longCaptor; + + private ProductService productService; + private Product product; + private Distillery distillery; + private Region region; + private PageRequest pageRequest; + + @BeforeEach + public void setUp() { + region = FixturesFactory.region().build(); + distillery = FixturesFactory.distillery(region).build(); + product = FixturesFactory.product(distillery).build(); + pageRequest = PageRequest.of(1, 1); + + productService = new ProductServiceImpl(productDAO, distilleryService); + } + + @Test + public void findAll() { + when(productDAO.findAll()) + .thenReturn(Collections.singletonList(product)); + + List retrieved = productService.findAll(); + + assertThat(retrieved, contains(product)); + } + + @Test + public void findAll_Paged() { + Page productPage = new PageImpl<>(Collections.singletonList(product)); + when(productDAO.findAll(pageRequest)) + .thenReturn(productPage); + + Page retrieved = productService.findAll(pageRequest); + + assertThat(retrieved, contains(product)); + } + + @Test + public void findByDistillery() { + Page productPage = new PageImpl<>(Collections.singletonList(product)); + when(productDAO.findByDistilleryOrderByName(distillery, pageRequest)) + .thenReturn(productPage); + + Page retrieved = productService.findByDistillery(distillery, pageRequest); + + assertThat(retrieved, contains(product)); + } + + @Test + public void findByRegion() { + Page productPage = new PageImpl<>(Collections.singletonList(product)); + when(productDAO.findByRegionOrderByName(region, pageRequest)) + .thenReturn(productPage); + + Page retrieved = productService.findByRegion(region, pageRequest); + + assertThat(retrieved, contains(product)); + } + + @Test + public void findByAvailability() { + boolean available = true; + Page productPage = new PageImpl<>(Collections.singletonList(product)); + when(productDAO.findByAvailableOrderByName(available, pageRequest)) + .thenReturn(productPage); + + Page retrieved = productService.findByAvailability(Boolean.toString(available), pageRequest); + + assertThat(retrieved, contains(product)); + } + + @Test + public void getProduct() { + when(productDAO.findById(product.getId())) + .thenReturn(Optional.of(product)); + + Product retrieved = productService.getProduct(product.getId()); + + assertThat(retrieved, equalTo(product)); + } + + @Test + public void findOne() { + Optional productOptional = Optional.of(product); + when(productDAO.findById(product.getId())) + .thenReturn(productOptional); + + Optional retrieved = productService.findById(product.getId()); + + assertThat(retrieved, equalTo(productOptional)); + } + + @Test + public void create() { + when(distilleryService.findByTitle(distillery.getTitle())) + .thenReturn(distillery); + + productService.create(product, distillery.getTitle()); + + verify(productDAO).save(productCaptor.capture()); + assertThat(productCaptor.getValue(), equalTo(product)); + } + + @Test + public void update() { + Product changedProduct = new Product.Builder(product) + .setPrice(product.getPrice() + 50) + .build(); + when(distilleryService.findByTitle(distillery.getTitle())) + .thenReturn(distillery); + when(productDAO.findById(product.getId())) + .thenReturn(Optional.of(product)); + + productService.update(product.getId(), changedProduct, distillery.getTitle()); + + verify(productDAO).save(productCaptor.capture()); + assertThat(productCaptor.getValue(), equalTo(changedProduct)); + } + + @Test + public void updateAvailability() { + boolean updatedAvailability = !product.isAvailable(); + Product expectedProduct = new Product.Builder(product) + .setAvailable(updatedAvailability) + .build(); + Map> changes = new HashMap<>(); + changes.put(updatedAvailability, Collections.singletonList(product.getId())); + when(productDAO.findById(product.getId())) + .thenReturn(Optional.of(product)); + + productService.updateAvailability(changes); + + verify(productDAO).save(productCaptor.capture()); + assertThat(productCaptor.getValue(), equalTo(expectedProduct)); + } + + @Test + public void delete() { + productService.delete(product.getId()); + + verify(productDAO).deleteById(longCaptor.capture()); + assertThat(longCaptor.getValue(), equalTo(product.getId())); + } +} diff --git a/sut-market/market-core/src/test/java/market/service/RegionServiceTest.java b/sut-market/market-core/src/test/java/market/service/RegionServiceTest.java new file mode 100644 index 0000000..100f58b --- /dev/null +++ b/sut-market/market-core/src/test/java/market/service/RegionServiceTest.java @@ -0,0 +1,104 @@ +package market.service; + +import market.FixturesFactory; +import market.dao.RegionDAO; +import market.domain.Region; +import market.service.impl.RegionServiceImpl; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Captor; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import java.util.Collections; +import java.util.List; +import java.util.Optional; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.contains; +import static org.hamcrest.Matchers.equalTo; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +public class RegionServiceTest { + + @Mock + private RegionDAO regionDAO; + + @Captor + private ArgumentCaptor regionCaptor; + @Captor + private ArgumentCaptor longCaptor; + + private RegionService regionService; + private Region region; + + @BeforeEach + public void setUp() { + region = FixturesFactory.region().build(); + regionService = new RegionServiceImpl(regionDAO); + } + + @Test + public void findAll() { + when(regionDAO.findAll()) + .thenReturn(Collections.singletonList(region)); + + List retrieved = regionService.findAll(); + + assertThat(retrieved, contains(region)); + } + + @Test + public void findOne() { + when(regionDAO.findById(region.getId())) + .thenReturn(Optional.of(region)); + + Region retrieved = regionService.findOne(region.getId()); + + assertThat(retrieved, equalTo(region)); + } + + @Test + public void findByName() { + when(regionDAO.findByName(region.getName())) + .thenReturn(Optional.of(region)); + + Region retrieved = regionService.findByName(region.getName()); + + assertThat(retrieved, equalTo(region)); + } + + @Test + public void create() { + regionService.create(region); + + verify(regionDAO).save(regionCaptor.capture()); + assertThat(regionCaptor.getValue(), equalTo(region)); + } + + @Test + public void update() { + Region changedRegion = new Region.Builder(region) + .setName(region.getName() + "_changed") + .build(); + when(regionDAO.findById(region.getId())) + .thenReturn(Optional.of(region)); + + regionService.update(changedRegion.getId(), changedRegion); + + verify(regionDAO).save(regionCaptor.capture()); + assertThat(regionCaptor.getValue(), equalTo(changedRegion)); + } + + @Test + public void delete() { + regionService.delete(region.getId()); + + verify(regionDAO).deleteById(longCaptor.capture()); + assertThat(longCaptor.getValue(), equalTo(region.getId())); + } +} diff --git a/sut-market/market-core/src/test/java/market/service/UserAccountServiceTest.java b/sut-market/market-core/src/test/java/market/service/UserAccountServiceTest.java new file mode 100644 index 0000000..5946ae7 --- /dev/null +++ b/sut-market/market-core/src/test/java/market/service/UserAccountServiceTest.java @@ -0,0 +1,58 @@ +package market.service; + +import market.FixturesFactory; +import market.dao.UserAccountDAO; +import market.domain.UserAccount; +import market.service.impl.UserAccountServiceImpl; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Captor; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.equalTo; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +public class UserAccountServiceTest { + + @Mock + private UserAccountDAO userAccountDAO; + + @Captor + private ArgumentCaptor userAccountCaptor; + + private UserAccountService userAccountService; + private UserAccount userAccount; + + @BeforeEach + public void setUp() { + userAccount = FixturesFactory.account().build(); + userAccountService = new UserAccountServiceImpl(userAccountDAO); + } + + @Test + public void findByEmail() { + when(userAccountDAO.findByEmail(userAccount.getEmail())) + .thenReturn(userAccount); + + UserAccount retrieved = userAccountService.findByEmail(userAccount.getEmail()); + + assertThat(retrieved, equalTo(userAccount)); + } + + @Test + public void create() { + when(userAccountDAO.findByEmail(userAccount.getEmail())) + .thenReturn(null); + + userAccountService.create(userAccount); + + verify(userAccountDAO).save(userAccountCaptor.capture()); + assertThat(userAccountCaptor.getValue(), equalTo(userAccount)); + } +} diff --git a/sut-market/market-coverage/.gitignore b/sut-market/market-coverage/.gitignore new file mode 100644 index 0000000..b83d222 --- /dev/null +++ b/sut-market/market-coverage/.gitignore @@ -0,0 +1 @@ +/target/ diff --git a/sut-market/market-coverage/pom.xml b/sut-market/market-coverage/pom.xml new file mode 100644 index 0000000..a8fa0ea --- /dev/null +++ b/sut-market/market-coverage/pom.xml @@ -0,0 +1,135 @@ + + + + market + net.lukyanets + 0.1.2 + .. + + 4.0.0 + + market-coverage + + + 1.8 + + ../market-core/target + ../market-web/target + ../market-rest/target + + ../market-core/target/classes + ../market-web/target/classes + ../market-rest/target/classes + + ../market-core/src/main/java + ../market-web/src/main/java + ../market-rest/src/main/java + + ../market-core/target/generated-sources/annotations + ../market-web/target/generated-sources/annotations + ../market-rest/target/generated-sources/annotations + + + + + + org.apache.maven.plugins + maven-deploy-plugin + 2.8.2 + + true + + + + org.apache.maven.plugins + maven-dependency-plugin + + + + jacoco-dependency-ant + + copy + + process-test-resources + false + + + + org.jacoco + org.jacoco.ant + ${jacoco.version} + + + true + ${basedir}/target/jacoco-jars + + + + + + org.apache.maven.plugins + maven-antrun-plugin + ${ant.run.plugin.version} + + + test + post-integration-test + + run + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + org.jacoco + org.jacoco.ant + ${jacoco.version} + + + + + + \ No newline at end of file diff --git a/sut-market/market-rest/.gitignore b/sut-market/market-rest/.gitignore new file mode 100644 index 0000000..b83d222 --- /dev/null +++ b/sut-market/market-rest/.gitignore @@ -0,0 +1 @@ +/target/ diff --git a/sut-market/market-rest/Dockerfile b/sut-market/market-rest/Dockerfile new file mode 100644 index 0000000..d6a328a --- /dev/null +++ b/sut-market/market-rest/Dockerfile @@ -0,0 +1,5 @@ +# requires installing the module first: 'mvn clean install' +FROM adoptopenjdk/openjdk11:alpine-jre@sha256:89d70c41f6642605c5a7c655969e386815f2f4c0cf923bc1d87e2eadf8669330 +RUN mkdir /app +COPY ./target/*.jar /app/java-app.jar +ENTRYPOINT ["java","-jar","/app/java-app.jar"] \ No newline at end of file diff --git a/sut-market/market-rest/pom.xml b/sut-market/market-rest/pom.xml new file mode 100644 index 0000000..d59cbb7 --- /dev/null +++ b/sut-market/market-rest/pom.xml @@ -0,0 +1,80 @@ + + + + market + net.lukyanets + 0.1.2 + .. + + 4.0.0 + + market-rest + 0.1.2 + jar + + market-rest + + + UTF-8 + 3.0.0 + 3.0.0 + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + + + + net.lukyanets + market-core + ${project.version} + + + + io.springfox + springfox-boot-starter + ${swagger.version} + + + org.springframework.boot + spring-boot-starter-web + + + + + org.springframework.boot + spring-boot-starter + + + org.springframework.boot + spring-boot-starter-logging + + + + + org.springframework.boot + spring-boot-starter-log4j2 + + + com.lmax + disruptor + 3.4.2 + + + + + org.springframework.boot + spring-boot-devtools + true + + + + \ No newline at end of file diff --git a/sut-market/market-rest/src/main/java/market/RestApplication.java b/sut-market/market-rest/src/main/java/market/RestApplication.java new file mode 100644 index 0000000..ed8a534 --- /dev/null +++ b/sut-market/market-rest/src/main/java/market/RestApplication.java @@ -0,0 +1,18 @@ +package market; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.SpringBootConfiguration; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.context.annotation.Import; +import org.springframework.context.annotation.PropertySource; + +@SpringBootConfiguration +@EnableAutoConfiguration +@Import({DataConfig.class, RestSecurityConfig.class, RestConfig.class}) +@PropertySource("classpath:/market.properties") +public class RestApplication { + + public static void main(String[] args) { + SpringApplication.run(RestApplication.class, args); + } +} diff --git a/sut-market/market-rest/src/main/java/market/RestConfig.java b/sut-market/market-rest/src/main/java/market/RestConfig.java new file mode 100644 index 0000000..0cd3f5e --- /dev/null +++ b/sut-market/market-rest/src/main/java/market/RestConfig.java @@ -0,0 +1,23 @@ +package market; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.Configuration; +import springfox.documentation.builders.PathSelectors; +import springfox.documentation.builders.RequestHandlerSelectors; +import springfox.documentation.spi.DocumentationType; +import springfox.documentation.spring.web.plugins.Docket; + +@Configuration +@ComponentScan(basePackages = {"market.rest"}) +public class RestConfig { + + @Bean + public Docket api() { + return new Docket(DocumentationType.SWAGGER_2) + .select() + .apis(RequestHandlerSelectors.basePackage("market.rest")) + .paths(PathSelectors.any()) + .build(); + } +} diff --git a/sut-market/market-rest/src/main/java/market/RestSecurityConfig.java b/sut-market/market-rest/src/main/java/market/RestSecurityConfig.java new file mode 100644 index 0000000..14d543a --- /dev/null +++ b/sut-market/market-rest/src/main/java/market/RestSecurityConfig.java @@ -0,0 +1,20 @@ +package market; + +import org.springframework.context.annotation.Configuration; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; + +@Configuration +public class RestSecurityConfig extends SecurityConfigBase { + + protected void configure(HttpSecurity http) throws Exception { + http.headers() + .frameOptions().disable().and() + .authorizeRequests() + .antMatchers("/customer**").access("hasRole('ROLE_USER')").and() + .httpBasic().and() + .anonymous() + .authorities("ROLE_ANONYMOUS").and() + .csrf().disable() + ; + } +} diff --git a/sut-market/market-rest/src/main/java/market/rest/CartRestController.java b/sut-market/market-rest/src/main/java/market/rest/CartRestController.java new file mode 100644 index 0000000..7ad2fd2 --- /dev/null +++ b/sut-market/market-rest/src/main/java/market/rest/CartRestController.java @@ -0,0 +1,131 @@ +package market.rest; + +import market.domain.Cart; +import market.domain.Order; +import market.dto.CartDTO; +import market.dto.CartItemDTO; +import market.dto.CreditCardDTO; +import market.dto.OrderDTO; +import market.dto.assembler.CartDtoAssembler; +import market.dto.assembler.OrderDtoAssembler; +import market.exception.EmptyCartException; +import market.exception.UnknownEntityException; +import market.properties.MarketProperties; +import market.service.CartService; +import market.service.OrderService; +import org.springframework.hateoas.server.ExposesResourceFor; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.security.access.annotation.Secured; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.ResponseStatus; +import org.springframework.web.bind.annotation.RestController; + +import javax.validation.Valid; +import java.net.URI; +import java.security.Principal; + +import static org.springframework.hateoas.server.mvc.WebMvcLinkBuilder.linkTo; + +@RestController +@RequestMapping(value = "customer/cart") +@ExposesResourceFor(CartDTO.class) +@Secured({"ROLE_USER"}) +public class CartRestController { + public static final String DELIVERY = "/delivery"; + public static final String PAY = "/pay"; + + private final CartService cartService; + private final OrderService orderService; + private final CartDtoAssembler cartDtoAssembler; + private final OrderDtoAssembler orderDtoAssembler = new OrderDtoAssembler(); + private final MarketProperties marketProperties; + + public CartRestController(CartService cartService, OrderService orderService, MarketProperties marketProperties) { + this.cartService = cartService; + this.orderService = orderService; + this.marketProperties = marketProperties; + cartDtoAssembler = new CartDtoAssembler(this.marketProperties); + } + + /** + * Viewing the cart. + */ + @GetMapping + public CartDTO getCart(Principal principal) { + String login = principal.getName(); + Cart cart = cartService.getCartOrCreate(login); + return toDto(cart); + } + + /** + * Adding a product. + * + * @return updated cart + * @throws UnknownEntityException if the specified product does not exist + */ + @PutMapping + public CartDTO addItem(Principal principal, @RequestBody @Valid CartItemDTO item) { + String login = principal.getName(); + Cart cart = cartService.addToCart(login, item.getProductId(), item.getQuantity()); + return toDto(cart); + } + + /** + * Clearing the cart. + * + * @return cleared cart + */ + @DeleteMapping + public CartDTO clearCart(Principal principal) { + String login = principal.getName(); + Cart cart = cartService.clearCart(login); + return toDto(cart); + } + + /** + * Setting delivery option. + * + * @return updated cart + */ + @PutMapping(value = DELIVERY) + public CartDTO setDelivery(Principal principal, @RequestParam(name = "included") boolean included) { + String login = principal.getName(); + Cart cart = cartService.setDelivery(login, included); + return toDto(cart); + } + + /** + * Order registration. + * + * @return created order + * @throws EmptyCartException if the cart is empty + */ + @PostMapping(value = PAY) + @ResponseStatus(HttpStatus.CREATED) + public ResponseEntity payByCard(Principal principal, @RequestBody @Valid CreditCardDTO card) { + String login = principal.getName(); + Order order = orderService.createUserOrder(login, marketProperties.getDeliveryCost(), card.getCcNumber()); + OrderDTO dto = orderDtoAssembler.toModel(order); + + HttpHeaders headers = new HttpHeaders(); + dto.getLink("self").ifPresent(link -> headers.setLocation(URI.create(link.getHref()))); + return new ResponseEntity<>(dto, headers, HttpStatus.CREATED); + } + + private CartDTO toDto(Cart cart) { + CartDTO dto = cartDtoAssembler.toModel(cart); + dto.add(linkTo(ContactsRestController.class).withRel("Customer contacts")); + dto.add(linkTo(getClass()).slash(CartRestController.PAY).withRel("Proceed to payment")); + for (CartItemDTO cartItemDto : dto.getCartItems()) + cartItemDto.add(linkTo(ProductsRestController.class).slash(cartItemDto.getProductId()).withRel("View product")); + return dto; + } +} diff --git a/sut-market/market-rest/src/main/java/market/rest/ContactsRestController.java b/sut-market/market-rest/src/main/java/market/rest/ContactsRestController.java new file mode 100644 index 0000000..f08a168 --- /dev/null +++ b/sut-market/market-rest/src/main/java/market/rest/ContactsRestController.java @@ -0,0 +1,62 @@ +package market.rest; + +import market.domain.Contacts; +import market.dto.ContactsDTO; +import market.dto.assembler.ContactsDtoAssembler; +import market.service.ContactsService; +import org.springframework.hateoas.server.ExposesResourceFor; +import org.springframework.security.access.annotation.Secured; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import javax.validation.Valid; +import java.security.Principal; + +import static org.springframework.hateoas.server.mvc.WebMvcLinkBuilder.linkTo; + +/** + * Customer contacts controller. + */ +@RestController +@RequestMapping(value = "customer/contacts") +@ExposesResourceFor(ContactsDTO.class) +@Secured({"ROLE_USER"}) +public class ContactsRestController { + private final ContactsService contactsService; + private final ContactsDtoAssembler contactsDtoAssembler = new ContactsDtoAssembler(); + + public ContactsRestController(ContactsService contactsService) { + this.contactsService = contactsService; + } + + /** + * Viewing contacts. + */ + @GetMapping + public ContactsDTO getContacts(Principal principal) { + Contacts contacts = contactsService.getContacts(principal.getName()); + return toDto(contacts); + } + + /** + * Updating contacts. + * + * @return updated contacts + */ + @PutMapping + public ContactsDTO updateContacts(Principal principal, @RequestBody @Valid ContactsDTO contactsDto) { + String login = principal.getName(); + Contacts changedContacts = contactsDtoAssembler.toDomain(contactsDto); + contactsService.updateUserContacts(changedContacts, login); + return toDto(changedContacts); + } + + private ContactsDTO toDto(Contacts changedContacts) { + ContactsDTO dto = contactsDtoAssembler.toModel(changedContacts); + dto.add(linkTo(CartRestController.class).withRel("Shopping cart")); + return dto; + } +} diff --git a/sut-market/market-rest/src/main/java/market/rest/CustomerRestController.java b/sut-market/market-rest/src/main/java/market/rest/CustomerRestController.java new file mode 100644 index 0000000..e2f7b59 --- /dev/null +++ b/sut-market/market-rest/src/main/java/market/rest/CustomerRestController.java @@ -0,0 +1,65 @@ +package market.rest; + +import market.domain.UserAccount; +import market.dto.UserDTO; +import market.dto.assembler.UserAccountDtoAssembler; +import market.exception.EmailExistsException; +import market.security.AuthenticationService; +import market.service.UserAccountService; +import org.springframework.hateoas.server.ExposesResourceFor; +import org.springframework.http.HttpStatus; +import org.springframework.security.access.annotation.Secured; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.ResponseStatus; +import org.springframework.web.bind.annotation.RestController; + +import javax.validation.Valid; +import java.security.Principal; + +import static org.springframework.hateoas.server.mvc.WebMvcLinkBuilder.linkTo; + +@RestController +@ExposesResourceFor(UserDTO.class) +public class CustomerRestController { + + private final UserAccountService userAccountService; + private final AuthenticationService authenticationService; + private final UserAccountDtoAssembler userAccountDtoAssembler = new UserAccountDtoAssembler(); + + public CustomerRestController(UserAccountService userAccountService, AuthenticationService authenticationService) { + this.userAccountService = userAccountService; + this.authenticationService = authenticationService; + } + + @GetMapping("/customer") + @Secured({"ROLE_USER", "ROLE_ADMIN"}) + public UserDTO getCustomer(Principal principal) { + UserAccount newAccount = userAccountService.findByEmail(principal.getName()); + return toDto(newAccount); + } + + /** + * New customer registration. + * + * @return newly created account, already authenticated + * @throws EmailExistsException if the account with the specified email already exists + */ + @PostMapping("/register") + @ResponseStatus(HttpStatus.CREATED) + public UserDTO createCustomer(@RequestBody @Valid UserDTO user) { + UserAccount userData = userAccountDtoAssembler.toDomain(user); + UserAccount newAccount = userAccountService.create(userData); + authenticationService.authenticate(newAccount.getEmail(), user.getPassword()); + return toDto(newAccount); + } + + private UserDTO toDto(UserAccount newAccount) { + UserDTO dto = userAccountDtoAssembler.toModel(newAccount); + dto.add(linkTo(CartRestController.class).withRel("Shopping cart")); + dto.add(linkTo(getClass()).withRel("Manage contacts")); + return dto; + } +} diff --git a/sut-market/market-rest/src/main/java/market/rest/DistilleryRestController.java b/sut-market/market-rest/src/main/java/market/rest/DistilleryRestController.java new file mode 100644 index 0000000..a155b15 --- /dev/null +++ b/sut-market/market-rest/src/main/java/market/rest/DistilleryRestController.java @@ -0,0 +1,38 @@ +package market.rest; + +import org.springframework.hateoas.server.ExposesResourceFor; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RestController; + +import market.domain.Distillery; +import market.dto.DistilleryDTO; +import market.dto.assembler.DistilleryDtoAssembler; +import market.service.DistilleryService; + +/* + * New class for test: endpoint (POST) to insert a distillery + */ +@RestController +@ExposesResourceFor(DistilleryDTO.class) + +public class DistilleryRestController { + + private final DistilleryService distilleryService; + private final DistilleryDtoAssembler distilleryDtoAssembler = new DistilleryDtoAssembler(); + + public DistilleryRestController(DistilleryService distilleryService) { + this.distilleryService = distilleryService; + } + + /* + * New endpoint for test: insert a new distillery in a region + */ + @PostMapping (value = "distillerydto") + public DistilleryDTO createDistillery(@RequestBody DistilleryDTO distillery) { + Distillery dis = distilleryDtoAssembler.toDomain(distillery); + + distilleryService.create(dis, distillery.getRegion()); + return distilleryDtoAssembler.toModel(distilleryService.findByTitle(dis.getTitle())); + } +} diff --git a/sut-market/market-rest/src/main/java/market/rest/OrdersRestController.java b/sut-market/market-rest/src/main/java/market/rest/OrdersRestController.java new file mode 100644 index 0000000..34bf176 --- /dev/null +++ b/sut-market/market-rest/src/main/java/market/rest/OrdersRestController.java @@ -0,0 +1,59 @@ +package market.rest; + +import market.dto.OrderDTO; +import market.dto.assembler.OrderDtoAssembler; +import market.exception.UnknownEntityException; +import market.service.OrderService; +import org.springframework.hateoas.server.ExposesResourceFor; +import org.springframework.security.access.annotation.Secured; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.security.Principal; +import java.util.List; + +import static java.util.stream.Collectors.toList; + +/** + * Customer orders history. + */ +@RestController +@RequestMapping(value = "customer/orders") +@ExposesResourceFor(OrderDTO.class) +@Secured({"ROLE_USER"}) +public class OrdersRestController { + private final OrderService orderService; + private final OrderDtoAssembler orderDtoAssembler = new OrderDtoAssembler(); + + public OrdersRestController(OrderService orderService) { + this.orderService = orderService; + } + + /** + * View orders. + * + * @return orders list of the specified customer + */ + @GetMapping + public List getOrders(Principal principal) { + return orderService.getUserOrders(principal.getName()).stream() + .map(orderDtoAssembler::toModel) + .collect(toList()); + } + + /** + * View a single order. + * + * @return order of the specified customer + * @throws UnknownEntityException if the order with the specified id doesn't exist + */ + @GetMapping(value = "/{orderId}") + public OrderDTO getOrder(Principal principal, @PathVariable long orderId) { + String login = principal.getName(); + return orderService.getUserOrder(login, orderId) + .map(orderDtoAssembler::toModel) + .orElseThrow(() -> new UnknownEntityException(OrderDTO.class, orderId)); + } +} diff --git a/sut-market/market-rest/src/main/java/market/rest/ProductsRestController.java b/sut-market/market-rest/src/main/java/market/rest/ProductsRestController.java new file mode 100644 index 0000000..7d154b9 --- /dev/null +++ b/sut-market/market-rest/src/main/java/market/rest/ProductsRestController.java @@ -0,0 +1,84 @@ +package market.rest; + +import static org.springframework.hateoas.server.mvc.WebMvcLinkBuilder.linkTo; +import static org.springframework.hateoas.server.mvc.WebMvcLinkBuilder.methodOn; + +import java.util.Collection; +import java.util.Comparator; +import java.util.stream.Collectors; + +import org.springframework.hateoas.server.ExposesResourceFor; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import market.domain.Product; +import market.dto.ProductDTO; +import market.dto.assembler.ProductDtoAssembler; +import market.exception.UnknownEntityException; +import market.service.ProductService; + +@RestController +@RequestMapping(value = "products") +@ExposesResourceFor(ProductDTO.class) +public class ProductsRestController { + + private final ProductService productService; + private final ProductDtoAssembler productAssembler = new ProductDtoAssembler(); + + public ProductsRestController(ProductService productService) { + this.productService = productService; + } + + /* + * New endpoint for test: insert a new product of a distillery + */ + @PostMapping(value = "productdto") + public ProductDTO createProduct(@RequestBody ProductDTO product) { + Product prod = productAssembler.dtoDomain(product); + + productService.create(prod, product.getDistillery()); + return productService.findById(prod.getId()) + .map(productAssembler::toModel) + .map(this::addListLink) + .orElseThrow(() -> new UnknownEntityException(ProductDTO.class, prod.getId())); + } + + /** + * All the existing products, sorted by id. + */ + @GetMapping + public Collection getProducts() { + return productService.findAll().stream() + .sorted(Comparator.comparing(Product::getId)) + .map(productAssembler::toModel) + .peek(this::addSelfLink) + .collect(Collectors.toList()); + } + + private void addSelfLink(ProductDTO dto) { + dto.add(linkTo(methodOn(getClass()).getProduct(dto.getProductId())).withRel("self")); + } + + /** + * Viewing a single product. + * + * @return product with the specified id + * @throws UnknownEntityException if the product with the specified id doesn't exist + */ + @GetMapping(value = "/{productId}") + public ProductDTO getProduct(@PathVariable long productId) { + return productService.findById(productId) + .map(productAssembler::toModel) + .map(this::addListLink) + .orElseThrow(() -> new UnknownEntityException(ProductDTO.class, productId)); + } + + private ProductDTO addListLink(ProductDTO dto) { + dto.add(linkTo(methodOn(getClass()).getProducts()).withRel("All products")); + return dto; + } +} diff --git a/sut-market/market-rest/src/main/java/market/rest/RegionRestController.java b/sut-market/market-rest/src/main/java/market/rest/RegionRestController.java new file mode 100644 index 0000000..a87881f --- /dev/null +++ b/sut-market/market-rest/src/main/java/market/rest/RegionRestController.java @@ -0,0 +1,38 @@ +package market.rest; + +import org.springframework.hateoas.server.ExposesResourceFor; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RestController; + +import market.domain.Region; +import market.dto.RegionDTO; +import market.dto.assembler.RegionDtoAssembler; +import market.service.RegionService; + +/* + * New class for test: endpoint (POST) to insert a region + */ +@RestController +@ExposesResourceFor(RegionDTO.class) +public class RegionRestController { + + private final RegionService regionService; + private final RegionDtoAssembler regionDtoAssembler = new RegionDtoAssembler(); + + public RegionRestController(RegionService regionService) { + this.regionService = regionService; + } + + /* + * New endpoint for test: insert a new region + */ + @PostMapping (value = "regiondto") + public RegionDTO createRegion(@RequestBody RegionDTO region) { + Region reg = regionDtoAssembler.toDomain(region); + + regionService.create(reg); + + return regionDtoAssembler.toModel(regionService.findByName(region.getName())); + } +} diff --git a/sut-market/market-rest/src/main/java/market/rest/TestRestController.java b/sut-market/market-rest/src/main/java/market/rest/TestRestController.java new file mode 100644 index 0000000..4c9755c --- /dev/null +++ b/sut-market/market-rest/src/main/java/market/rest/TestRestController.java @@ -0,0 +1,40 @@ +package market.rest; + +import java.sql.SQLException; + +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import market.service.TestService; + +/* + * New class for test: endpoints (GET and DELETE) to get and detele all data in the database + */ +@RestController +@RequestMapping(value = "test") +public class TestRestController { + private final TestService testService; + + public TestRestController(TestService testService) { + this.testService = testService; + } + /** + * New endpoint for test: get all the existing data + */ + @GetMapping("/getAll") + public Object getAll() { + final Object allData = testService.getAll(); + + return allData; + } + + /** + * New endpoint for test: delete all the existing data + */ + @DeleteMapping("/deleteAll") + public void deleteAll() throws SQLException { + testService.deleteAll(); + } +} diff --git a/sut-market/market-rest/src/main/java/market/rest/exception/RestErrorResponse.java b/sut-market/market-rest/src/main/java/market/rest/exception/RestErrorResponse.java new file mode 100644 index 0000000..5b3be58 --- /dev/null +++ b/sut-market/market-rest/src/main/java/market/rest/exception/RestErrorResponse.java @@ -0,0 +1,62 @@ +package market.rest.exception; + +import market.dto.exception.FieldErrorDTO; +import org.springframework.web.context.request.WebRequest; + +import java.util.ArrayList; +import java.util.List; + +/** + * Generalized response for the case of a request processing error. + */ +public class RestErrorResponse { + + private final String message; + private final String description; + private final String entityName; + private final List fieldErrors = new ArrayList<>(); + + public RestErrorResponse(String message, WebRequest request) { + this(message, null, request.getDescription(false)); + } + + public RestErrorResponse(String message, String entityName, WebRequest request) { + this(message, entityName, request.getDescription(false)); + } + + public RestErrorResponse(String message, String entityName, String description) { + this.message = message; + this.entityName = entityName; + this.description = description; + } + + public void addFieldError(String path, String message) { + fieldErrors.add(new FieldErrorDTO(path, message)); + } + + public String getDescription() { + return description; + } + + public String getMessage() { + return message; + } + + public String getEntityName() { + return entityName; + } + + public List getFieldErrors() { + return fieldErrors; + } + + @Override + public String toString() { + return "RestErrorResponse{" + + "message='" + message + '\'' + + ", entityName='" + entityName + '\'' + + ", description='" + description + '\'' + + ", fieldErrors=" + fieldErrors + + '}'; + } +} diff --git a/sut-market/market-rest/src/main/java/market/rest/exception/RestExceptionHandler.java b/sut-market/market-rest/src/main/java/market/rest/exception/RestExceptionHandler.java new file mode 100644 index 0000000..0efa2fe --- /dev/null +++ b/sut-market/market-rest/src/main/java/market/rest/exception/RestExceptionHandler.java @@ -0,0 +1,112 @@ +package market.rest.exception; + +import market.exception.CustomNotValidException; +import market.exception.UnknownEntityException; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.springframework.context.MessageSource; +import org.springframework.context.i18n.LocaleContextHolder; +import org.springframework.http.HttpStatus; +import org.springframework.security.access.AccessDeniedException; +import org.springframework.validation.BindingResult; +import org.springframework.validation.FieldError; +import org.springframework.web.bind.MethodArgumentNotValidException; +import org.springframework.web.bind.annotation.ControllerAdvice; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.ResponseStatus; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.context.request.WebRequest; + +import java.util.List; +import java.util.Locale; + +@ControllerAdvice(basePackages = "market.rest") +@RestController +public class RestExceptionHandler { + private static final String ARGUMENT_VALIDATION_CODE = "Error.Validation.Parameter"; + private static final String NOT_EXIST_CODE = "NotExist"; + + private static final Logger log = LogManager.getLogger(RestExceptionHandler.class); + + private final MessageSource messageSource; + + public RestExceptionHandler(MessageSource messageSource) { + this.messageSource = messageSource; + } + + @ExceptionHandler(Exception.class) + @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) + public RestErrorResponse otherExceptions(Exception e, WebRequest request) { + RestErrorResponse response = new RestErrorResponse(e.getMessage(), request); + log.error(response.toString(), e); + return response; + } + + @ExceptionHandler(AccessDeniedException.class) + @ResponseStatus(HttpStatus.UNAUTHORIZED) + public RestErrorResponse accessDeniedException(AccessDeniedException e, WebRequest request) { + RestErrorResponse response = new RestErrorResponse(e.getMessage(), request); + log.warn(response.toString()); + return response; + } + + @ExceptionHandler(UnknownEntityException.class) + @ResponseStatus(HttpStatus.NOT_FOUND) + public RestErrorResponse unknownEntityException(UnknownEntityException e, WebRequest request) { + String entityType = e.getEntityType(); + List fieldErrors = e.getFieldErrors(); + RestErrorResponse response = createLocalizedResponse(NOT_EXIST_CODE, entityType, fieldErrors, request); + log.warn(response.toString()); + return response; + } + + @ExceptionHandler(CustomNotValidException.class) + @ResponseStatus(HttpStatus.NOT_ACCEPTABLE) + public RestErrorResponse customNotValidException(CustomNotValidException e, WebRequest request) { + String entityType = e.getEntityType(); + List fieldErrors = e.getFieldErrors(); + RestErrorResponse response = createLocalizedResponse(ARGUMENT_VALIDATION_CODE, entityType, fieldErrors, request); + log.warn(response.toString()); + return response; + } + + @ExceptionHandler(MethodArgumentNotValidException.class) + @ResponseStatus(HttpStatus.NOT_ACCEPTABLE) + public RestErrorResponse processValidationError(MethodArgumentNotValidException e, WebRequest request) { + BindingResult result = e.getBindingResult(); + String objectName = result.getObjectName(); + List fieldErrors = result.getFieldErrors(); + RestErrorResponse response = createLocalizedResponse(ARGUMENT_VALIDATION_CODE, objectName, fieldErrors, request); + log.warn(response.toString()); + return response; + } + + private RestErrorResponse createLocalizedResponse(String localizationCode, String entityType, + List fieldErrors, WebRequest request) + { + Locale currentLocale = LocaleContextHolder.getLocale(); + String localizedMessage = messageSource.getMessage(localizationCode, null, currentLocale); + RestErrorResponse response = new RestErrorResponse(localizedMessage, entityType, request); + return resolveFieldErrors(response, fieldErrors); + } + + private RestErrorResponse resolveFieldErrors(RestErrorResponse response, List fieldErrors) { + for (FieldError fieldError : fieldErrors) { + String localizedErrorMessage = resolveErrorMessage(fieldError); + response.addFieldError(fieldError.getField(), localizedErrorMessage); + } + return response; + } + + private String resolveErrorMessage(FieldError fieldError) { + Locale currentLocale = LocaleContextHolder.getLocale(); + String localizedErrorMessage = messageSource.getMessage(fieldError, currentLocale); + + if (localizedErrorMessage.equals(fieldError.getDefaultMessage())) { + String[] fieldErrorCodes = fieldError.getCodes(); + if (fieldErrorCodes != null) + localizedErrorMessage = fieldErrorCodes[0]; + } + return localizedErrorMessage; + } +} diff --git a/sut-market/market-rest/src/main/resources/application.yml b/sut-market/market-rest/src/main/resources/application.yml new file mode 100644 index 0000000..97a0b6c --- /dev/null +++ b/sut-market/market-rest/src/main/resources/application.yml @@ -0,0 +1,39 @@ +server: + port: ${PORT:8081} + +spring: + application: + name: market-rest + jpa: + show-sql: true + generate-ddl: false + hibernate: + ddl-auto: none + properties: + hibernate: + dialect: org.hibernate.dialect.PostgreSQL9Dialect + hbm2ddl.auto: none + messages: + basename: messages + fallback-to-system-locale: false + use-code-as-default-message: true + +--- + +spring: + profiles: default + datasource: + url: jdbc:h2:mem:market;DB_CLOSE_DELAY=-1 + driverClassName: org.h2.Driver + username: sa + password: + +--- + +spring: + profiles: prod + datasource: + url: jdbc:postgresql://${DB_HOST:localhost}:5432/market + driverClassName: org.postgresql.Driver + username: market + password: market \ No newline at end of file diff --git a/sut-market/market-rest/src/main/resources/log4j2.xml b/sut-market/market-rest/src/main/resources/log4j2.xml new file mode 100644 index 0000000..dc90dcf --- /dev/null +++ b/sut-market/market-rest/src/main/resources/log4j2.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/sut-market/market-rest/src/main/resources/market.properties b/sut-market/market-rest/src/main/resources/market.properties new file mode 100644 index 0000000..a2583f0 --- /dev/null +++ b/sut-market/market-rest/src/main/resources/market.properties @@ -0,0 +1 @@ +deliveryCost=400 \ No newline at end of file diff --git a/sut-market/market-web/.gitignore b/sut-market/market-web/.gitignore new file mode 100644 index 0000000..b83d222 --- /dev/null +++ b/sut-market/market-web/.gitignore @@ -0,0 +1 @@ +/target/ diff --git a/sut-market/market-web/Dockerfile b/sut-market/market-web/Dockerfile new file mode 100644 index 0000000..d6a328a --- /dev/null +++ b/sut-market/market-web/Dockerfile @@ -0,0 +1,5 @@ +# requires installing the module first: 'mvn clean install' +FROM adoptopenjdk/openjdk11:alpine-jre@sha256:89d70c41f6642605c5a7c655969e386815f2f4c0cf923bc1d87e2eadf8669330 +RUN mkdir /app +COPY ./target/*.jar /app/java-app.jar +ENTRYPOINT ["java","-jar","/app/java-app.jar"] \ No newline at end of file diff --git a/sut-market/market-web/pom.xml b/sut-market/market-web/pom.xml new file mode 100644 index 0000000..e1221b7 --- /dev/null +++ b/sut-market/market-web/pom.xml @@ -0,0 +1,126 @@ + + + + market + net.lukyanets + 0.1.2 + .. + + 4.0.0 + + market-web + 0.1.2 + jar + + market-web + + + UTF-8 + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + org.apache.tomcat.maven + tomcat7-maven-plugin + 2.2 + + http://localhost:8080/manager/text + tomcat + /market + admin + admin + + + + org.apache.maven.plugins + maven-surefire-plugin + ${maven.surefire.plugin.version} + + + org.jacoco + jacoco-maven-plugin + ${jacoco.version} + + + + prepare-agent + + + + report + prepare-package + + report + + + + + + **/*market/sorting/** + **/*market/interceptors/** + + + + + + + + + net.lukyanets + market-core + ${project.version} + + + + org.springframework.boot + spring-boot-starter-web + + + + + org.springframework.boot + spring-boot-starter-thymeleaf + + + org.thymeleaf.extras + thymeleaf-extras-springsecurity5 + + + + + org.springframework.boot + spring-boot-starter + + + org.springframework.boot + spring-boot-starter-logging + + + + + org.springframework.boot + spring-boot-starter-log4j2 + + + com.lmax + disruptor + 3.4.2 + + + + + org.springframework.boot + spring-boot-devtools + true + + + + \ No newline at end of file diff --git a/sut-market/market-web/src/main/java/market/ServletConfig.java b/sut-market/market-web/src/main/java/market/ServletConfig.java new file mode 100644 index 0000000..4894965 --- /dev/null +++ b/sut-market/market-web/src/main/java/market/ServletConfig.java @@ -0,0 +1,22 @@ +package market; + +import market.interceptors.SessionCartInterceptor; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.servlet.config.annotation.InterceptorRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +@Configuration +@ComponentScan(basePackages = { + "market.controller.frontend", + "market.controller.backend" +}) +public class ServletConfig implements WebMvcConfigurer { + + @Override + public void addInterceptors(InterceptorRegistry registry) { + registry.addInterceptor(new SessionCartInterceptor()) + .addPathPatterns("/**") + .excludePathPatterns("/admin/**"); + } +} diff --git a/sut-market/market-web/src/main/java/market/WebApplication.java b/sut-market/market-web/src/main/java/market/WebApplication.java new file mode 100644 index 0000000..c5f32fc --- /dev/null +++ b/sut-market/market-web/src/main/java/market/WebApplication.java @@ -0,0 +1,18 @@ +package market; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.SpringBootConfiguration; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.context.annotation.Import; +import org.springframework.context.annotation.PropertySource; + +@SpringBootConfiguration +@EnableAutoConfiguration +@Import({DataConfig.class, WebSecurityConfig.class, ServletConfig.class}) +@PropertySource("classpath:/market.properties") +public class WebApplication { + + public static void main(String[] args) { + SpringApplication.run(WebApplication.class, args); + } +} diff --git a/sut-market/market-web/src/main/java/market/WebSecurityConfig.java b/sut-market/market-web/src/main/java/market/WebSecurityConfig.java new file mode 100644 index 0000000..aa6e287 --- /dev/null +++ b/sut-market/market-web/src/main/java/market/WebSecurityConfig.java @@ -0,0 +1,53 @@ +package market; + +import market.properties.MarketProperties; +import market.security.CustomAuthenticationSuccessHandler; +import market.service.UserAccountService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.web.authentication.AuthenticationSuccessHandler; + +import javax.servlet.ServletContext; + +@Configuration +public class WebSecurityConfig extends SecurityConfigBase { + + @Autowired + private AuthenticationSuccessHandler customAuthenticationSuccessHandler; + + protected void configure(HttpSecurity http) throws Exception { + http.headers() + .frameOptions().disable().and() + .authorizeRequests() + .antMatchers("/admin/**").access("hasRole('ROLE_STAFF') or hasRole('ROLE_ADMIN')").and() + .httpBasic().and() + .anonymous() + .authorities("ROLE_ANONYMOUS").and() + .formLogin() + .loginPage("/login") + .loginProcessingUrl("/login") + .usernameParameter("email") + .passwordParameter("password") + .successHandler(customAuthenticationSuccessHandler) + .failureUrl("/login?error").and() + .rememberMe() + .key("marketAppKey").and() + .logout() + .logoutUrl("/logout") + .logoutSuccessUrl("/") + .invalidateHttpSession(true).clearAuthentication(true) + .deleteCookies("JSESSIONID").and() + .sessionManagement().maximumSessions(25).and().and() + .csrf().disable() + ; + } + + @Bean + public AuthenticationSuccessHandler customAuthenticationSuccessHandler(ServletContext servletContext, + UserAccountService userAccountService, MarketProperties marketProperties) + { + return new CustomAuthenticationSuccessHandler(servletContext, userAccountService, marketProperties); + } +} diff --git a/sut-market/market-web/src/main/java/market/controller/CartModelHelper.java b/sut-market/market-web/src/main/java/market/controller/CartModelHelper.java new file mode 100644 index 0000000..3122443 --- /dev/null +++ b/sut-market/market-web/src/main/java/market/controller/CartModelHelper.java @@ -0,0 +1,23 @@ +package market.controller; + +import market.domain.Cart; +import market.dto.CartDTO; +import market.dto.assembler.CartDtoAssembler; +import org.springframework.ui.Model; + +import javax.servlet.http.HttpServletRequest; + +public class CartModelHelper { + private final CartDtoAssembler cartDtoAssembler; + + public CartModelHelper(CartDtoAssembler cartDtoAssembler) { + this.cartDtoAssembler = cartDtoAssembler; + } + + public CartDTO convertAndUpdateAttributes(Cart cart, Model model, HttpServletRequest request) { + CartDTO cartDTO = cartDtoAssembler.toModel(cart); + model.addAttribute("cart", cartDTO); + request.getSession().setAttribute("cart", cartDTO); + return cartDTO; + } +} diff --git a/sut-market/market-web/src/main/java/market/controller/SpringExceptionHandler.java b/sut-market/market-web/src/main/java/market/controller/SpringExceptionHandler.java new file mode 100644 index 0000000..3887d54 --- /dev/null +++ b/sut-market/market-web/src/main/java/market/controller/SpringExceptionHandler.java @@ -0,0 +1,97 @@ +package market.controller; + +import market.exception.CustomNotValidException; +import market.exception.EmailExistsException; +import market.exception.EmptyCartException; +import market.exception.UnknownEntityException; +import market.dto.exception.ValidationErrorDTO; +import org.springframework.context.MessageSource; +import org.springframework.context.i18n.LocaleContextHolder; +import org.springframework.http.HttpStatus; +import org.springframework.stereotype.Controller; +import org.springframework.validation.BindingResult; +import org.springframework.validation.FieldError; +import org.springframework.web.bind.MethodArgumentNotValidException; +import org.springframework.web.bind.annotation.ControllerAdvice; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.bind.annotation.ResponseStatus; + +import java.util.List; +import java.util.Locale; + +/** + * Обработчик исключений. + */ +@Controller +@ControllerAdvice(basePackages = {"market.controller"}) +public class SpringExceptionHandler { + private final MessageSource messageSource; + + public SpringExceptionHandler(MessageSource messageSource) { + this.messageSource = messageSource; + } + + //-------------------------------------------------- Обработчики исключений + + /** + * Запрос пользователем несуществующих объектов. + */ + @ExceptionHandler(UnknownEntityException.class) + @ResponseStatus(HttpStatus.NOT_FOUND) + @ResponseBody + public String handleProductNotFoundException(Exception ex) { + return ex.getMessage(); + } + + /** + * Исключения при обработке переданных пользователем объектов. + * Ответ сервера сопровождается пояснениями. + * + * @return перечень нарушенных ограничений + */ + @ExceptionHandler({EmailExistsException.class, EmptyCartException.class}) + @ResponseStatus(HttpStatus.NOT_ACCEPTABLE) + @ResponseBody + public ValidationErrorDTO handleEmailExistsException(CustomNotValidException ex) { + return processFieldErrors(ex.getFieldErrors()); + } + + /** + * Ошибки валидации полученного от клиента объекта. + * Ответ сервера сопровождается пояснениями. + * + * @return перечень нарушенных ограничений + */ + @ExceptionHandler(MethodArgumentNotValidException.class) + @ResponseStatus(HttpStatus.NOT_ACCEPTABLE) + @ResponseBody + public ValidationErrorDTO processValidationError(MethodArgumentNotValidException ex) { + BindingResult result = ex.getBindingResult(); + List fieldErrors = result.getFieldErrors(); + return processFieldErrors(fieldErrors); + } + + //----------------------------------------- Компоновка сообщений об ошибках + + private ValidationErrorDTO processFieldErrors(List fieldErrors) { + ValidationErrorDTO dto = new ValidationErrorDTO(); + for (FieldError fieldError : fieldErrors) { + String localizedErrorMessage = resolveErrorMessage(fieldError); + dto.addFieldError(fieldError.getField(), localizedErrorMessage); + } + return dto; + } + + private String resolveErrorMessage(FieldError fieldError) { + Locale currentLocale = LocaleContextHolder.getLocale(); + String localizedErrorMessage = messageSource.getMessage(fieldError, currentLocale); + + // Если подходящего сообщения не найдено - попытаться найти ближайшее по коду ошибки + if (localizedErrorMessage.equals(fieldError.getDefaultMessage())) { + String[] fieldErrorCodes = fieldError.getCodes(); + localizedErrorMessage = fieldErrorCodes[0]; + } + return localizedErrorMessage; + } +} diff --git a/sut-market/market-web/src/main/java/market/controller/backend/BackendController.java b/sut-market/market-web/src/main/java/market/controller/backend/BackendController.java new file mode 100644 index 0000000..cbf9f50 --- /dev/null +++ b/sut-market/market-web/src/main/java/market/controller/backend/BackendController.java @@ -0,0 +1,19 @@ +package market.controller.backend; + +import org.springframework.security.access.annotation.*; +import org.springframework.stereotype.*; +import org.springframework.web.bind.annotation.*; + +@Controller +@RequestMapping(value = "/admin") +@Secured({"ROLE_STAFF", "ROLE_ADMIN"}) +public class BackendController { + + /** + * Backend title page. + */ + @RequestMapping(value = {"", "/", "/index"}, method = RequestMethod.GET) + public String index() { + return "admin/index"; + } +} diff --git a/sut-market/market-web/src/main/java/market/controller/backend/DistilleryController.java b/sut-market/market-web/src/main/java/market/controller/backend/DistilleryController.java new file mode 100644 index 0000000..4a3eee8 --- /dev/null +++ b/sut-market/market-web/src/main/java/market/controller/backend/DistilleryController.java @@ -0,0 +1,111 @@ +package market.controller.backend; + +import market.domain.Distillery; +import market.dto.DistilleryDTO; +import market.dto.RegionDTO; +import market.dto.assembler.DistilleryDtoAssembler; +import market.dto.assembler.RegionDtoAssembler; +import market.service.DistilleryService; +import market.service.RegionService; +import org.springframework.security.access.annotation.Secured; +import org.springframework.stereotype.Controller; +import org.springframework.ui.Model; +import org.springframework.validation.BindingResult; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; + +import javax.validation.Valid; +import java.util.List; + +import static java.util.stream.Collectors.toList; + +@Controller +@RequestMapping("/admin/distilleries") +@Secured({"ROLE_STAFF", "ROLE_ADMIN"}) +public class DistilleryController { + private static final String DISTILLERIES_BASE = "admin/distilleries"; + private static final String DISTILLERIES_NEW = DISTILLERIES_BASE + "/new"; + private static final String DISTILLERIES_EDIT = DISTILLERIES_BASE + "/edit"; + + private final DistilleryService distilleryService; + private final RegionService regionService; + private final RegionDtoAssembler regionDtoAssembler = new RegionDtoAssembler(); + private final DistilleryDtoAssembler distilleryDtoAssembler = new DistilleryDtoAssembler(); + + public DistilleryController(DistilleryService distilleryService, RegionService regionService) { + this.distilleryService = distilleryService; + this.regionService = regionService; + } + + @RequestMapping(method = RequestMethod.GET) + public String allDistilleries(Model model) { + List distilleriesDto = distilleryService.findAll().stream() + .map(distilleryDtoAssembler::toModel) + .collect(toList()); + model.addAttribute("distilleries", distilleriesDto); + return DISTILLERIES_BASE; + } + + //------------------------------------------------ Creating new distillery + + @RequestMapping(method = RequestMethod.GET, value = "/new") + public String newDistillery(Model model) { + List regionsDto = regionService.findAll().stream() + .map(regionDtoAssembler::toModel) + .collect(toList()); + model.addAttribute("regions", regionsDto); + model.addAttribute("distillery", new Distillery()); + return DISTILLERIES_NEW; + } + + @RequestMapping(method = RequestMethod.POST, value = "/new") + public String postDistillery( + @Valid DistilleryDTO distilleryDto, BindingResult bindingResult + ) { + if (bindingResult.hasErrors()) + return DISTILLERIES_NEW; + + Distillery newDistillery = distilleryDtoAssembler.toDomain(distilleryDto); + distilleryService.create(newDistillery, distilleryDto.getRegion()); + return "redirect:/" + DISTILLERIES_BASE; + } + + //------------------------------------------------ Changing distillery + + @RequestMapping(method = RequestMethod.GET, value = "/{distilleryId}/edit") + public String editDistillery( + @PathVariable long distilleryId, Model model + ) { + List regionsDto = regionService.findAll().stream() + .map(regionDtoAssembler::toModel) + .collect(toList()); + model.addAttribute("regions", regionsDto); + + Distillery distillery = distilleryService.findById(distilleryId); + model.addAttribute("distillery", distilleryDtoAssembler.toModel(distillery)); + + return DISTILLERIES_EDIT; + } + + @RequestMapping(method = RequestMethod.POST, value = "/{distilleryId}/edit") + public String putDistillery( + @PathVariable long distilleryId, + @Valid DistilleryDTO distilleryDto, BindingResult bindingResult + ) { + if (bindingResult.hasErrors()) + return DISTILLERIES_EDIT; + + Distillery changedDistillery = distilleryDtoAssembler.toDomain(distilleryDto); + distilleryService.update(distilleryId, changedDistillery, distilleryDto.getRegion()); + return "redirect:/" + DISTILLERIES_BASE; + } + + //------------------------------------------------------ Removing distillery + + @RequestMapping(method = RequestMethod.POST, value = "/{distilleryId}/delete") + public String deleteDistillery(@PathVariable long distilleryId) { + distilleryService.delete(distilleryId); + return "redirect:/" + DISTILLERIES_BASE; + } +} diff --git a/sut-market/market-web/src/main/java/market/controller/backend/OrdersController.java b/sut-market/market-web/src/main/java/market/controller/backend/OrdersController.java new file mode 100644 index 0000000..1203917 --- /dev/null +++ b/sut-market/market-web/src/main/java/market/controller/backend/OrdersController.java @@ -0,0 +1,102 @@ +package market.controller.backend; + +import market.domain.Order; +import market.domain.OrderedProduct; +import market.domain.UserAccount; +import market.dto.*; +import market.dto.assembler.*; +import market.properties.PaginationProperties; +import market.service.OrderService; +import market.sorting.ISorter; +import market.sorting.OrderSorting; +import market.sorting.SortingValuesDTO; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.security.access.annotation.Secured; +import org.springframework.stereotype.Controller; +import org.springframework.ui.Model; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RequestParam; + +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import static java.util.stream.Collectors.toList; +import static java.util.stream.Collectors.toMap; + +@Controller +@RequestMapping("/admin/orders") +@Secured({"ROLE_STAFF", "ROLE_ADMIN"}) +public class OrdersController { + private static final String ORDERS_BASE = "admin/orders"; + + private final OrderService orderService; + private final ISorter orderSorting; + private final OrderDtoAssembler orderDtoAssembler = new OrderDtoAssembler(); + private final OrderedProductDtoAssembler orderedProductDTOAssembler = new OrderedProductDtoAssembler(); + private final ProductDtoAssembler productDTOAssembler = new ProductDtoAssembler(); + private final ContactsDtoAssembler contactsDTOAssembler = new ContactsDtoAssembler(); + private final BillDtoAssembler billDTOAssembler = new BillDtoAssembler(); + + public OrdersController(OrderService orderService, PaginationProperties paginationProperties) { + this.orderService = orderService; + orderSorting = new OrderSorting(paginationProperties.getBackendOrder()); + } + + @RequestMapping(method = RequestMethod.GET) + public String getOrders( + SortingValuesDTO sortingValues, + @RequestParam(value = "executed", required = false, defaultValue = "all") String executed, + @RequestParam(value = "created", required = false, defaultValue = "all") String created, + Model model + ) { + PageRequest request = orderSorting.updateSorting(sortingValues); + Page page = orderService.fetchFiltered(executed, created, request); + orderSorting.prepareModel(model, page.map(orderDtoAssembler::toModel)); + List orders = page.getContent(); + + Map> orderedProductsMap = new HashMap<>(); + for (Order order : orders) { + List productsDto = order.getOrderedProducts().stream() + .map(orderedProductDTOAssembler::toModel) + .collect(toList()); + orderedProductsMap.put(order.getId(), productsDto); + } + model.addAttribute("orderedProductsByOrderId", orderedProductsMap); + + Map productsById = orders.stream() + .map(Order::getOrderedProducts) + .flatMap(Collection::stream) + .map(OrderedProduct::getProduct) + .distinct() + .map(productDTOAssembler::toModel) + .collect(toMap(ProductDTO::getProductId, p -> p)); + model.addAttribute("productsById", productsById); + + Map contactsByAccount = orders.stream() + .map(Order::getUserAccount) + .collect(toMap(UserAccount::getEmail, a -> contactsDTOAssembler.toModel(a.getContacts()))); + model.addAttribute("contactsByAccount", contactsByAccount); + + Map billsByOrderId = orders.stream() + .collect(toMap(Order::getId, o -> billDTOAssembler.toModel(o.getBill()))); + model.addAttribute("billsByOrderId", billsByOrderId); + + model.addAttribute("currentExecuted", executed); + model.addAttribute("currentCreated", created); + return ORDERS_BASE; + } + + @RequestMapping(method = RequestMethod.POST, value = "/{orderId}") + public String setExecutionStatus( + @PathVariable long orderId, + @RequestParam(value = "executed") boolean executed + ) { + orderService.updateStatus(orderId, executed); + return "redirect:/" + ORDERS_BASE; + } +} diff --git a/sut-market/market-web/src/main/java/market/controller/backend/ProductController.java b/sut-market/market-web/src/main/java/market/controller/backend/ProductController.java new file mode 100644 index 0000000..e040bca --- /dev/null +++ b/sut-market/market-web/src/main/java/market/controller/backend/ProductController.java @@ -0,0 +1,158 @@ +package market.controller.backend; + +import market.domain.Distillery; +import market.domain.Product; +import market.dto.DistilleryDTO; +import market.dto.ProductDTO; +import market.dto.assembler.DistilleryDtoAssembler; +import market.dto.assembler.ProductDtoAssembler; +import market.exception.UnknownEntityException; +import market.properties.PaginationProperties; +import market.service.DistilleryService; +import market.service.ProductService; +import market.sorting.ISorter; +import market.sorting.ProductBackendSorting; +import market.sorting.SortingValuesDTO; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.security.access.annotation.Secured; +import org.springframework.stereotype.Controller; +import org.springframework.ui.Model; +import org.springframework.validation.BindingResult; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RequestParam; + +import javax.validation.Valid; +import java.util.List; +import java.util.Map; +import java.util.Optional; + +import static java.util.stream.Collectors.toList; +import static java.util.stream.Collectors.toMap; + +@Controller +@RequestMapping("/admin/products") +@Secured({"ROLE_STAFF", "ROLE_ADMIN"}) +public class ProductController { + private static final Logger log = LogManager.getLogger(ProductController.class); + + private static final String PRODUCTS_BASE = "admin/products"; + private static final String PRODUCTS_EDIT = PRODUCTS_BASE + "/edit"; + private static final String PRODUCTS_NEW = PRODUCTS_BASE + "/new"; + + private final ProductService productService; + private final DistilleryService distilleryService; + private final ISorter productBackendSorting; + private final ProductDtoAssembler productDtoAssembler = new ProductDtoAssembler(); + private final DistilleryDtoAssembler distilleryDtoAssembler = new DistilleryDtoAssembler(); + + public ProductController(ProductService productService, DistilleryService distilleryService, + PaginationProperties paginationProperties) + { + this.productService = productService; + this.distilleryService = distilleryService; + productBackendSorting = new ProductBackendSorting(paginationProperties.getBackendProduct()); + } + + @RequestMapping(method = RequestMethod.GET) + public String getProducts( + SortingValuesDTO sortingValues, + @RequestParam(value = "dist", required = false, defaultValue = "0") long distilleryId, + Model model + ) { + PageRequest request = productBackendSorting.updateSorting(sortingValues); + Page pagedProducts; + if (distilleryId == 0) { + pagedProducts = productService.findAll(request); + } else { + Distillery distillery = distilleryService.findById(distilleryId); + pagedProducts = productService.findByDistillery(distillery, request); + model.addAttribute("currentDistilleryTitle", distillery.getTitle()); + } + productBackendSorting.prepareModel(model, pagedProducts.map(productDtoAssembler::toModel)); + + List distilleries = distilleryService.findAll(); + List distilleriesDto = distilleries.stream() + .map(distilleryDtoAssembler::toModel) + .collect(toList()); + model.addAttribute("distilleries", distilleriesDto); + + Map regionByDistillery = distilleries.stream() + .collect(toMap(Distillery::getTitle, d -> d.getRegion().getName())); + model.addAttribute("regionByDistillery", regionByDistillery); + + return PRODUCTS_BASE; + } + + //------------------------------------------------------- Creating new product + + @RequestMapping(method = RequestMethod.GET, value = "/new") + public String newProduct(Model model) { + List distilleriesDto = distilleryService.findAll().stream() + .map(distilleryDtoAssembler::toModel) + .collect(toList()); + model.addAttribute("distilleries", distilleriesDto); + model.addAttribute("product", productDtoAssembler.toModel(new Product())); + return PRODUCTS_NEW; + } + + @RequestMapping(method = RequestMethod.POST, value = "/new") + public String postProduct( + @Valid ProductDTO product, BindingResult bindingResult + ) { + if (bindingResult.hasErrors()) + return "redirect:/" + PRODUCTS_NEW; + + Product newProduct = productDtoAssembler.dtoDomain(product); + productService.create(newProduct, product.getDistillery()); + return "redirect:/" + PRODUCTS_BASE; + } + + //--------------------------------------------------- Updating product + + @RequestMapping(method = RequestMethod.GET, value = "/{productId}/edit") + public String editProduct( + @PathVariable long productId, Model model + ) { + Optional productOptional = productService.findById(productId); + if (!productOptional.isPresent()) + return "redirect:/" + PRODUCTS_BASE; + + List distilleriesDto = distilleryService.findAll().stream() + .map(distilleryDtoAssembler::toModel) + .collect(toList()); + model.addAttribute("distilleries", distilleriesDto); + model.addAttribute("product", productDtoAssembler.toModel(productOptional.get())); + return PRODUCTS_EDIT; + } + + @RequestMapping(method = RequestMethod.POST, value = "/{productId}/edit") + public String putProduct( + @PathVariable long productId, + @Valid ProductDTO product, BindingResult bindingResult + ) { + if (bindingResult.hasErrors()) + return "redirect:/" + PRODUCTS_EDIT; + + Product changedProduct = productDtoAssembler.dtoDomain(product); + try { + productService.update(productId, changedProduct, product.getDistillery()); + return "redirect:/" + PRODUCTS_BASE; + } catch (UnknownEntityException e) { + log.warn(e.getMessage()); + return "redirect:/" + PRODUCTS_EDIT; + } + } + + //--------------------------------------------------------- Removing product + + @RequestMapping(method = RequestMethod.POST, value = "/{productId}/delete") + public String deleteProduct(@PathVariable long productId) { + productService.delete(productId); + return "redirect:/" + PRODUCTS_BASE; + } +} diff --git a/sut-market/market-web/src/main/java/market/controller/backend/RegionController.java b/sut-market/market-web/src/main/java/market/controller/backend/RegionController.java new file mode 100644 index 0000000..d9dd532 --- /dev/null +++ b/sut-market/market-web/src/main/java/market/controller/backend/RegionController.java @@ -0,0 +1,97 @@ +package market.controller.backend; + +import market.domain.Region; +import market.dto.RegionDTO; +import market.dto.assembler.RegionDtoAssembler; +import market.service.RegionService; +import org.springframework.security.access.annotation.Secured; +import org.springframework.stereotype.Controller; +import org.springframework.ui.Model; +import org.springframework.validation.BindingResult; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; + +import javax.validation.Valid; +import java.util.Comparator; +import java.util.List; + +import static java.util.stream.Collectors.toList; + +@Controller +@RequestMapping("/admin/regions") +@Secured({"ROLE_STAFF", "ROLE_ADMIN"}) +public class RegionController { + private static final String REGIONS_BASE = "admin/regions"; + private static final String REGIONS_NEW = REGIONS_BASE + "/new"; + private static final String REGIONS_EDIT = REGIONS_BASE + "/edit"; + + private final RegionService regionService; + private final RegionDtoAssembler regionDTOAssembler = new RegionDtoAssembler(); + + public RegionController(RegionService regionService) { + this.regionService = regionService; + } + + @RequestMapping(method = RequestMethod.GET) + public String allRegions(Model model) { + List regionsDto = regionService.findAll().stream() + .sorted(Comparator.comparing(Region::getId)) + .map(regionDTOAssembler::toModel) + .collect(toList()); + model.addAttribute("regions", regionsDto); + return REGIONS_BASE; + } + + //------------------------------------------------- Creating new region + + @RequestMapping(method = RequestMethod.GET, value = "/new") + public String newRegion(Model model) { + model.addAttribute("region", regionDTOAssembler.toModel(new Region())); + return REGIONS_NEW; + } + + @RequestMapping(method = RequestMethod.POST, value = "/new") + public String postRegion( + @Valid RegionDTO regionDto, BindingResult bindingResult + ) { + if (bindingResult.hasErrors()) + return REGIONS_NEW; + + Region newRegion = regionDTOAssembler.toDomain(regionDto); + regionService.create(newRegion); + return "redirect:/" + REGIONS_BASE; + } + + //-------------------------------------------------- Updating region + + @RequestMapping(method = RequestMethod.GET, value = "/{regionId}/edit") + public String editRegion( + @PathVariable long regionId, Model model + ) { + Region region = regionService.findOne(regionId); + model.addAttribute("region", regionDTOAssembler.toModel(region)); + return REGIONS_EDIT; + } + + @RequestMapping(method = RequestMethod.POST, value = "/{regionId}/edit") + public String putRegion( + @PathVariable long regionId, + @Valid RegionDTO regionDto, BindingResult bindingResult + ) { + if (bindingResult.hasErrors()) + return REGIONS_EDIT; + + Region changedRegion = regionDTOAssembler.toDomain(regionDto); + regionService.update(regionId, changedRegion); + return "redirect:/" + REGIONS_BASE; + } + + //-------------------------------------------------------- Deleting region + + @RequestMapping(method = RequestMethod.POST, value = "/{regionId}/delete") + public String deleteRegion(@PathVariable long regionId) { + regionService.delete(regionId); + return "redirect:/" + REGIONS_BASE; + } +} diff --git a/sut-market/market-web/src/main/java/market/controller/backend/StorageController.java b/sut-market/market-web/src/main/java/market/controller/backend/StorageController.java new file mode 100644 index 0000000..eea5d62 --- /dev/null +++ b/sut-market/market-web/src/main/java/market/controller/backend/StorageController.java @@ -0,0 +1,65 @@ +package market.controller.backend; + +import market.domain.Product; +import market.dto.ProductDTO; +import market.dto.assembler.ProductDtoAssembler; +import market.service.ProductService; +import market.sorting.ISorter; +import market.sorting.SortingValuesDTO; +import market.sorting.StorageSorting; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.security.access.annotation.Secured; +import org.springframework.stereotype.Controller; +import org.springframework.ui.Model; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RequestParam; + +import java.util.*; + +import static java.util.stream.Collectors.groupingBy; + +@Controller +@RequestMapping("/admin/storage") +@Secured({"ROLE_STAFF", "ROLE_ADMIN"}) +public class StorageController { + private static final String STORAGE_BASE = "admin/storage"; + + private final ProductService productService; + private final ISorter storageSorting = new StorageSorting(); + private final ProductDtoAssembler productAssembler = new ProductDtoAssembler(); + + public StorageController(ProductService productService) { + this.productService = productService; + } + + @RequestMapping(method = RequestMethod.GET) + public String getStorageUnits( + SortingValuesDTO sortingValues, + @RequestParam(value = "available", required = false, defaultValue = "all") String available, + Model model + ) { + PageRequest request = storageSorting.updateSorting(sortingValues); + Page pagedProducts = productService.findByAvailability(available, request); + storageSorting.prepareModel(model, pagedProducts.map(productAssembler::toModel)); + + model.addAttribute("currentlyAvailable", available); + return STORAGE_BASE; + } + + @RequestMapping(method = RequestMethod.POST) + public String postStorage( + @RequestParam(value = "productsIds", required = false) Long[] productsIds, + @RequestParam(value = "availableProductsIds", required = false) Long[] availableProductsIds + ) { + if (availableProductsIds != null && productsIds != null) { + Set available = new HashSet<>(Arrays.asList(availableProductsIds)); + Map> productIdsByAvailability = Arrays.stream(productsIds) + .filter(Objects::nonNull) + .collect(groupingBy(available::contains)); + productService.updateAvailability(productIdsByAvailability); + } + return "redirect:/" + STORAGE_BASE; + } +} diff --git a/sut-market/market-web/src/main/java/market/controller/frontend/CartController.java b/sut-market/market-web/src/main/java/market/controller/frontend/CartController.java new file mode 100644 index 0000000..f084551 --- /dev/null +++ b/sut-market/market-web/src/main/java/market/controller/frontend/CartController.java @@ -0,0 +1,215 @@ +package market.controller.frontend; + +import market.controller.CartModelHelper; +import market.domain.Cart; +import market.domain.CartItem; +import market.domain.Product; +import market.dto.CartDTO; +import market.dto.CartItemDTO; +import market.dto.ProductDTO; +import market.dto.assembler.CartDtoAssembler; +import market.dto.assembler.ProductDtoAssembler; +import market.exception.UnknownEntityException; +import market.properties.MarketProperties; +import market.service.CartService; +import market.service.ProductService; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.springframework.http.MediaType; +import org.springframework.stereotype.Controller; +import org.springframework.ui.Model; +import org.springframework.validation.BindingResult; +import org.springframework.web.bind.annotation.ModelAttribute; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.bind.annotation.SessionAttributes; + +import javax.servlet.http.HttpServletRequest; +import javax.validation.Valid; +import java.security.Principal; +import java.util.Map; +import java.util.Optional; + +import static java.util.stream.Collectors.toMap; + +@Controller +@RequestMapping("/cart") +@SessionAttributes({"cart"}) +public class CartController { + private static final Logger log = LogManager.getLogger(CartController.class); + + private static final String CART_BASE = "cart"; + + private final CartService cartService; + private final ProductService productService; + private final CartDtoAssembler cartDtoAssembler; + private final ProductDtoAssembler productDtoAssembler = new ProductDtoAssembler(); + private final MarketProperties marketProperties; + private final CartModelHelper cartModelHelper; + + public CartController(CartService cartService, ProductService productService, MarketProperties marketProperties) { + this.cartService = cartService; + this.productService = productService; + this.marketProperties = marketProperties; + cartDtoAssembler = new CartDtoAssembler(marketProperties); + cartModelHelper = new CartModelHelper(cartDtoAssembler); + } + + @RequestMapping(method = RequestMethod.GET) + public String getCart( + Principal principal, HttpServletRequest request, Model model, + @ModelAttribute(value = "cart") CartDTO cartDto) + { + if (isAuthorized(principal)) { + Cart cart = cartService.getCartOrCreate(principal.getName()); + cartModelHelper.convertAndUpdateAttributes(cart, model, request); + model.addAttribute("productsById", collectProductsMap(cart)); + } else { + Map productsById = cartDto.getCartItems().stream() + .map(CartItemDTO::getProductId) + .map(productService::findById) + .filter(Optional::isPresent) + .map(Optional::get) + .map(productDtoAssembler::toModel) + .collect(toMap(ProductDTO::getProductId, p -> p)); + model.addAttribute("productsById", productsById); + } + model.addAttribute("deliveryCost", marketProperties.getDeliveryCost()); + return CART_BASE; + } + + private Map collectProductsMap(Cart cart) { + return cart.getCartItems().stream() + .map(CartItem::getProduct) + .distinct() + .map(productDtoAssembler::toModel) + .collect(toMap(ProductDTO::getProductId, p -> p)); + } + + @RequestMapping(value = "/clear", method = RequestMethod.POST) + public String clearCart( + Principal principal, HttpServletRequest request, Model model, + @ModelAttribute(value = "cart") CartDTO cartDto) + { + if (isAuthorized(principal)) { + Cart clearedCart = cartService.clearCart(principal.getName()); + cartModelHelper.convertAndUpdateAttributes(clearedCart, model, request); + } else { + Cart cart = cartDtoAssembler.toDomain(cartDto, productService); + cart.clear(); + model.addAttribute("cart", cartDtoAssembler.toAnonymousResource(cart)); + } + return "redirect:/" + CART_BASE; + } + + //--------------------------------------------- Adding item to cart + + @RequestMapping(method = RequestMethod.POST) + public String updateCartByForm( + Model model, HttpServletRequest request, Principal principal, + @Valid @ModelAttribute("cartItem") CartItemDTO cartItemDto, BindingResult bindingResult, + @ModelAttribute(value = "cart") CartDTO cartDto + ) { + if (bindingResult.hasErrors()) + return CART_BASE; + + if (!isAuthorized(principal)) { + CartDTO handledCartDto = updateGuestCart(cartDto, cartItemDto); + model.addAttribute("cart", handledCartDto); + model.addAttribute("deliveryCost", marketProperties.getDeliveryCost()); + return CART_BASE; + } else { + try { + addToAuthorizedCart(cartItemDto, principal.getName(), request, model); + } catch (UnknownEntityException ex) { + bindingResult.addError(ex.getFieldError()); + return CART_BASE; + } + } + return "redirect:/" + CART_BASE; + } + + /** + * Adding via AJAX + * @return updated cart + */ + @RequestMapping(method = RequestMethod.PUT, + consumes = MediaType.APPLICATION_JSON_VALUE, + produces = MediaType.APPLICATION_JSON_VALUE) + @ResponseBody + public CartDTO updateCartByAjax( + Principal principal, HttpServletRequest request, + @Valid @RequestBody CartItemDTO cartItemDto, + BindingResult bindingResult, Model model, + @ModelAttribute(value = "cart") CartDTO cartDto + ) { + if (bindingResult.hasErrors()) + return cartDto; + + if (!isAuthorized(principal)) { + CartDTO handledCartDto = updateGuestCart(cartDto, cartItemDto); + model.addAttribute("cart", handledCartDto); + return handledCartDto; + } else { + try { + return addToAuthorizedCart(cartItemDto, principal.getName(), request, model); + } catch (UnknownEntityException e) { + log.error("Cannot add item to cart", e); + return cartDto; + } + } + } + + private CartDTO addToAuthorizedCart(CartItemDTO itemToAdd, String login, HttpServletRequest request, Model model) { + long productId = itemToAdd.getProductId(); + int quantity = itemToAdd.getQuantity(); + Cart updatedCart = cartService.addToCart(login, productId, quantity); + return cartModelHelper.convertAndUpdateAttributes(updatedCart, model, request); + } + + private CartDTO updateGuestCart(CartDTO cartDto, CartItemDTO newCartItem) { + Optional productOptional = productService.findById(newCartItem.getProductId()); + if (productOptional.isPresent()) { + Product product = productOptional.get(); + if (product.isAvailable()) { + Cart cart = cartDtoAssembler.toDomain(cartDto, productService); + cart.update(product, newCartItem.getQuantity()); + return cartDtoAssembler.toAnonymousResource(cart); + } + } + return cartDto; + } + + //---------------------------------------------- Setting delivery option + + /** + * Setting delivery option via AJAX. + * @return updated cart + */ + @RequestMapping(value = "/delivery/{delivery}", + method = RequestMethod.PUT, + produces = MediaType.APPLICATION_JSON_VALUE) + @ResponseBody + public CartDTO setDelivery( + Principal principal, Model model, HttpServletRequest request, + @PathVariable String delivery, + @ModelAttribute(value = "cart") CartDTO cartDto) + { + boolean included = Boolean.parseBoolean(delivery); + if (isAuthorized(principal)) { + String login = principal.getName(); + Cart updatedCart = cartService.setDelivery(login, included); + return cartModelHelper.convertAndUpdateAttributes(updatedCart, model, request); + } else { + cartDto.setDeliveryIncluded(included); + return cartDto; + } + } + + private boolean isAuthorized(Principal principal) { + return principal != null; + } +} diff --git a/sut-market/market-web/src/main/java/market/controller/frontend/CheckoutController.java b/sut-market/market-web/src/main/java/market/controller/frontend/CheckoutController.java new file mode 100644 index 0000000..0a38f24 --- /dev/null +++ b/sut-market/market-web/src/main/java/market/controller/frontend/CheckoutController.java @@ -0,0 +1,166 @@ +package market.controller.frontend; + +import market.controller.CartModelHelper; +import market.domain.Cart; +import market.domain.CartItem; +import market.domain.Contacts; +import market.domain.Order; +import market.domain.UserAccount; +import market.dto.ContactsDTO; +import market.dto.CreditCardDTO; +import market.dto.ProductDTO; +import market.dto.assembler.CartDtoAssembler; +import market.dto.assembler.ContactsDtoAssembler; +import market.dto.assembler.OrderDtoAssembler; +import market.dto.assembler.ProductDtoAssembler; +import market.dto.assembler.UserAccountDtoAssembler; +import market.exception.EmptyCartException; +import market.properties.MarketProperties; +import market.service.CartService; +import market.service.ContactsService; +import market.service.OrderService; +import market.service.UserAccountService; +import org.springframework.security.access.annotation.Secured; +import org.springframework.stereotype.Controller; +import org.springframework.ui.Model; +import org.springframework.validation.BindingResult; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.SessionAttributes; + +import javax.servlet.http.HttpServletRequest; +import javax.validation.Valid; +import java.security.Principal; +import java.util.Map; +import java.util.function.Function; + +import static java.util.stream.Collectors.toMap; + +/** + * Checkout steps controller. + */ +@Controller +@RequestMapping("/checkout") +@Secured({"ROLE_USER"}) +@SessionAttributes({"createdOrder"}) +public class CheckoutController { + public static final String CHECKOUT_BASE = "checkout"; + public static final String CHECKOUT_DETAILS = CHECKOUT_BASE + "/details"; + public static final String CHECKOUT_PAYMENT = CHECKOUT_BASE + "/payment"; + public static final String CHECKOUT_CONFIRMATION = CHECKOUT_BASE + "/confirmation"; + + private final UserAccountService userAccountService; + private final ContactsService contactsService; + private final OrderService orderService; + private final CartService cartService; + private final OrderDtoAssembler orderDtoAssembler = new OrderDtoAssembler(); + private final ContactsDtoAssembler contactsDtoAssembler = new ContactsDtoAssembler(); + private final UserAccountDtoAssembler accountDtoAssembler = new UserAccountDtoAssembler(); + private final ProductDtoAssembler productDtoAssembler = new ProductDtoAssembler(); + private final MarketProperties marketProperties; + private final CartModelHelper cartModelHelper; + + public CheckoutController(UserAccountService userAccountService, ContactsService contactsService, + OrderService orderService, CartService cartService, MarketProperties marketProperties) + { + this.userAccountService = userAccountService; + this.contactsService = contactsService; + this.orderService = orderService; + this.cartService = cartService; + this.marketProperties = marketProperties; + cartModelHelper = new CartModelHelper(new CartDtoAssembler(marketProperties)); + } + + //--------------------------------------------- Changing contacts + + @RequestMapping(value = "/details", method = RequestMethod.GET) + public String details(Principal principal, Model model) { + String login = principal.getName(); + Cart cart = cartService.getCartOrCreate(login); + if (!cart.isDeliveryIncluded()) + return "redirect:/" + CHECKOUT_PAYMENT; + + model.addAttribute("userContacts", contactsDtoAssembler.toModel(contactsService.getContacts(login))); + return CHECKOUT_DETAILS; + } + + @RequestMapping(value = "/details", method = RequestMethod.POST) + public String changeContacts( + Model model, Principal principal, + @RequestParam(value = "changeContacts") String changeContacts, + @Valid ContactsDTO contactsDto, BindingResult bindingResult + ) { + String login = principal.getName(); + if (!"changeRequested".equals(changeContacts)) { + model.addAttribute("userContacts", contactsDtoAssembler.toModel(contactsService.getContacts(login))); + return "redirect:/" + CHECKOUT_PAYMENT; + } + + if (bindingResult.hasErrors()) + return CHECKOUT_DETAILS; + + Contacts changedContacts = contactsDtoAssembler.toDomain(contactsDto); + contactsService.updateUserContacts(changedContacts, login); + model.addAttribute("userContacts", contactsDtoAssembler.toModel(changedContacts)); + return "redirect:/" + CHECKOUT_PAYMENT; + } + + //------------------------------------------------------- Payment + + @RequestMapping(value = "/payment", method = RequestMethod.GET) + public String getPayment(Principal principal, Model model) { + String login = principal.getName(); + UserAccount account = userAccountService.findByEmail(login); + if (account == null) + return CHECKOUT_DETAILS; + + Cart cart = cartService.getCartOrCreate(login); + Map productsById = cart.getCartItems().stream() + .map(CartItem::getProduct) + .map(productDtoAssembler::toModel) + .collect(toMap(ProductDTO::getProductId, Function.identity())); + model.addAttribute("productsById", productsById); + model.addAttribute("userName", account.getName()); + model.addAttribute("userContacts", contactsDtoAssembler.toModel(account.getContacts())); + model.addAttribute("deliveryCost", marketProperties.getDeliveryCost()); + model.addAttribute("creditCard", new CreditCardDTO()); + return CHECKOUT_PAYMENT; + } + + @RequestMapping(value = "/payment", method = RequestMethod.POST) + public String postPayment( + Principal principal, Model model, HttpServletRequest request, + @Valid CreditCardDTO creditCard, BindingResult bindingResult + ) { + if (bindingResult.hasErrors()) + return CHECKOUT_PAYMENT; + + String login = principal.getName(); + try { + Order order = orderService.createUserOrder(login, marketProperties.getDeliveryCost(), creditCard.getCcNumber()); + model.addAttribute("createdOrder", orderDtoAssembler.toModel(order)); + + Cart cart = cartService.getCartOrCreate(login); + cartModelHelper.convertAndUpdateAttributes(cart, model, request); + + return "redirect:/" + CHECKOUT_CONFIRMATION; + } catch (EmptyCartException ex) { + bindingResult.addError(ex.getFieldError()); + return CHECKOUT_PAYMENT; + } + } + + //---------------------------------- Gratitude + + @RequestMapping(value = "/confirmation", method = RequestMethod.GET) + public String getGratitude(Principal principal, Model model, HttpServletRequest request) { + UserAccount account = userAccountService.findByEmail(principal.getName()); + if (account == null) + return CHECKOUT_DETAILS; + + model.addAttribute("userAccount", accountDtoAssembler.toModel(account)); + model.addAttribute("userContacts", contactsDtoAssembler.toModel(account.getContacts())); + return CHECKOUT_CONFIRMATION; + } +} diff --git a/sut-market/market-web/src/main/java/market/controller/frontend/CustomerController.java b/sut-market/market-web/src/main/java/market/controller/frontend/CustomerController.java new file mode 100644 index 0000000..6f3edb5 --- /dev/null +++ b/sut-market/market-web/src/main/java/market/controller/frontend/CustomerController.java @@ -0,0 +1,154 @@ +package market.controller.frontend; + +import market.controller.CartModelHelper; +import market.domain.Cart; +import market.domain.Order; +import market.domain.OrderedProduct; +import market.domain.UserAccount; +import market.dto.CartDTO; +import market.dto.OrderDTO; +import market.dto.OrderedProductDTO; +import market.dto.ProductDTO; +import market.dto.UserDTO; +import market.dto.assembler.CartDtoAssembler; +import market.dto.assembler.OrderDtoAssembler; +import market.dto.assembler.OrderedProductDtoAssembler; +import market.dto.assembler.ProductDtoAssembler; +import market.dto.assembler.UserAccountDtoAssembler; +import market.exception.EmailExistsException; +import market.properties.MarketProperties; +import market.security.AuthenticationService; +import market.service.CartService; +import market.service.OrderService; +import market.service.ProductService; +import market.service.UserAccountService; +import org.springframework.security.access.annotation.Secured; +import org.springframework.stereotype.Controller; +import org.springframework.ui.Model; +import org.springframework.validation.BindingResult; +import org.springframework.web.bind.annotation.ModelAttribute; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.SessionAttributes; + +import javax.servlet.http.HttpServletRequest; +import javax.validation.Valid; +import java.security.Principal; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import static java.util.stream.Collectors.toList; +import static java.util.stream.Collectors.toMap; + +@Controller +@RequestMapping("/customer") +@SessionAttributes({"cart"}) +public class CustomerController { + private static final String CUSTOMER_ORDERS = "customer/orders"; + private static final String CUSTOMER_NEW = "customer/new"; + private static final String ROOT = "/"; + + private final UserAccountService userAccountService; + private final CartService cartService; + private final OrderService orderService; + private final ProductService productService; + private final AuthenticationService authenticationService; + + private final UserAccountDtoAssembler userAccountDtoAssembler = new UserAccountDtoAssembler(); + private final OrderDtoAssembler orderDtoAssembler = new OrderDtoAssembler(); + private final OrderedProductDtoAssembler orderedProductDtoAssembler = new OrderedProductDtoAssembler(); + private final ProductDtoAssembler productDtoAssembler = new ProductDtoAssembler(); + private final CartDtoAssembler cartDtoAssembler; + private final CartModelHelper cartModelHelper; + + public CustomerController(UserAccountService userAccountService, OrderService orderService, + AuthenticationService authenticationService, CartService cartService, ProductService productService, + MarketProperties marketProperties) + { + this.userAccountService = userAccountService; + this.orderService = orderService; + this.authenticationService = authenticationService; + this.cartService = cartService; + this.productService = productService; + cartDtoAssembler = new CartDtoAssembler(marketProperties); + cartModelHelper = new CartModelHelper(cartDtoAssembler); + } + + @Secured({"ROLE_USER"}) + @RequestMapping(value = "/orders", method = RequestMethod.GET) + public String orders(Principal principal, Model model) { + if (!isAuthorized(principal)) + return "redirect:" + ROOT; + + List orders = orderService.getUserOrders(principal.getName()); + List ordersDto = orders.stream() + .map(orderDtoAssembler::toModel) + .collect(toList()); + model.addAttribute("userOrders", ordersDto); + + Map> orderedProductsByOrderId = new HashMap<>(); + for (Order order : orders) { + List productsDto = order.getOrderedProducts().stream() + .map(orderedProductDtoAssembler::toModel) + .collect(toList()); + orderedProductsByOrderId.put(order.getId(), productsDto); + } + model.addAttribute("orderedProductsByOrderId", orderedProductsByOrderId); + + Map productsById = orders.stream() + .map(Order::getOrderedProducts) + .flatMap(Collection::stream) + .map(OrderedProduct::getProduct) + .distinct() + .map(productDtoAssembler::toModel) + .collect(toMap(ProductDTO::getProductId, p -> p)); + model.addAttribute("productsById", productsById); + + return CUSTOMER_ORDERS; + } + + private boolean isAuthorized(Principal principal) { + return principal != null; + } + + //----------------------------------------- Registering new account + + @RequestMapping(value = "/new", method = RequestMethod.GET) + public String getRegistrationPage(Model model) { + model.addAttribute("userAccount", new UserDTO()); + return CUSTOMER_NEW; + } + + @RequestMapping(value = "/new", method = RequestMethod.POST) + public String postRegistrationForm( + Model model, HttpServletRequest request, + @Valid UserDTO user, BindingResult bindingResult, + @ModelAttribute(value = "cart") CartDTO cartDto + ) { + model.addAttribute("userAccount", user); // place user data back to redirect him back to pre-filled registration form + if (bindingResult.hasErrors()) + return CUSTOMER_NEW; + + UserAccount account = userAccountDtoAssembler.toDomain(user); + UserAccount newAccount; + try { + newAccount = userAccountService.create(account); + } catch (EmailExistsException e) { + bindingResult.addError(e.getFieldError()); + return CUSTOMER_NEW; + } + boolean authenticated = authenticationService.authenticate(account.getEmail(), user.getPassword()); + if (!authenticated) + return CUSTOMER_NEW; + + model.addAttribute("userAccount", userAccountDtoAssembler.toModel(newAccount)); // now add the authorized data + + Cart unauthorisedCart = cartDtoAssembler.toDomain(cartDto, productService); + Cart updatedCart = cartService.addAllToCart(newAccount.getEmail(), unauthorisedCart.getCartItems()); + cartModelHelper.convertAndUpdateAttributes(updatedCart, model, request); + + return "redirect:" + ROOT; + } +} diff --git a/sut-market/market-web/src/main/java/market/controller/frontend/FrontendController.java b/sut-market/market-web/src/main/java/market/controller/frontend/FrontendController.java new file mode 100644 index 0000000..7d81774 --- /dev/null +++ b/sut-market/market-web/src/main/java/market/controller/frontend/FrontendController.java @@ -0,0 +1,64 @@ +package market.controller.frontend; + +import market.domain.Region; +import market.dto.RegionDTO; +import market.dto.assembler.RegionDtoAssembler; +import market.service.RegionService; +import org.springframework.stereotype.Controller; +import org.springframework.ui.Model; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; + +import java.util.List; + +import static java.util.stream.Collectors.toList; + +/** + * Frontend root pages controller. + */ +@Controller +public class FrontendController { + private final RegionService regionService; + private final RegionDtoAssembler regionDTOAssembler = new RegionDtoAssembler(); + + public FrontendController(RegionService regionService) { + this.regionService = regionService; + } + + /** + * Title page. + */ + @RequestMapping(value = {"", "/", "/index"}, method = RequestMethod.GET) + public String index(Model model) { + List regionsDto = regionService.findAll().stream() + .map(regionDTOAssembler::toModel) + .collect(toList()); + model.addAttribute("regions", regionsDto); + model.addAttribute("selectedRegion", Region.NULL); + return "index"; + } + + /** + * Login page. + */ + @RequestMapping(value = "/login", method = RequestMethod.GET) + public String login() { + return "login"; + } + + /** + * Implementation description page. + */ + @RequestMapping(method = RequestMethod.GET, value = "/inside") + public String whatsInside() { + return "inside"; + } + + /** + * REST description page. + */ + @RequestMapping(method = RequestMethod.GET, value = "/rest-api") + public String restApi() { + return "rest"; + } +} diff --git a/sut-market/market-web/src/main/java/market/controller/frontend/ShowcaseController.java b/sut-market/market-web/src/main/java/market/controller/frontend/ShowcaseController.java new file mode 100644 index 0000000..52263b1 --- /dev/null +++ b/sut-market/market-web/src/main/java/market/controller/frontend/ShowcaseController.java @@ -0,0 +1,90 @@ +package market.controller.frontend; + +import market.domain.Distillery; +import market.domain.Product; +import market.domain.Region; +import market.dto.DistilleryDTO; +import market.dto.ProductDTO; +import market.dto.RegionDTO; +import market.dto.assembler.DistilleryDtoAssembler; +import market.dto.assembler.ProductDtoAssembler; +import market.dto.assembler.RegionDtoAssembler; +import market.service.DistilleryService; +import market.service.ProductService; +import market.service.RegionService; +import market.sorting.ISorter; +import market.sorting.ProductSorting; +import market.sorting.SortingValuesDTO; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.stereotype.Controller; +import org.springframework.ui.Model; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RequestParam; + +import java.util.Comparator; +import java.util.List; + +import static java.util.stream.Collectors.toList; + +/** + * Region products showcase. + */ +@Controller +@RequestMapping("/regions") +public class ShowcaseController { + private static final String REGIONS_BASE = "regions"; + + private final RegionService regionService; + private final ProductService productService; + private final DistilleryService distilleryService; + private final ISorter productSorting = new ProductSorting(); + private final ProductDtoAssembler productAssembler = new ProductDtoAssembler(); + private final RegionDtoAssembler regionDTOAssembler = new RegionDtoAssembler(); + private final DistilleryDtoAssembler distilleryDTOAssembler = new DistilleryDtoAssembler(); + + public ShowcaseController(RegionService regionService, ProductService productService, DistilleryService distilleryService) { + this.regionService = regionService; + this.productService = productService; + this.distilleryService = distilleryService; + } + + /** + * Region products page. Filtering by distillery and sorting. + */ + @RequestMapping(method = RequestMethod.GET, value = "/{regionId}") + public String getRegionProducts( + @PathVariable long regionId, + SortingValuesDTO sortingValues, + @RequestParam(value = "dist", required = false, defaultValue = "0") Long distilleryId, + Model model + ) { + Region region = regionService.findOne(regionId); + + PageRequest request = productSorting.updateSorting(sortingValues); + Page pagedProducts; + if (distilleryId == 0) { + pagedProducts = productService.findByRegion(region, request); + } else { + Distillery distillery = distilleryService.findById(distilleryId); + pagedProducts = productService.findByDistillery(distillery, request); + model.addAttribute("currentDistilleryTitle", distillery.getTitle()); + } + productSorting.prepareModel(model, pagedProducts.map(productAssembler::toModel)); + + List distilleriesDto = distilleryService.findByRegion(region).stream() + .map(distilleryDTOAssembler::toModel) + .collect(toList()); + model.addAttribute("distilleries", distilleriesDto); + + List regionsDto = regionService.findAll().stream() + .sorted(Comparator.comparing(Region::getId)) + .map(regionDTOAssembler::toModel) + .collect(toList()); + model.addAttribute("regions", regionsDto); + model.addAttribute("selectedRegion", regionDTOAssembler.toModel(region)); + return REGIONS_BASE; + } +} diff --git a/sut-market/market-web/src/main/java/market/interceptors/SessionCartInterceptor.java b/sut-market/market-web/src/main/java/market/interceptors/SessionCartInterceptor.java new file mode 100644 index 0000000..777283e --- /dev/null +++ b/sut-market/market-web/src/main/java/market/interceptors/SessionCartInterceptor.java @@ -0,0 +1,24 @@ +package market.interceptors; + +import market.dto.CartDTO; +import org.springframework.web.servlet.handler.HandlerInterceptorAdapter; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.HttpSession; + +/** + * Перехватчик сеансовой корзины. + *

+ * При отсутствии корзины в сессии создаёт новую корзину. + */ +public class SessionCartInterceptor extends HandlerInterceptorAdapter { + + @Override + public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { + HttpSession session = request.getSession(true); + if (session.getAttribute("cart") == null) + session.setAttribute("cart", new CartDTO()); + return super.preHandle(request, response, handler); + } +} diff --git a/sut-market/market-web/src/main/java/market/properties/PaginationProperties.java b/sut-market/market-web/src/main/java/market/properties/PaginationProperties.java new file mode 100644 index 0000000..d75dc76 --- /dev/null +++ b/sut-market/market-web/src/main/java/market/properties/PaginationProperties.java @@ -0,0 +1,45 @@ +package market.properties; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +@Component +public class PaginationProperties { + private final int backendProduct; + private final int backendOrder; + + public PaginationProperties( + @Value("${pagination.backend.product}") int backendProduct, + @Value("${pagination.backend.order}") int backendOrder + ) { + this.backendProduct = backendProduct; + this.backendOrder = backendOrder; + } + + public int getBackendProduct() { + return backendProduct; + } + + public int getBackendOrder() { + return backendOrder; + } + + public static class Builder { + private int backendProduct; + private int backendOrder; + + public PaginationProperties build() { + return new PaginationProperties(backendProduct, backendOrder); + } + + public Builder setBackendProduct(int backendProduct) { + this.backendProduct = backendProduct; + return this; + } + + public Builder setBackendOrder(int backendOrder) { + this.backendOrder = backendOrder; + return this; + } + } +} \ No newline at end of file diff --git a/sut-market/market-web/src/main/java/market/security/CustomAuthenticationSuccessHandler.java b/sut-market/market-web/src/main/java/market/security/CustomAuthenticationSuccessHandler.java new file mode 100644 index 0000000..e889301 --- /dev/null +++ b/sut-market/market-web/src/main/java/market/security/CustomAuthenticationSuccessHandler.java @@ -0,0 +1,66 @@ +package market.security; + +import market.domain.Cart; +import market.domain.UserAccount; +import market.dto.CartDTO; +import market.dto.assembler.CartDtoAssembler; +import market.properties.MarketProperties; +import market.service.UserAccountService; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.authority.AuthorityUtils; +import org.springframework.security.web.authentication.AuthenticationSuccessHandler; + +import javax.servlet.ServletContext; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.util.Set; + +public class CustomAuthenticationSuccessHandler implements AuthenticationSuccessHandler { + private static final Logger log = LogManager.getLogger(CustomAuthenticationSuccessHandler.class); + + private final ServletContext servletContext; + private final UserAccountService userAccountService; + private final CartDtoAssembler cartDtoAssembler; + + public CustomAuthenticationSuccessHandler(ServletContext servletContext, UserAccountService userAccountService, + MarketProperties marketProperties) + { + this.servletContext = servletContext; + this.userAccountService = userAccountService; + cartDtoAssembler = new CartDtoAssembler(marketProperties); + } + + @Override + public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, + Authentication authentication) throws IOException + { + Set roles = AuthorityUtils.authorityListToSet(authentication.getAuthorities()); + if (roles.contains("ROLE_USER")) { + UserAccount account = userAccountService.findByEmail(authentication.getName()); + CartDTO cartDto = prepareCartDto(account); + request.getSession().setAttribute("cart", cartDto); + } + if (isStaff(roles)) { + response.sendRedirect(servletContext.getContextPath() + "/admin/"); + } else { + response.sendRedirect(servletContext.getContextPath() + "/"); + } + request.getSession(false).setMaxInactiveInterval(30); + } + + private CartDTO prepareCartDto(UserAccount account) { + Cart cart = account.getCart(); + if (cart == null) { + log.warn(String.format("Account #%d has no cart, this shall never happen", account.getId())); + return null; + } + return cartDtoAssembler.toModel(cart); + } + + private boolean isStaff(Set roles) { + return roles.contains("ROLE_ADMIN") || roles.contains("ROLE_STAFF"); + } +} diff --git a/sut-market/market-web/src/main/java/market/sorting/AbstractSorter.java b/sut-market/market-web/src/main/java/market/sorting/AbstractSorter.java new file mode 100644 index 0000000..bcb9e54 --- /dev/null +++ b/sut-market/market-web/src/main/java/market/sorting/AbstractSorter.java @@ -0,0 +1,169 @@ +package market.sorting; + +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Sort; +import org.springframework.ui.Model; + +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.stream.IntStream; + +import static java.util.stream.Collectors.toList; + +/** + * Управляющий сортировкой и разбивкой на страницы. + *

+ * Инкапсулирует операции с опциями сортировки и разбивки на страницы: хранение + * и обновление значений, а также дополнение модели необходимыми объектами в + * соответствии с текущими значениями. + *

+ * Добавление перечня опций сортировки (по умолчанию пустой) и другого + * дополнительного функционала (e.g. фильтрации) осуществляется в классах-потомках. + * + * @param класс элементов обрабатываемого списка + */ +public abstract class AbstractSorter implements ISorter { + + public static Integer FIRST_PAGE = 1; + public static Integer PAGE_SIZE_DEFAULT = 2; + public static Sort.Direction DIRECTION_DEFAULT = Sort.Direction.ASC; + + protected final Map sortFieldOptions = new LinkedHashMap<>(); + private final Map pageSizeOptions = new LinkedHashMap<>(); + private final Map directionOptions = new LinkedHashMap<>(); + private Integer pageNumber; + private Integer pageSize; + private String sortBy; + private Sort.Direction sortDirection; + + public AbstractSorter() { + directionOptions.put(DIRECTION_DEFAULT.toString(), "по возрастанию"); + directionOptions.put(Sort.Direction.DESC.toString(), "по убыванию"); + + pageSizeOptions.put(2, "2"); + pageSizeOptions.put(5, "5"); + pageSizeOptions.put(10, "10"); + pageSizeOptions.put(20, "20"); + } + + //-------------------------------------------------------- Обновление опций + + private static Sort.Direction parseSortDirection(String direction) { + if (direction == null) + return DIRECTION_DEFAULT; + return Sort.Direction.fromOptionalString(direction).orElse(DIRECTION_DEFAULT); + } + + @Override + public PageRequest updateSorting(SortingValuesDTO values) { + this.sortBy = (values.getSort() == null) ? getSortFieldDefault() : values.getSort(); + this.pageSize = (values.getSize() == null) ? getDefaultPageSize() : values.getSize(); + this.pageNumber = (values.getPage() == null) ? FIRST_PAGE : values.getPage(); + this.sortDirection = parseSortDirection(values.getDirect()); + return createPageRequest(); + } + + /** + * @return кол-во объектов на странице + */ + protected int getDefaultPageSize() { + return PAGE_SIZE_DEFAULT; + } + + private String getSortFieldDefault() { + return sortFieldOptions.keySet().iterator().next(); + } + + private PageRequest createPageRequest() { + return PageRequest.of( + getPageNumber() - 1, + getPageSize(), + getSortDirection(), + getSortBy()); + } + + //------------------------------------------------------- Подготовка модели + + @Override + public Model prepareModel(Model model, Page page) { + preparePagedModel(model, page); + prepareSortedModel(model); + prepareFilteredModel(model); + return model; + } + + /** + * Дополнение модели объектами разбивки на страницы. + * + * @param model изменяемая модель + * @return изменённая модель + */ + protected Model preparePagedModel(Model model, Page page) { + int current = page.getNumber() + 1; + int begin = Math.max(1, current - 5); + int end = Math.min(begin + 10, page.getTotalPages()); + model.addAttribute("page", page); + model.addAttribute("beginIndex", begin); + model.addAttribute("endIndex", end); + model.addAttribute("currentIndex", current); + model.addAttribute("indexesList", IntStream.rangeClosed(begin, end).boxed().collect(toList())); + return model; + } + + /** + * Дополнение модели объектами сортировки. + * + * @param model изменяемая модель + * @return изменённая модель + */ + protected Model prepareSortedModel(Model model) { + model.addAttribute("pageSizeOptions", getPageSizeOptions()); + model.addAttribute("sortOptions", getSortFieldOptions()); + model.addAttribute("directOptions", getDirectionOptions()); + model.addAttribute("currentPageSize", getPageSize()); + model.addAttribute("currentSort", getSortBy()); + model.addAttribute("currentDirection", getSortDirection().toString()); + return model; + } + + /** + * Дополнение модели объектами фильтрации. + * + * @param model изменяемая модель + * @return изменённая модель + */ + protected Model prepareFilteredModel(Model model) { + return model; + } + + //---------------------------------------------------- Аксессоры и мутаторы + + public Integer getPageNumber() { + return pageNumber; + } + + public Integer getPageSize() { + return pageSize; + } + + public String getSortBy() { + return sortBy; + } + + public Sort.Direction getSortDirection() { + return sortDirection; + } + + public Map getPageSizeOptions() { + return pageSizeOptions; + } + + public Map getSortFieldOptions() { + return sortFieldOptions; + } + + public Map getDirectionOptions() { + return directionOptions; + } +} diff --git a/sut-market/market-web/src/main/java/market/sorting/ISorter.java b/sut-market/market-web/src/main/java/market/sorting/ISorter.java new file mode 100644 index 0000000..a7b757f --- /dev/null +++ b/sut-market/market-web/src/main/java/market/sorting/ISorter.java @@ -0,0 +1,33 @@ +package market.sorting; + +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.ui.Model; + +/** + * Интерфейс опций сортировки и разбивки на страницы. + * + * @param класс элементов обрабатываемого списка + */ +public interface ISorter { + + /** + * Обновление значений опций сортировки. + * + * @param sortingValues новые значения опций + * @return поисковый запрос для обращения к ДАО + */ + PageRequest updateSorting(SortingValuesDTO sortingValues); + + /** + * Добавление данных в модель. + *

+ * Добавляет в модель данные и все служебные объекты, связанные + * с постраничным отображаением и сортировкой. + * + * @param model модель, которая будет обновлена + * @param page результаты постраничной выборки из БД + * @return дополненная модель + */ + Model prepareModel(Model model, Page page); +} diff --git a/sut-market/market-web/src/main/java/market/sorting/OrderSorting.java b/sut-market/market-web/src/main/java/market/sorting/OrderSorting.java new file mode 100644 index 0000000..6109d47 --- /dev/null +++ b/sut-market/market-web/src/main/java/market/sorting/OrderSorting.java @@ -0,0 +1,46 @@ +package market.sorting; + +import org.springframework.stereotype.Component; +import org.springframework.ui.Model; + +import java.util.LinkedHashMap; +import java.util.Map; + +/** + * Опции сортировки и фильтрации списка заказов. + */ +@Component +public class OrderSorting extends AbstractSorter { + private final int defaultPageSize; + + private final Map executedOptions = new LinkedHashMap<>(); + private final Map createdOptions = new LinkedHashMap<>(); + + public OrderSorting(int defaultPageSize) { + this.defaultPageSize = defaultPageSize; + + sortFieldOptions.put("dateCreated", "по дате оформления"); + sortFieldOptions.put("bill.totalCost", "по сумме"); + sortFieldOptions.put("userAccount.name", "по имени покупателя"); + + executedOptions.put("all", "все заказы"); + executedOptions.put("true", "только исполненные"); + executedOptions.put("false", "только в исполнении"); + createdOptions.put("all", "за всё время"); + createdOptions.put("1", "за сутки"); + createdOptions.put("7", "за 7 дней"); + createdOptions.put("30", "за 30 дней"); + } + + @Override + public int getDefaultPageSize() { + return defaultPageSize; + } + + @Override + public Model prepareFilteredModel(Model model) { + model.addAttribute("executedOptions", executedOptions); + model.addAttribute("createdOptions", createdOptions); + return model; + } +} diff --git a/sut-market/market-web/src/main/java/market/sorting/ProductBackendSorting.java b/sut-market/market-web/src/main/java/market/sorting/ProductBackendSorting.java new file mode 100644 index 0000000..f5697c9 --- /dev/null +++ b/sut-market/market-web/src/main/java/market/sorting/ProductBackendSorting.java @@ -0,0 +1,24 @@ +package market.sorting; + +import org.springframework.stereotype.Component; + +/** + * Опции сортировки и фильтрации списка товаров. + */ +@Component +public class ProductBackendSorting extends AbstractSorter { + + private final int defaultPageSize; + + public ProductBackendSorting(int defaultPageSize) { + this.defaultPageSize = defaultPageSize; + sortFieldOptions.put("price", "по цене"); + sortFieldOptions.put("distillery.title", "по винокурне"); + sortFieldOptions.put("age", "по возрасту"); + } + + @Override + public int getDefaultPageSize() { + return defaultPageSize; + } +} diff --git a/sut-market/market-web/src/main/java/market/sorting/ProductSorting.java b/sut-market/market-web/src/main/java/market/sorting/ProductSorting.java new file mode 100644 index 0000000..e579967 --- /dev/null +++ b/sut-market/market-web/src/main/java/market/sorting/ProductSorting.java @@ -0,0 +1,16 @@ +package market.sorting; + +import org.springframework.stereotype.Component; + +/** + * Опции сортировки и фильтрации списка товаров. + */ +@Component +public class ProductSorting extends AbstractSorter { + + public ProductSorting() { + sortFieldOptions.put("price", "по цене"); + sortFieldOptions.put("distillery.title", "по винокурне"); + sortFieldOptions.put("age", "по возрасту"); + } +} diff --git a/sut-market/market-web/src/main/java/market/sorting/SortingValuesDTO.java b/sut-market/market-web/src/main/java/market/sorting/SortingValuesDTO.java new file mode 100644 index 0000000..4c9a227 --- /dev/null +++ b/sut-market/market-web/src/main/java/market/sorting/SortingValuesDTO.java @@ -0,0 +1,44 @@ +package market.sorting; + +/** + * + */ +public class SortingValuesDTO { + + private Integer page; + private Integer size; + private String sort; + private String direct; + + public Integer getPage() { + return page; + } + + public void setPage(Integer page) { + this.page = page; + } + + public Integer getSize() { + return size; + } + + public void setSize(Integer size) { + this.size = size; + } + + public String getSort() { + return sort; + } + + public void setSort(String sort) { + this.sort = sort; + } + + public String getDirect() { + return direct; + } + + public void setDirect(String direct) { + this.direct = direct; + } +} diff --git a/sut-market/market-web/src/main/java/market/sorting/StorageSorting.java b/sut-market/market-web/src/main/java/market/sorting/StorageSorting.java new file mode 100644 index 0000000..ef4d055 --- /dev/null +++ b/sut-market/market-web/src/main/java/market/sorting/StorageSorting.java @@ -0,0 +1,36 @@ +package market.sorting; + +import org.springframework.stereotype.*; +import org.springframework.ui.*; + +import java.util.*; + +/** + * Опции сортировки и фильтрации списка единиц хранения. + */ +@Component +public class StorageSorting extends AbstractSorter { + + private final Map availableOptions = new LinkedHashMap<>(); + + public StorageSorting() { + sortFieldOptions.put("price", "по цене"); + sortFieldOptions.put("distillery.title", "по винокурне"); + sortFieldOptions.put("age", "по возрасту"); + + availableOptions.put("all", "все товары"); + availableOptions.put("true", "только в наличии"); + availableOptions.put("false", "только отсутствующие"); + } + + @Override + public int getDefaultPageSize() { + return 10; + } + + @Override + public Model prepareFilteredModel(Model model) { + model.addAttribute("availableOptions", availableOptions); + return model; + } +} diff --git a/sut-market/market-web/src/main/resources/application.yml b/sut-market/market-web/src/main/resources/application.yml new file mode 100644 index 0000000..8e6045d --- /dev/null +++ b/sut-market/market-web/src/main/resources/application.yml @@ -0,0 +1,50 @@ +server: + port: ${PORT:8080} + +spring: + application: + name: market-web + jpa: + show-sql: true + generate-ddl: false + hibernate: + ddl-auto: none + properties: + hibernate: + dialect: org.hibernate.dialect.PostgreSQL9Dialect + hbm2ddl.auto: none + messages: + basename: messages + fallback-to-system-locale: false + use-code-as-default-message: true + +--- + +spring: + profiles: default + datasource: + url: jdbc:h2:mem:market;DB_CLOSE_DELAY=-1 + driverClassName: org.h2.Driver + username: sa + password: + thymeleaf: + cache: false + mode: HTML + encoding: UTF-8 + resources: + cache: + period: 0 + +--- + +spring: + profiles: prod + datasource: + url: jdbc:postgresql://${DB_HOST:localhost}:5432/market + driverClassName: org.postgresql.Driver + username: market + password: market + thymeleaf: + cache: true + mode: HTML + encoding: UTF-8 \ No newline at end of file diff --git a/sut-market/market-web/src/main/resources/log4j2.xml b/sut-market/market-web/src/main/resources/log4j2.xml new file mode 100644 index 0000000..3249bf4 --- /dev/null +++ b/sut-market/market-web/src/main/resources/log4j2.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/sut-market/market-web/src/main/resources/market.properties b/sut-market/market-web/src/main/resources/market.properties new file mode 100644 index 0000000..b274982 --- /dev/null +++ b/sut-market/market-web/src/main/resources/market.properties @@ -0,0 +1,4 @@ +deliveryCost=400 + +pagination.backend.product=20 +pagination.backend.order=10 \ No newline at end of file diff --git a/sut-market/market-web/src/main/resources/static/css/bootstrap.min.css b/sut-market/market-web/src/main/resources/static/css/bootstrap.min.css new file mode 100644 index 0000000..c3540c3 --- /dev/null +++ b/sut-market/market-web/src/main/resources/static/css/bootstrap.min.css @@ -0,0 +1,7 @@ +/*! + * Bootstrap v3.1.1 (http://getbootstrap.com) + * Copyright 2011-2014 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + */ + +/*! normalize.css v3.0.0 | MIT License | git.io/normalize */html{font-family:sans-serif;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}body{margin:0}article,aside,details,figcaption,figure,footer,header,hgroup,main,nav,section,summary{display:block}audio,canvas,progress,video{display:inline-block;vertical-align:baseline}audio:not([controls]){display:none;height:0}[hidden],template{display:none}a{background:transparent}a:active,a:hover{outline:0}abbr[title]{border-bottom:1px dotted}b,strong{font-weight:bold}dfn{font-style:italic}h1{font-size:2em;margin:0.67em 0}mark{background:#ff0;color:#000}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sup{top:-0.5em}sub{bottom:-0.25em}img{border:0}svg:not(:root){overflow:hidden}figure{margin:1em 40px}hr{-moz-box-sizing:content-box;box-sizing:content-box;height:0}pre{overflow:auto}code,kbd,pre,samp{font-family:monospace, monospace;font-size:1em}button,input,optgroup,select,textarea{color:inherit;font:inherit;margin:0}button{overflow:visible}button,select{text-transform:none}button,html input[type="button"],input[type="reset"],input[type="submit"]{-webkit-appearance:button;cursor:pointer}button[disabled],html input[disabled]{cursor:default}button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}input{line-height:normal}input[type="checkbox"],input[type="radio"]{box-sizing:border-box;padding:0}input[type="number"]::-webkit-inner-spin-button,input[type="number"]::-webkit-outer-spin-button{height:auto}input[type="search"]{-webkit-appearance:textfield;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;box-sizing:content-box}input[type="search"]::-webkit-search-cancel-button,input[type="search"]::-webkit-search-decoration{-webkit-appearance:none}fieldset{border:1px solid #c0c0c0;margin:0 2px;padding:0.35em 0.625em 0.75em}legend{border:0;padding:0}textarea{overflow:auto}optgroup{font-weight:bold}table{border-collapse:collapse;border-spacing:0}td,th{padding:0}@media print{*{text-shadow:none !important;color:#000 !important;background:transparent !important;box-shadow:none !important}a,a:visited{text-decoration:underline}a[href]:after{content:" (" attr(href) ")"}abbr[title]:after{content:" (" attr(title) ")"}a[href^="javascript:"]:after,a[href^="#"]:after{content:""}pre,blockquote{border:1px solid #999;page-break-inside:avoid}thead{display:table-header-group}tr,img{page-break-inside:avoid}img{max-width:100% !important}p,h2,h3{orphans:3;widows:3}h2,h3{page-break-after:avoid}select{background:#fff !important}.navbar{display:none}.table td,.table th{background-color:#fff !important}.btn>.caret,.dropup>.btn>.caret{border-top-color:#000 !important}.label{border:1px solid #000}.table{border-collapse:collapse !important}.table-bordered th,.table-bordered td{border:1px solid #ddd !important}}*{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}*:before,*:after{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}html{font-size:62.5%;-webkit-tap-highlight-color:rgba(0,0,0,0)}body{font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:14px;line-height:1.42857143;color:#333;background-color:#fff}input,button,select,textarea{font-family:inherit;font-size:inherit;line-height:inherit}a{color:#5289b3;text-decoration:none}a:hover,a:focus{color:#386180;text-decoration:underline}a:focus{outline:thin dotted;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}figure{margin:0}img{vertical-align:middle}.img-responsive,.thumbnail>img,.thumbnail a>img,.carousel-inner>.item>img,.carousel-inner>.item>a>img{display:block;max-width:100%;height:auto}.img-rounded{border-radius:6px}.img-thumbnail{padding:4px;line-height:1.42857143;background-color:#fff;border:1px solid #ddd;border-radius:4px;-webkit-transition:all .2s ease-in-out;transition:all .2s ease-in-out;display:inline-block;max-width:100%;height:auto}.img-circle{border-radius:50%}hr{margin-top:20px;margin-bottom:20px;border:0;border-top:1px solid #eee}.sr-only{position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;clip:rect(0, 0, 0, 0);border:0}h1,h2,h3,h4,h5,h6,.h1,.h2,.h3,.h4,.h5,.h6{font-family:inherit;font-weight:500;line-height:1.1;color:inherit}h1 small,h2 small,h3 small,h4 small,h5 small,h6 small,.h1 small,.h2 small,.h3 small,.h4 small,.h5 small,.h6 small,h1 .small,h2 .small,h3 .small,h4 .small,h5 .small,h6 .small,.h1 .small,.h2 .small,.h3 .small,.h4 .small,.h5 .small,.h6 .small{font-weight:normal;line-height:1;color:#999}h1,.h1,h2,.h2,h3,.h3{margin-top:20px;margin-bottom:10px}h1 small,.h1 small,h2 small,.h2 small,h3 small,.h3 small,h1 .small,.h1 .small,h2 .small,.h2 .small,h3 .small,.h3 .small{font-size:65%}h4,.h4,h5,.h5,h6,.h6{margin-top:10px;margin-bottom:10px}h4 small,.h4 small,h5 small,.h5 small,h6 small,.h6 small,h4 .small,.h4 .small,h5 .small,.h5 .small,h6 .small,.h6 .small{font-size:75%}h1,.h1{font-size:36px}h2,.h2{font-size:30px}h3,.h3{font-size:24px}h4,.h4{font-size:18px}h5,.h5{font-size:14px}h6,.h6{font-size:12px}p{margin:0 0 10px}.lead{margin-bottom:20px;font-size:16px;font-weight:200;line-height:1.4}@media (min-width:768px){.lead{font-size:21px}}small,.small{font-size:85%}cite{font-style:normal}.text-left{text-align:left}.text-right{text-align:right}.text-center{text-align:center}.text-justify{text-align:justify}.text-muted{color:#999}.text-primary{color:#5289b3}a.text-primary:hover{color:#406e92}.text-success{color:#3c763d}a.text-success:hover{color:#2b542c}.text-info{color:#31708f}a.text-info:hover{color:#245269}.text-warning{color:#8a6d3b}a.text-warning:hover{color:#66512c}.text-danger{color:#a94442}a.text-danger:hover{color:#843534}.bg-primary{color:#fff;background-color:#5289b3}a.bg-primary:hover{background-color:#406e92}.bg-success{background-color:#dff0d8}a.bg-success:hover{background-color:#c1e2b3}.bg-info{background-color:#d9edf7}a.bg-info:hover{background-color:#afd9ee}.bg-warning{background-color:#fcf8e3}a.bg-warning:hover{background-color:#f7ecb5}.bg-danger{background-color:#f2dede}a.bg-danger:hover{background-color:#e4b9b9}.page-header{padding-bottom:9px;margin:40px 0 20px;border-bottom:1px solid #eee}ul,ol{margin-top:0;margin-bottom:10px}ul ul,ol ul,ul ol,ol ol{margin-bottom:0}.list-unstyled{padding-left:0;list-style:none}.list-inline{padding-left:0;list-style:none;margin-left:-5px}.list-inline>li{display:inline-block;padding-left:5px;padding-right:5px}dl{margin-top:0;margin-bottom:20px}dt,dd{line-height:1.42857143}dt{font-weight:bold}dd{margin-left:0}@media (min-width:768px){.dl-horizontal dt{float:left;width:160px;clear:left;text-align:right;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.dl-horizontal dd{margin-left:180px}}abbr[title],abbr[data-original-title]{cursor:help;border-bottom:1px dotted #999}.initialism{font-size:90%;text-transform:uppercase}blockquote{padding:10px 20px;margin:0 0 20px;font-size:17.5px;border-left:5px solid #eee}blockquote p:last-child,blockquote ul:last-child,blockquote ol:last-child{margin-bottom:0}blockquote footer,blockquote small,blockquote .small{display:block;font-size:80%;line-height:1.42857143;color:#999}blockquote footer:before,blockquote small:before,blockquote .small:before{content:'\2014 \00A0'}.blockquote-reverse,blockquote.pull-right{padding-right:15px;padding-left:0;border-right:5px solid #eee;border-left:0;text-align:right}.blockquote-reverse footer:before,blockquote.pull-right footer:before,.blockquote-reverse small:before,blockquote.pull-right small:before,.blockquote-reverse .small:before,blockquote.pull-right .small:before{content:''}.blockquote-reverse footer:after,blockquote.pull-right footer:after,.blockquote-reverse small:after,blockquote.pull-right small:after,.blockquote-reverse .small:after,blockquote.pull-right .small:after{content:'\00A0 \2014'}blockquote:before,blockquote:after{content:""}address{margin-bottom:20px;font-style:normal;line-height:1.42857143}code,kbd,pre,samp{font-family:Menlo,Monaco,Consolas,"Courier New",monospace}code{padding:2px 4px;font-size:90%;color:#c7254e;background-color:#f9f2f4;white-space:nowrap;border-radius:4px}kbd{padding:2px 4px;font-size:90%;color:#fff;background-color:#333;border-radius:3px;box-shadow:inset 0 -1px 0 rgba(0,0,0,0.25)}pre{display:block;padding:9.5px;margin:0 0 10px;font-size:13px;line-height:1.42857143;word-break:break-all;word-wrap:break-word;color:#333;background-color:#f5f5f5;border:1px solid #ccc;border-radius:4px}pre code{padding:0;font-size:inherit;color:inherit;white-space:pre-wrap;background-color:transparent;border-radius:0}.pre-scrollable{max-height:340px;overflow-y:scroll}.container{margin-right:auto;margin-left:auto;padding-left:15px;padding-right:15px}@media (min-width:768px){.container{width:750px}}@media (min-width:992px){.container{width:970px}}@media (min-width:1200px){.container{width:1170px}}.container-fluid{margin-right:auto;margin-left:auto;padding-left:15px;padding-right:15px}.row{margin-left:-15px;margin-right:-15px}.col-xs-1, .col-sm-1, .col-md-1, .col-lg-1, .col-xs-2, .col-sm-2, .col-md-2, .col-lg-2, .col-xs-3, .col-sm-3, .col-md-3, .col-lg-3, .col-xs-4, .col-sm-4, .col-md-4, .col-lg-4, .col-xs-5, .col-sm-5, .col-md-5, .col-lg-5, .col-xs-6, .col-sm-6, .col-md-6, .col-lg-6, .col-xs-7, .col-sm-7, .col-md-7, .col-lg-7, .col-xs-8, .col-sm-8, .col-md-8, .col-lg-8, .col-xs-9, .col-sm-9, .col-md-9, .col-lg-9, .col-xs-10, .col-sm-10, .col-md-10, .col-lg-10, .col-xs-11, .col-sm-11, .col-md-11, .col-lg-11, .col-xs-12, .col-sm-12, .col-md-12, .col-lg-12{position:relative;min-height:1px;padding-left:15px;padding-right:15px}.col-xs-1, .col-xs-2, .col-xs-3, .col-xs-4, .col-xs-5, .col-xs-6, .col-xs-7, .col-xs-8, .col-xs-9, .col-xs-10, .col-xs-11, .col-xs-12{float:left}.col-xs-12{width:100%}.col-xs-11{width:91.66666667%}.col-xs-10{width:83.33333333%}.col-xs-9{width:75%}.col-xs-8{width:66.66666667%}.col-xs-7{width:58.33333333%}.col-xs-6{width:50%}.col-xs-5{width:41.66666667%}.col-xs-4{width:33.33333333%}.col-xs-3{width:25%}.col-xs-2{width:16.66666667%}.col-xs-1{width:8.33333333%}.col-xs-pull-12{right:100%}.col-xs-pull-11{right:91.66666667%}.col-xs-pull-10{right:83.33333333%}.col-xs-pull-9{right:75%}.col-xs-pull-8{right:66.66666667%}.col-xs-pull-7{right:58.33333333%}.col-xs-pull-6{right:50%}.col-xs-pull-5{right:41.66666667%}.col-xs-pull-4{right:33.33333333%}.col-xs-pull-3{right:25%}.col-xs-pull-2{right:16.66666667%}.col-xs-pull-1{right:8.33333333%}.col-xs-pull-0{right:0}.col-xs-push-12{left:100%}.col-xs-push-11{left:91.66666667%}.col-xs-push-10{left:83.33333333%}.col-xs-push-9{left:75%}.col-xs-push-8{left:66.66666667%}.col-xs-push-7{left:58.33333333%}.col-xs-push-6{left:50%}.col-xs-push-5{left:41.66666667%}.col-xs-push-4{left:33.33333333%}.col-xs-push-3{left:25%}.col-xs-push-2{left:16.66666667%}.col-xs-push-1{left:8.33333333%}.col-xs-push-0{left:0}.col-xs-offset-12{margin-left:100%}.col-xs-offset-11{margin-left:91.66666667%}.col-xs-offset-10{margin-left:83.33333333%}.col-xs-offset-9{margin-left:75%}.col-xs-offset-8{margin-left:66.66666667%}.col-xs-offset-7{margin-left:58.33333333%}.col-xs-offset-6{margin-left:50%}.col-xs-offset-5{margin-left:41.66666667%}.col-xs-offset-4{margin-left:33.33333333%}.col-xs-offset-3{margin-left:25%}.col-xs-offset-2{margin-left:16.66666667%}.col-xs-offset-1{margin-left:8.33333333%}.col-xs-offset-0{margin-left:0}@media (min-width:768px){.col-sm-1, .col-sm-2, .col-sm-3, .col-sm-4, .col-sm-5, .col-sm-6, .col-sm-7, .col-sm-8, .col-sm-9, .col-sm-10, .col-sm-11, .col-sm-12{float:left}.col-sm-12{width:100%}.col-sm-11{width:91.66666667%}.col-sm-10{width:83.33333333%}.col-sm-9{width:75%}.col-sm-8{width:66.66666667%}.col-sm-7{width:58.33333333%}.col-sm-6{width:50%}.col-sm-5{width:41.66666667%}.col-sm-4{width:33.33333333%}.col-sm-3{width:25%}.col-sm-2{width:16.66666667%}.col-sm-1{width:8.33333333%}.col-sm-pull-12{right:100%}.col-sm-pull-11{right:91.66666667%}.col-sm-pull-10{right:83.33333333%}.col-sm-pull-9{right:75%}.col-sm-pull-8{right:66.66666667%}.col-sm-pull-7{right:58.33333333%}.col-sm-pull-6{right:50%}.col-sm-pull-5{right:41.66666667%}.col-sm-pull-4{right:33.33333333%}.col-sm-pull-3{right:25%}.col-sm-pull-2{right:16.66666667%}.col-sm-pull-1{right:8.33333333%}.col-sm-pull-0{right:0}.col-sm-push-12{left:100%}.col-sm-push-11{left:91.66666667%}.col-sm-push-10{left:83.33333333%}.col-sm-push-9{left:75%}.col-sm-push-8{left:66.66666667%}.col-sm-push-7{left:58.33333333%}.col-sm-push-6{left:50%}.col-sm-push-5{left:41.66666667%}.col-sm-push-4{left:33.33333333%}.col-sm-push-3{left:25%}.col-sm-push-2{left:16.66666667%}.col-sm-push-1{left:8.33333333%}.col-sm-push-0{left:0}.col-sm-offset-12{margin-left:100%}.col-sm-offset-11{margin-left:91.66666667%}.col-sm-offset-10{margin-left:83.33333333%}.col-sm-offset-9{margin-left:75%}.col-sm-offset-8{margin-left:66.66666667%}.col-sm-offset-7{margin-left:58.33333333%}.col-sm-offset-6{margin-left:50%}.col-sm-offset-5{margin-left:41.66666667%}.col-sm-offset-4{margin-left:33.33333333%}.col-sm-offset-3{margin-left:25%}.col-sm-offset-2{margin-left:16.66666667%}.col-sm-offset-1{margin-left:8.33333333%}.col-sm-offset-0{margin-left:0}}@media (min-width:992px){.col-md-1, .col-md-2, .col-md-3, .col-md-4, .col-md-5, .col-md-6, .col-md-7, .col-md-8, .col-md-9, .col-md-10, .col-md-11, .col-md-12{float:left}.col-md-12{width:100%}.col-md-11{width:91.66666667%}.col-md-10{width:83.33333333%}.col-md-9{width:75%}.col-md-8{width:66.66666667%}.col-md-7{width:58.33333333%}.col-md-6{width:50%}.col-md-5{width:41.66666667%}.col-md-4{width:33.33333333%}.col-md-3{width:25%}.col-md-2{width:16.66666667%}.col-md-1{width:8.33333333%}.col-md-pull-12{right:100%}.col-md-pull-11{right:91.66666667%}.col-md-pull-10{right:83.33333333%}.col-md-pull-9{right:75%}.col-md-pull-8{right:66.66666667%}.col-md-pull-7{right:58.33333333%}.col-md-pull-6{right:50%}.col-md-pull-5{right:41.66666667%}.col-md-pull-4{right:33.33333333%}.col-md-pull-3{right:25%}.col-md-pull-2{right:16.66666667%}.col-md-pull-1{right:8.33333333%}.col-md-pull-0{right:0}.col-md-push-12{left:100%}.col-md-push-11{left:91.66666667%}.col-md-push-10{left:83.33333333%}.col-md-push-9{left:75%}.col-md-push-8{left:66.66666667%}.col-md-push-7{left:58.33333333%}.col-md-push-6{left:50%}.col-md-push-5{left:41.66666667%}.col-md-push-4{left:33.33333333%}.col-md-push-3{left:25%}.col-md-push-2{left:16.66666667%}.col-md-push-1{left:8.33333333%}.col-md-push-0{left:0}.col-md-offset-12{margin-left:100%}.col-md-offset-11{margin-left:91.66666667%}.col-md-offset-10{margin-left:83.33333333%}.col-md-offset-9{margin-left:75%}.col-md-offset-8{margin-left:66.66666667%}.col-md-offset-7{margin-left:58.33333333%}.col-md-offset-6{margin-left:50%}.col-md-offset-5{margin-left:41.66666667%}.col-md-offset-4{margin-left:33.33333333%}.col-md-offset-3{margin-left:25%}.col-md-offset-2{margin-left:16.66666667%}.col-md-offset-1{margin-left:8.33333333%}.col-md-offset-0{margin-left:0}}@media (min-width:1200px){.col-lg-1, .col-lg-2, .col-lg-3, .col-lg-4, .col-lg-5, .col-lg-6, .col-lg-7, .col-lg-8, .col-lg-9, .col-lg-10, .col-lg-11, .col-lg-12{float:left}.col-lg-12{width:100%}.col-lg-11{width:91.66666667%}.col-lg-10{width:83.33333333%}.col-lg-9{width:75%}.col-lg-8{width:66.66666667%}.col-lg-7{width:58.33333333%}.col-lg-6{width:50%}.col-lg-5{width:41.66666667%}.col-lg-4{width:33.33333333%}.col-lg-3{width:25%}.col-lg-2{width:16.66666667%}.col-lg-1{width:8.33333333%}.col-lg-pull-12{right:100%}.col-lg-pull-11{right:91.66666667%}.col-lg-pull-10{right:83.33333333%}.col-lg-pull-9{right:75%}.col-lg-pull-8{right:66.66666667%}.col-lg-pull-7{right:58.33333333%}.col-lg-pull-6{right:50%}.col-lg-pull-5{right:41.66666667%}.col-lg-pull-4{right:33.33333333%}.col-lg-pull-3{right:25%}.col-lg-pull-2{right:16.66666667%}.col-lg-pull-1{right:8.33333333%}.col-lg-pull-0{right:0}.col-lg-push-12{left:100%}.col-lg-push-11{left:91.66666667%}.col-lg-push-10{left:83.33333333%}.col-lg-push-9{left:75%}.col-lg-push-8{left:66.66666667%}.col-lg-push-7{left:58.33333333%}.col-lg-push-6{left:50%}.col-lg-push-5{left:41.66666667%}.col-lg-push-4{left:33.33333333%}.col-lg-push-3{left:25%}.col-lg-push-2{left:16.66666667%}.col-lg-push-1{left:8.33333333%}.col-lg-push-0{left:0}.col-lg-offset-12{margin-left:100%}.col-lg-offset-11{margin-left:91.66666667%}.col-lg-offset-10{margin-left:83.33333333%}.col-lg-offset-9{margin-left:75%}.col-lg-offset-8{margin-left:66.66666667%}.col-lg-offset-7{margin-left:58.33333333%}.col-lg-offset-6{margin-left:50%}.col-lg-offset-5{margin-left:41.66666667%}.col-lg-offset-4{margin-left:33.33333333%}.col-lg-offset-3{margin-left:25%}.col-lg-offset-2{margin-left:16.66666667%}.col-lg-offset-1{margin-left:8.33333333%}.col-lg-offset-0{margin-left:0}}table{max-width:100%;background-color:transparent}th{text-align:left}.table{width:100%;margin-bottom:20px}.table>thead>tr>th,.table>tbody>tr>th,.table>tfoot>tr>th,.table>thead>tr>td,.table>tbody>tr>td,.table>tfoot>tr>td{padding:8px;line-height:1.42857143;vertical-align:top;border-top:1px solid #ddd}.table>thead>tr>th{vertical-align:bottom;border-bottom:2px solid #ddd}.table>caption+thead>tr:first-child>th,.table>colgroup+thead>tr:first-child>th,.table>thead:first-child>tr:first-child>th,.table>caption+thead>tr:first-child>td,.table>colgroup+thead>tr:first-child>td,.table>thead:first-child>tr:first-child>td{border-top:0}.table>tbody+tbody{border-top:2px solid #ddd}.table .table{background-color:#fff}.table-condensed>thead>tr>th,.table-condensed>tbody>tr>th,.table-condensed>tfoot>tr>th,.table-condensed>thead>tr>td,.table-condensed>tbody>tr>td,.table-condensed>tfoot>tr>td{padding:5px}.table-bordered{border:1px solid #ddd}.table-bordered>thead>tr>th,.table-bordered>tbody>tr>th,.table-bordered>tfoot>tr>th,.table-bordered>thead>tr>td,.table-bordered>tbody>tr>td,.table-bordered>tfoot>tr>td{border:1px solid #ddd}.table-bordered>thead>tr>th,.table-bordered>thead>tr>td{border-bottom-width:2px}.table-striped>tbody>tr:nth-child(odd)>td,.table-striped>tbody>tr:nth-child(odd)>th{background-color:#f9f9f9}.table-hover>tbody>tr:hover>td,.table-hover>tbody>tr:hover>th{background-color:#f5f5f5}table col[class*="col-"]{position:static;float:none;display:table-column}table td[class*="col-"],table th[class*="col-"]{position:static;float:none;display:table-cell}.table>thead>tr>td.active,.table>tbody>tr>td.active,.table>tfoot>tr>td.active,.table>thead>tr>th.active,.table>tbody>tr>th.active,.table>tfoot>tr>th.active,.table>thead>tr.active>td,.table>tbody>tr.active>td,.table>tfoot>tr.active>td,.table>thead>tr.active>th,.table>tbody>tr.active>th,.table>tfoot>tr.active>th{background-color:#f5f5f5}.table-hover>tbody>tr>td.active:hover,.table-hover>tbody>tr>th.active:hover,.table-hover>tbody>tr.active:hover>td,.table-hover>tbody>tr.active:hover>th{background-color:#e8e8e8}.table>thead>tr>td.success,.table>tbody>tr>td.success,.table>tfoot>tr>td.success,.table>thead>tr>th.success,.table>tbody>tr>th.success,.table>tfoot>tr>th.success,.table>thead>tr.success>td,.table>tbody>tr.success>td,.table>tfoot>tr.success>td,.table>thead>tr.success>th,.table>tbody>tr.success>th,.table>tfoot>tr.success>th{background-color:#dff0d8}.table-hover>tbody>tr>td.success:hover,.table-hover>tbody>tr>th.success:hover,.table-hover>tbody>tr.success:hover>td,.table-hover>tbody>tr.success:hover>th{background-color:#d0e9c6}.table>thead>tr>td.info,.table>tbody>tr>td.info,.table>tfoot>tr>td.info,.table>thead>tr>th.info,.table>tbody>tr>th.info,.table>tfoot>tr>th.info,.table>thead>tr.info>td,.table>tbody>tr.info>td,.table>tfoot>tr.info>td,.table>thead>tr.info>th,.table>tbody>tr.info>th,.table>tfoot>tr.info>th{background-color:#d9edf7}.table-hover>tbody>tr>td.info:hover,.table-hover>tbody>tr>th.info:hover,.table-hover>tbody>tr.info:hover>td,.table-hover>tbody>tr.info:hover>th{background-color:#c4e3f3}.table>thead>tr>td.warning,.table>tbody>tr>td.warning,.table>tfoot>tr>td.warning,.table>thead>tr>th.warning,.table>tbody>tr>th.warning,.table>tfoot>tr>th.warning,.table>thead>tr.warning>td,.table>tbody>tr.warning>td,.table>tfoot>tr.warning>td,.table>thead>tr.warning>th,.table>tbody>tr.warning>th,.table>tfoot>tr.warning>th{background-color:#fcf8e3}.table-hover>tbody>tr>td.warning:hover,.table-hover>tbody>tr>th.warning:hover,.table-hover>tbody>tr.warning:hover>td,.table-hover>tbody>tr.warning:hover>th{background-color:#faf2cc}.table>thead>tr>td.danger,.table>tbody>tr>td.danger,.table>tfoot>tr>td.danger,.table>thead>tr>th.danger,.table>tbody>tr>th.danger,.table>tfoot>tr>th.danger,.table>thead>tr.danger>td,.table>tbody>tr.danger>td,.table>tfoot>tr.danger>td,.table>thead>tr.danger>th,.table>tbody>tr.danger>th,.table>tfoot>tr.danger>th{background-color:#f2dede}.table-hover>tbody>tr>td.danger:hover,.table-hover>tbody>tr>th.danger:hover,.table-hover>tbody>tr.danger:hover>td,.table-hover>tbody>tr.danger:hover>th{background-color:#ebcccc}@media (max-width:767px){.table-responsive{width:100%;margin-bottom:15px;overflow-y:hidden;overflow-x:scroll;-ms-overflow-style:-ms-autohiding-scrollbar;border:1px solid #ddd;-webkit-overflow-scrolling:touch}.table-responsive>.table{margin-bottom:0}.table-responsive>.table>thead>tr>th,.table-responsive>.table>tbody>tr>th,.table-responsive>.table>tfoot>tr>th,.table-responsive>.table>thead>tr>td,.table-responsive>.table>tbody>tr>td,.table-responsive>.table>tfoot>tr>td{white-space:nowrap}.table-responsive>.table-bordered{border:0}.table-responsive>.table-bordered>thead>tr>th:first-child,.table-responsive>.table-bordered>tbody>tr>th:first-child,.table-responsive>.table-bordered>tfoot>tr>th:first-child,.table-responsive>.table-bordered>thead>tr>td:first-child,.table-responsive>.table-bordered>tbody>tr>td:first-child,.table-responsive>.table-bordered>tfoot>tr>td:first-child{border-left:0}.table-responsive>.table-bordered>thead>tr>th:last-child,.table-responsive>.table-bordered>tbody>tr>th:last-child,.table-responsive>.table-bordered>tfoot>tr>th:last-child,.table-responsive>.table-bordered>thead>tr>td:last-child,.table-responsive>.table-bordered>tbody>tr>td:last-child,.table-responsive>.table-bordered>tfoot>tr>td:last-child{border-right:0}.table-responsive>.table-bordered>tbody>tr:last-child>th,.table-responsive>.table-bordered>tfoot>tr:last-child>th,.table-responsive>.table-bordered>tbody>tr:last-child>td,.table-responsive>.table-bordered>tfoot>tr:last-child>td{border-bottom:0}}fieldset{padding:0;margin:0;border:0;min-width:0}legend{display:block;width:100%;padding:0;margin-bottom:20px;font-size:21px;line-height:inherit;color:#333;border:0;border-bottom:1px solid #e5e5e5}label{display:inline-block;margin-bottom:5px;font-weight:bold}input[type="search"]{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}input[type="radio"],input[type="checkbox"]{margin:4px 0 0;margin-top:1px \9;line-height:normal}input[type="file"]{display:block}input[type="range"]{display:block;width:100%}select[multiple],select[size]{height:auto}input[type="file"]:focus,input[type="radio"]:focus,input[type="checkbox"]:focus{outline:thin dotted;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}output{display:block;padding-top:7px;font-size:14px;line-height:1.42857143;color:#555}.form-control{display:block;width:100%;height:34px;padding:6px 12px;font-size:14px;line-height:1.42857143;color:#555;background-color:#fff;background-image:none;border:1px solid #ccc;border-radius:4px;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);-webkit-transition:border-color ease-in-out .15s, box-shadow ease-in-out .15s;transition:border-color ease-in-out .15s, box-shadow ease-in-out .15s}.form-control:focus{border-color:#66afe9;outline:0;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px rgba(102, 175, 233, 0.6);box-shadow:inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px rgba(102, 175, 233, 0.6)}.form-control::-moz-placeholder{color:#999;opacity:1}.form-control:-ms-input-placeholder{color:#999}.form-control::-webkit-input-placeholder{color:#999}.form-control[disabled],.form-control[readonly],fieldset[disabled] .form-control{cursor:not-allowed;background-color:#eee;opacity:1}textarea.form-control{height:auto}input[type="search"]{-webkit-appearance:none}input[type="date"]{line-height:34px}.form-group{margin-bottom:15px}.radio,.checkbox{display:block;min-height:20px;margin-top:10px;margin-bottom:10px;padding-left:20px}.radio label,.checkbox label{display:inline;font-weight:normal;cursor:pointer}.radio input[type="radio"],.radio-inline input[type="radio"],.checkbox input[type="checkbox"],.checkbox-inline input[type="checkbox"]{float:left;margin-left:-20px}.radio+.radio,.checkbox+.checkbox{margin-top:-5px}.radio-inline,.checkbox-inline{display:inline-block;padding-left:20px;margin-bottom:0;vertical-align:middle;font-weight:normal;cursor:pointer}.radio-inline+.radio-inline,.checkbox-inline+.checkbox-inline{margin-top:0;margin-left:10px}input[type="radio"][disabled],input[type="checkbox"][disabled],.radio[disabled],.radio-inline[disabled],.checkbox[disabled],.checkbox-inline[disabled],fieldset[disabled] input[type="radio"],fieldset[disabled] input[type="checkbox"],fieldset[disabled] .radio,fieldset[disabled] .radio-inline,fieldset[disabled] .checkbox,fieldset[disabled] .checkbox-inline{cursor:not-allowed}.input-sm{height:30px;padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}select.input-sm{height:30px;line-height:30px}textarea.input-sm,select[multiple].input-sm{height:auto}.input-lg{height:46px;padding:10px 16px;font-size:18px;line-height:1.33;border-radius:6px}select.input-lg{height:46px;line-height:46px}textarea.input-lg,select[multiple].input-lg{height:auto}.has-feedback{position:relative}.has-feedback .form-control{padding-right:42.5px}.has-feedback .form-control-feedback{position:absolute;top:25px;right:0;display:block;width:34px;height:34px;line-height:34px;text-align:center}.has-success .help-block,.has-success .control-label,.has-success .radio,.has-success .checkbox,.has-success .radio-inline,.has-success .checkbox-inline{color:#3c763d}.has-success .form-control{border-color:#3c763d;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075)}.has-success .form-control:focus{border-color:#2b542c;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #67b168;box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #67b168}.has-success .input-group-addon{color:#3c763d;border-color:#3c763d;background-color:#dff0d8}.has-success .form-control-feedback{color:#3c763d}.has-warning .help-block,.has-warning .control-label,.has-warning .radio,.has-warning .checkbox,.has-warning .radio-inline,.has-warning .checkbox-inline{color:#8a6d3b}.has-warning .form-control{border-color:#8a6d3b;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075)}.has-warning .form-control:focus{border-color:#66512c;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #c0a16b;box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #c0a16b}.has-warning .input-group-addon{color:#8a6d3b;border-color:#8a6d3b;background-color:#fcf8e3}.has-warning .form-control-feedback{color:#8a6d3b}.has-error .help-block,.has-error .control-label,.has-error .radio,.has-error .checkbox,.has-error .radio-inline,.has-error .checkbox-inline{color:#a94442}.has-error .form-control{border-color:#a94442;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075)}.has-error .form-control:focus{border-color:#843534;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #ce8483;box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #ce8483}.has-error .input-group-addon{color:#a94442;border-color:#a94442;background-color:#f2dede}.has-error .form-control-feedback{color:#a94442}.form-control-static{margin-bottom:0}.help-block{display:block;margin-top:5px;margin-bottom:10px;color:#737373}@media (min-width:768px){.form-inline .form-group{display:inline-block;margin-bottom:0;vertical-align:middle}.form-inline .form-control{display:inline-block;width:auto;vertical-align:middle}.form-inline .input-group>.form-control{width:100%}.form-inline .control-label{margin-bottom:0;vertical-align:middle}.form-inline .radio,.form-inline .checkbox{display:inline-block;margin-top:0;margin-bottom:0;padding-left:0;vertical-align:middle}.form-inline .radio input[type="radio"],.form-inline .checkbox input[type="checkbox"]{float:none;margin-left:0}.form-inline .has-feedback .form-control-feedback{top:0}}.form-horizontal .control-label,.form-horizontal .radio,.form-horizontal .checkbox,.form-horizontal .radio-inline,.form-horizontal .checkbox-inline{margin-top:0;margin-bottom:0;padding-top:7px}.form-horizontal .radio,.form-horizontal .checkbox{min-height:27px}.form-horizontal .form-group{margin-left:-15px;margin-right:-15px}.form-horizontal .form-control-static{padding-top:7px}@media (min-width:768px){.form-horizontal .control-label{text-align:right}}.form-horizontal .has-feedback .form-control-feedback{top:0;right:15px}.btn{display:inline-block;margin-bottom:0;font-weight:normal;text-align:center;vertical-align:middle;cursor:pointer;background-image:none;border:1px solid transparent;white-space:nowrap;padding:6px 12px;font-size:14px;line-height:1.42857143;border-radius:4px;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.btn:focus,.btn:active:focus,.btn.active:focus{outline:thin dotted;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}.btn:hover,.btn:focus{color:#333;text-decoration:none}.btn:active,.btn.active{outline:0;background-image:none;-webkit-box-shadow:inset 0 3px 5px rgba(0,0,0,0.125);box-shadow:inset 0 3px 5px rgba(0,0,0,0.125)}.btn.disabled,.btn[disabled],fieldset[disabled] .btn{cursor:not-allowed;pointer-events:none;opacity:.65;filter:alpha(opacity=65);-webkit-box-shadow:none;box-shadow:none}.btn-default{color:#333;background-color:#fff;border-color:#ccc}.btn-default:hover,.btn-default:focus,.btn-default:active,.btn-default.active,.open .dropdown-toggle.btn-default{color:#333;background-color:#ebebeb;border-color:#adadad}.btn-default:active,.btn-default.active,.open .dropdown-toggle.btn-default{background-image:none}.btn-default.disabled,.btn-default[disabled],fieldset[disabled] .btn-default,.btn-default.disabled:hover,.btn-default[disabled]:hover,fieldset[disabled] .btn-default:hover,.btn-default.disabled:focus,.btn-default[disabled]:focus,fieldset[disabled] .btn-default:focus,.btn-default.disabled:active,.btn-default[disabled]:active,fieldset[disabled] .btn-default:active,.btn-default.disabled.active,.btn-default[disabled].active,fieldset[disabled] .btn-default.active{background-color:#fff;border-color:#ccc}.btn-default .badge{color:#fff;background-color:#333}.btn-primary{color:#fff;background-color:#5289b3;border-color:#487ca4}.btn-primary:hover,.btn-primary:focus,.btn-primary:active,.btn-primary.active,.open .dropdown-toggle.btn-primary{color:#fff;background-color:#437499;border-color:#355c79}.btn-primary:active,.btn-primary.active,.open .dropdown-toggle.btn-primary{background-image:none}.btn-primary.disabled,.btn-primary[disabled],fieldset[disabled] .btn-primary,.btn-primary.disabled:hover,.btn-primary[disabled]:hover,fieldset[disabled] .btn-primary:hover,.btn-primary.disabled:focus,.btn-primary[disabled]:focus,fieldset[disabled] .btn-primary:focus,.btn-primary.disabled:active,.btn-primary[disabled]:active,fieldset[disabled] .btn-primary:active,.btn-primary.disabled.active,.btn-primary[disabled].active,fieldset[disabled] .btn-primary.active{background-color:#5289b3;border-color:#487ca4}.btn-primary .badge{color:#5289b3;background-color:#fff}.btn-success{color:#fff;background-color:#70a829;border-color:#629424}.btn-success:hover,.btn-success:focus,.btn-success:active,.btn-success.active,.open .dropdown-toggle.btn-success{color:#fff;background-color:#5a8721;border-color:#426218}.btn-success:active,.btn-success.active,.open .dropdown-toggle.btn-success{background-image:none}.btn-success.disabled,.btn-success[disabled],fieldset[disabled] .btn-success,.btn-success.disabled:hover,.btn-success[disabled]:hover,fieldset[disabled] .btn-success:hover,.btn-success.disabled:focus,.btn-success[disabled]:focus,fieldset[disabled] .btn-success:focus,.btn-success.disabled:active,.btn-success[disabled]:active,fieldset[disabled] .btn-success:active,.btn-success.disabled.active,.btn-success[disabled].active,fieldset[disabled] .btn-success.active{background-color:#70a829;border-color:#629424}.btn-success .badge{color:#70a829;background-color:#fff}.btn-info{color:#fff;background-color:#5bc0de;border-color:#46b8da}.btn-info:hover,.btn-info:focus,.btn-info:active,.btn-info.active,.open .dropdown-toggle.btn-info{color:#fff;background-color:#39b3d7;border-color:#269abc}.btn-info:active,.btn-info.active,.open .dropdown-toggle.btn-info{background-image:none}.btn-info.disabled,.btn-info[disabled],fieldset[disabled] .btn-info,.btn-info.disabled:hover,.btn-info[disabled]:hover,fieldset[disabled] .btn-info:hover,.btn-info.disabled:focus,.btn-info[disabled]:focus,fieldset[disabled] .btn-info:focus,.btn-info.disabled:active,.btn-info[disabled]:active,fieldset[disabled] .btn-info:active,.btn-info.disabled.active,.btn-info[disabled].active,fieldset[disabled] .btn-info.active{background-color:#5bc0de;border-color:#46b8da}.btn-info .badge{color:#5bc0de;background-color:#fff}.btn-warning{color:#fff;background-color:#f9c861;border-color:#f8bf48}.btn-warning:hover,.btn-warning:focus,.btn-warning:active,.btn-warning.active,.open .dropdown-toggle.btn-warning{color:#fff;background-color:#f8ba3a;border-color:#f6ab0d}.btn-warning:active,.btn-warning.active,.open .dropdown-toggle.btn-warning{background-image:none}.btn-warning.disabled,.btn-warning[disabled],fieldset[disabled] .btn-warning,.btn-warning.disabled:hover,.btn-warning[disabled]:hover,fieldset[disabled] .btn-warning:hover,.btn-warning.disabled:focus,.btn-warning[disabled]:focus,fieldset[disabled] .btn-warning:focus,.btn-warning.disabled:active,.btn-warning[disabled]:active,fieldset[disabled] .btn-warning:active,.btn-warning.disabled.active,.btn-warning[disabled].active,fieldset[disabled] .btn-warning.active{background-color:#f9c861;border-color:#f8bf48}.btn-warning .badge{color:#f9c861;background-color:#fff}.btn-danger{color:#fff;background-color:#d9534f;border-color:#d43f3a}.btn-danger:hover,.btn-danger:focus,.btn-danger:active,.btn-danger.active,.open .dropdown-toggle.btn-danger{color:#fff;background-color:#d2322d;border-color:#ac2925}.btn-danger:active,.btn-danger.active,.open .dropdown-toggle.btn-danger{background-image:none}.btn-danger.disabled,.btn-danger[disabled],fieldset[disabled] .btn-danger,.btn-danger.disabled:hover,.btn-danger[disabled]:hover,fieldset[disabled] .btn-danger:hover,.btn-danger.disabled:focus,.btn-danger[disabled]:focus,fieldset[disabled] .btn-danger:focus,.btn-danger.disabled:active,.btn-danger[disabled]:active,fieldset[disabled] .btn-danger:active,.btn-danger.disabled.active,.btn-danger[disabled].active,fieldset[disabled] .btn-danger.active{background-color:#d9534f;border-color:#d43f3a}.btn-danger .badge{color:#d9534f;background-color:#fff}.btn-link{color:#5289b3;font-weight:normal;cursor:pointer;border-radius:0}.btn-link,.btn-link:active,.btn-link[disabled],fieldset[disabled] .btn-link{background-color:transparent;-webkit-box-shadow:none;box-shadow:none}.btn-link,.btn-link:hover,.btn-link:focus,.btn-link:active{border-color:transparent}.btn-link:hover,.btn-link:focus{color:#386180;text-decoration:underline;background-color:transparent}.btn-link[disabled]:hover,fieldset[disabled] .btn-link:hover,.btn-link[disabled]:focus,fieldset[disabled] .btn-link:focus{color:#999;text-decoration:none}.btn-lg,.btn-group-lg>.btn{padding:10px 16px;font-size:18px;line-height:1.33;border-radius:6px}.btn-sm,.btn-group-sm>.btn{padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}.btn-xs,.btn-group-xs>.btn{padding:1px 5px;font-size:12px;line-height:1.5;border-radius:3px}.btn-block{display:block;width:100%;padding-left:0;padding-right:0}.btn-block+.btn-block{margin-top:5px}input[type="submit"].btn-block,input[type="reset"].btn-block,input[type="button"].btn-block{width:100%}.fade{opacity:0;-webkit-transition:opacity .15s linear;transition:opacity .15s linear}.fade.in{opacity:1}.collapse{display:none}.collapse.in{display:block}.collapsing{position:relative;height:0;overflow:hidden;-webkit-transition:height .35s ease;transition:height .35s ease}@font-face{font-family:'Glyphicons Halflings';src:url('../fonts/glyphicons-halflings-regular.eot');src:url('../fonts/glyphicons-halflings-regular.eot?#iefix') format('embedded-opentype'),url('../fonts/glyphicons-halflings-regular.woff') format('woff'),url('../fonts/glyphicons-halflings-regular.ttf') format('truetype'),url('../fonts/glyphicons-halflings-regular.svg#glyphicons_halflingsregular') format('svg')}.glyphicon{position:relative;top:1px;display:inline-block;font-family:'Glyphicons Halflings';font-style:normal;font-weight:normal;line-height:1;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.glyphicon-asterisk:before{content:"\2a"}.glyphicon-plus:before{content:"\2b"}.glyphicon-euro:before{content:"\20ac"}.glyphicon-minus:before{content:"\2212"}.glyphicon-cloud:before{content:"\2601"}.glyphicon-envelope:before{content:"\2709"}.glyphicon-pencil:before{content:"\270f"}.glyphicon-glass:before{content:"\e001"}.glyphicon-music:before{content:"\e002"}.glyphicon-search:before{content:"\e003"}.glyphicon-heart:before{content:"\e005"}.glyphicon-star:before{content:"\e006"}.glyphicon-star-empty:before{content:"\e007"}.glyphicon-user:before{content:"\e008"}.glyphicon-film:before{content:"\e009"}.glyphicon-th-large:before{content:"\e010"}.glyphicon-th:before{content:"\e011"}.glyphicon-th-list:before{content:"\e012"}.glyphicon-ok:before{content:"\e013"}.glyphicon-remove:before{content:"\e014"}.glyphicon-zoom-in:before{content:"\e015"}.glyphicon-zoom-out:before{content:"\e016"}.glyphicon-off:before{content:"\e017"}.glyphicon-signal:before{content:"\e018"}.glyphicon-cog:before{content:"\e019"}.glyphicon-trash:before{content:"\e020"}.glyphicon-home:before{content:"\e021"}.glyphicon-file:before{content:"\e022"}.glyphicon-time:before{content:"\e023"}.glyphicon-road:before{content:"\e024"}.glyphicon-download-alt:before{content:"\e025"}.glyphicon-download:before{content:"\e026"}.glyphicon-upload:before{content:"\e027"}.glyphicon-inbox:before{content:"\e028"}.glyphicon-play-circle:before{content:"\e029"}.glyphicon-repeat:before{content:"\e030"}.glyphicon-refresh:before{content:"\e031"}.glyphicon-list-alt:before{content:"\e032"}.glyphicon-lock:before{content:"\e033"}.glyphicon-flag:before{content:"\e034"}.glyphicon-headphones:before{content:"\e035"}.glyphicon-volume-off:before{content:"\e036"}.glyphicon-volume-down:before{content:"\e037"}.glyphicon-volume-up:before{content:"\e038"}.glyphicon-qrcode:before{content:"\e039"}.glyphicon-barcode:before{content:"\e040"}.glyphicon-tag:before{content:"\e041"}.glyphicon-tags:before{content:"\e042"}.glyphicon-book:before{content:"\e043"}.glyphicon-bookmark:before{content:"\e044"}.glyphicon-print:before{content:"\e045"}.glyphicon-camera:before{content:"\e046"}.glyphicon-font:before{content:"\e047"}.glyphicon-bold:before{content:"\e048"}.glyphicon-italic:before{content:"\e049"}.glyphicon-text-height:before{content:"\e050"}.glyphicon-text-width:before{content:"\e051"}.glyphicon-align-left:before{content:"\e052"}.glyphicon-align-center:before{content:"\e053"}.glyphicon-align-right:before{content:"\e054"}.glyphicon-align-justify:before{content:"\e055"}.glyphicon-list:before{content:"\e056"}.glyphicon-indent-left:before{content:"\e057"}.glyphicon-indent-right:before{content:"\e058"}.glyphicon-facetime-video:before{content:"\e059"}.glyphicon-picture:before{content:"\e060"}.glyphicon-map-marker:before{content:"\e062"}.glyphicon-adjust:before{content:"\e063"}.glyphicon-tint:before{content:"\e064"}.glyphicon-edit:before{content:"\e065"}.glyphicon-share:before{content:"\e066"}.glyphicon-check:before{content:"\e067"}.glyphicon-move:before{content:"\e068"}.glyphicon-step-backward:before{content:"\e069"}.glyphicon-fast-backward:before{content:"\e070"}.glyphicon-backward:before{content:"\e071"}.glyphicon-play:before{content:"\e072"}.glyphicon-pause:before{content:"\e073"}.glyphicon-stop:before{content:"\e074"}.glyphicon-forward:before{content:"\e075"}.glyphicon-fast-forward:before{content:"\e076"}.glyphicon-step-forward:before{content:"\e077"}.glyphicon-eject:before{content:"\e078"}.glyphicon-chevron-left:before{content:"\e079"}.glyphicon-chevron-right:before{content:"\e080"}.glyphicon-plus-sign:before{content:"\e081"}.glyphicon-minus-sign:before{content:"\e082"}.glyphicon-remove-sign:before{content:"\e083"}.glyphicon-ok-sign:before{content:"\e084"}.glyphicon-question-sign:before{content:"\e085"}.glyphicon-info-sign:before{content:"\e086"}.glyphicon-screenshot:before{content:"\e087"}.glyphicon-remove-circle:before{content:"\e088"}.glyphicon-ok-circle:before{content:"\e089"}.glyphicon-ban-circle:before{content:"\e090"}.glyphicon-arrow-left:before{content:"\e091"}.glyphicon-arrow-right:before{content:"\e092"}.glyphicon-arrow-up:before{content:"\e093"}.glyphicon-arrow-down:before{content:"\e094"}.glyphicon-share-alt:before{content:"\e095"}.glyphicon-resize-full:before{content:"\e096"}.glyphicon-resize-small:before{content:"\e097"}.glyphicon-exclamation-sign:before{content:"\e101"}.glyphicon-gift:before{content:"\e102"}.glyphicon-leaf:before{content:"\e103"}.glyphicon-fire:before{content:"\e104"}.glyphicon-eye-open:before{content:"\e105"}.glyphicon-eye-close:before{content:"\e106"}.glyphicon-warning-sign:before{content:"\e107"}.glyphicon-plane:before{content:"\e108"}.glyphicon-calendar:before{content:"\e109"}.glyphicon-random:before{content:"\e110"}.glyphicon-comment:before{content:"\e111"}.glyphicon-magnet:before{content:"\e112"}.glyphicon-chevron-up:before{content:"\e113"}.glyphicon-chevron-down:before{content:"\e114"}.glyphicon-retweet:before{content:"\e115"}.glyphicon-shopping-cart:before{content:"\e116"}.glyphicon-folder-close:before{content:"\e117"}.glyphicon-folder-open:before{content:"\e118"}.glyphicon-resize-vertical:before{content:"\e119"}.glyphicon-resize-horizontal:before{content:"\e120"}.glyphicon-hdd:before{content:"\e121"}.glyphicon-bullhorn:before{content:"\e122"}.glyphicon-bell:before{content:"\e123"}.glyphicon-certificate:before{content:"\e124"}.glyphicon-thumbs-up:before{content:"\e125"}.glyphicon-thumbs-down:before{content:"\e126"}.glyphicon-hand-right:before{content:"\e127"}.glyphicon-hand-left:before{content:"\e128"}.glyphicon-hand-up:before{content:"\e129"}.glyphicon-hand-down:before{content:"\e130"}.glyphicon-circle-arrow-right:before{content:"\e131"}.glyphicon-circle-arrow-left:before{content:"\e132"}.glyphicon-circle-arrow-up:before{content:"\e133"}.glyphicon-circle-arrow-down:before{content:"\e134"}.glyphicon-globe:before{content:"\e135"}.glyphicon-wrench:before{content:"\e136"}.glyphicon-tasks:before{content:"\e137"}.glyphicon-filter:before{content:"\e138"}.glyphicon-briefcase:before{content:"\e139"}.glyphicon-fullscreen:before{content:"\e140"}.glyphicon-dashboard:before{content:"\e141"}.glyphicon-paperclip:before{content:"\e142"}.glyphicon-heart-empty:before{content:"\e143"}.glyphicon-link:before{content:"\e144"}.glyphicon-phone:before{content:"\e145"}.glyphicon-pushpin:before{content:"\e146"}.glyphicon-usd:before{content:"\e148"}.glyphicon-gbp:before{content:"\e149"}.glyphicon-sort:before{content:"\e150"}.glyphicon-sort-by-alphabet:before{content:"\e151"}.glyphicon-sort-by-alphabet-alt:before{content:"\e152"}.glyphicon-sort-by-order:before{content:"\e153"}.glyphicon-sort-by-order-alt:before{content:"\e154"}.glyphicon-sort-by-attributes:before{content:"\e155"}.glyphicon-sort-by-attributes-alt:before{content:"\e156"}.glyphicon-unchecked:before{content:"\e157"}.glyphicon-expand:before{content:"\e158"}.glyphicon-collapse-down:before{content:"\e159"}.glyphicon-collapse-up:before{content:"\e160"}.glyphicon-log-in:before{content:"\e161"}.glyphicon-flash:before{content:"\e162"}.glyphicon-log-out:before{content:"\e163"}.glyphicon-new-window:before{content:"\e164"}.glyphicon-record:before{content:"\e165"}.glyphicon-save:before{content:"\e166"}.glyphicon-open:before{content:"\e167"}.glyphicon-saved:before{content:"\e168"}.glyphicon-import:before{content:"\e169"}.glyphicon-export:before{content:"\e170"}.glyphicon-send:before{content:"\e171"}.glyphicon-floppy-disk:before{content:"\e172"}.glyphicon-floppy-saved:before{content:"\e173"}.glyphicon-floppy-remove:before{content:"\e174"}.glyphicon-floppy-save:before{content:"\e175"}.glyphicon-floppy-open:before{content:"\e176"}.glyphicon-credit-card:before{content:"\e177"}.glyphicon-transfer:before{content:"\e178"}.glyphicon-cutlery:before{content:"\e179"}.glyphicon-header:before{content:"\e180"}.glyphicon-compressed:before{content:"\e181"}.glyphicon-earphone:before{content:"\e182"}.glyphicon-phone-alt:before{content:"\e183"}.glyphicon-tower:before{content:"\e184"}.glyphicon-stats:before{content:"\e185"}.glyphicon-sd-video:before{content:"\e186"}.glyphicon-hd-video:before{content:"\e187"}.glyphicon-subtitles:before{content:"\e188"}.glyphicon-sound-stereo:before{content:"\e189"}.glyphicon-sound-dolby:before{content:"\e190"}.glyphicon-sound-5-1:before{content:"\e191"}.glyphicon-sound-6-1:before{content:"\e192"}.glyphicon-sound-7-1:before{content:"\e193"}.glyphicon-copyright-mark:before{content:"\e194"}.glyphicon-registration-mark:before{content:"\e195"}.glyphicon-cloud-download:before{content:"\e197"}.glyphicon-cloud-upload:before{content:"\e198"}.glyphicon-tree-conifer:before{content:"\e199"}.glyphicon-tree-deciduous:before{content:"\e200"}.caret{display:inline-block;width:0;height:0;margin-left:2px;vertical-align:middle;border-top:4px solid;border-right:4px solid transparent;border-left:4px solid transparent}.dropdown{position:relative}.dropdown-toggle:focus{outline:0}.dropdown-menu{position:absolute;top:100%;left:0;z-index:1000;display:none;float:left;min-width:160px;padding:5px 0;margin:2px 0 0;list-style:none;font-size:14px;background-color:#fff;border:1px solid #ccc;border:1px solid rgba(0,0,0,0.15);border-radius:4px;-webkit-box-shadow:0 6px 12px rgba(0,0,0,0.175);box-shadow:0 6px 12px rgba(0,0,0,0.175);background-clip:padding-box}.dropdown-menu.pull-right{right:0;left:auto}.dropdown-menu .divider{height:1px;margin:9px 0;overflow:hidden;background-color:#e5e5e5}.dropdown-menu>li>a{display:block;padding:3px 20px;clear:both;font-weight:normal;line-height:1.42857143;color:#333;white-space:nowrap}.dropdown-menu>li>a:hover,.dropdown-menu>li>a:focus{text-decoration:none;color:#262626;background-color:#f5f5f5}.dropdown-menu>.active>a,.dropdown-menu>.active>a:hover,.dropdown-menu>.active>a:focus{color:#fff;text-decoration:none;outline:0;background-color:#5289b3}.dropdown-menu>.disabled>a,.dropdown-menu>.disabled>a:hover,.dropdown-menu>.disabled>a:focus{color:#999}.dropdown-menu>.disabled>a:hover,.dropdown-menu>.disabled>a:focus{text-decoration:none;background-color:transparent;background-image:none;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);cursor:not-allowed}.open>.dropdown-menu{display:block}.open>a{outline:0}.dropdown-menu-right{left:auto;right:0}.dropdown-menu-left{left:0;right:auto}.dropdown-header{display:block;padding:3px 20px;font-size:12px;line-height:1.42857143;color:#999}.dropdown-backdrop{position:fixed;left:0;right:0;bottom:0;top:0;z-index:990}.pull-right>.dropdown-menu{right:0;left:auto}.dropup .caret,.navbar-fixed-bottom .dropdown .caret{border-top:0;border-bottom:4px solid;content:""}.dropup .dropdown-menu,.navbar-fixed-bottom .dropdown .dropdown-menu{top:auto;bottom:100%;margin-bottom:1px}@media (min-width:768px){.navbar-right .dropdown-menu{left:auto;right:0}.navbar-right .dropdown-menu-left{left:0;right:auto}}.btn-group,.btn-group-vertical{position:relative;display:inline-block;vertical-align:middle}.btn-group>.btn,.btn-group-vertical>.btn{position:relative;float:left}.btn-group>.btn:hover,.btn-group-vertical>.btn:hover,.btn-group>.btn:focus,.btn-group-vertical>.btn:focus,.btn-group>.btn:active,.btn-group-vertical>.btn:active,.btn-group>.btn.active,.btn-group-vertical>.btn.active{z-index:2}.btn-group>.btn:focus,.btn-group-vertical>.btn:focus{outline:none}.btn-group .btn+.btn,.btn-group .btn+.btn-group,.btn-group .btn-group+.btn,.btn-group .btn-group+.btn-group{margin-left:-1px}.btn-toolbar{margin-left:-5px}.btn-toolbar .btn-group,.btn-toolbar .input-group{float:left}.btn-toolbar>.btn,.btn-toolbar>.btn-group,.btn-toolbar>.input-group{margin-left:5px}.btn-group>.btn:not(:first-child):not(:last-child):not(.dropdown-toggle){border-radius:0}.btn-group>.btn:first-child{margin-left:0}.btn-group>.btn:first-child:not(:last-child):not(.dropdown-toggle){border-bottom-right-radius:0;border-top-right-radius:0}.btn-group>.btn:last-child:not(:first-child),.btn-group>.dropdown-toggle:not(:first-child){border-bottom-left-radius:0;border-top-left-radius:0}.btn-group>.btn-group{float:left}.btn-group>.btn-group:not(:first-child):not(:last-child)>.btn{border-radius:0}.btn-group>.btn-group:first-child>.btn:last-child,.btn-group>.btn-group:first-child>.dropdown-toggle{border-bottom-right-radius:0;border-top-right-radius:0}.btn-group>.btn-group:last-child>.btn:first-child{border-bottom-left-radius:0;border-top-left-radius:0}.btn-group .dropdown-toggle:active,.btn-group.open .dropdown-toggle{outline:0}.btn-group>.btn+.dropdown-toggle{padding-left:8px;padding-right:8px}.btn-group>.btn-lg+.dropdown-toggle{padding-left:12px;padding-right:12px}.btn-group.open .dropdown-toggle{-webkit-box-shadow:inset 0 3px 5px rgba(0,0,0,0.125);box-shadow:inset 0 3px 5px rgba(0,0,0,0.125)}.btn-group.open .dropdown-toggle.btn-link{-webkit-box-shadow:none;box-shadow:none}.btn .caret{margin-left:0}.btn-lg .caret{border-width:5px 5px 0;border-bottom-width:0}.dropup .btn-lg .caret{border-width:0 5px 5px}.btn-group-vertical>.btn,.btn-group-vertical>.btn-group,.btn-group-vertical>.btn-group>.btn{display:block;float:none;width:100%;max-width:100%}.btn-group-vertical>.btn-group>.btn{float:none}.btn-group-vertical>.btn+.btn,.btn-group-vertical>.btn+.btn-group,.btn-group-vertical>.btn-group+.btn,.btn-group-vertical>.btn-group+.btn-group{margin-top:-1px;margin-left:0}.btn-group-vertical>.btn:not(:first-child):not(:last-child){border-radius:0}.btn-group-vertical>.btn:first-child:not(:last-child){border-top-right-radius:4px;border-bottom-right-radius:0;border-bottom-left-radius:0}.btn-group-vertical>.btn:last-child:not(:first-child){border-bottom-left-radius:4px;border-top-right-radius:0;border-top-left-radius:0}.btn-group-vertical>.btn-group:not(:first-child):not(:last-child)>.btn{border-radius:0}.btn-group-vertical>.btn-group:first-child:not(:last-child)>.btn:last-child,.btn-group-vertical>.btn-group:first-child:not(:last-child)>.dropdown-toggle{border-bottom-right-radius:0;border-bottom-left-radius:0}.btn-group-vertical>.btn-group:last-child:not(:first-child)>.btn:first-child{border-top-right-radius:0;border-top-left-radius:0}.btn-group-justified{display:table;width:100%;table-layout:fixed;border-collapse:separate}.btn-group-justified>.btn,.btn-group-justified>.btn-group{float:none;display:table-cell;width:1%}.btn-group-justified>.btn-group .btn{width:100%}[data-toggle="buttons"]>.btn>input[type="radio"],[data-toggle="buttons"]>.btn>input[type="checkbox"]{display:none}.input-group{position:relative;display:table;border-collapse:separate}.input-group[class*="col-"]{float:none;padding-left:0;padding-right:0}.input-group .form-control{position:relative;z-index:2;float:left;width:100%;margin-bottom:0}.input-group-lg>.form-control,.input-group-lg>.input-group-addon,.input-group-lg>.input-group-btn>.btn{height:46px;padding:10px 16px;font-size:18px;line-height:1.33;border-radius:6px}select.input-group-lg>.form-control,select.input-group-lg>.input-group-addon,select.input-group-lg>.input-group-btn>.btn{height:46px;line-height:46px}textarea.input-group-lg>.form-control,textarea.input-group-lg>.input-group-addon,textarea.input-group-lg>.input-group-btn>.btn,select[multiple].input-group-lg>.form-control,select[multiple].input-group-lg>.input-group-addon,select[multiple].input-group-lg>.input-group-btn>.btn{height:auto}.input-group-sm>.form-control,.input-group-sm>.input-group-addon,.input-group-sm>.input-group-btn>.btn{height:30px;padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}select.input-group-sm>.form-control,select.input-group-sm>.input-group-addon,select.input-group-sm>.input-group-btn>.btn{height:30px;line-height:30px}textarea.input-group-sm>.form-control,textarea.input-group-sm>.input-group-addon,textarea.input-group-sm>.input-group-btn>.btn,select[multiple].input-group-sm>.form-control,select[multiple].input-group-sm>.input-group-addon,select[multiple].input-group-sm>.input-group-btn>.btn{height:auto}.input-group-addon,.input-group-btn,.input-group .form-control{display:table-cell}.input-group-addon:not(:first-child):not(:last-child),.input-group-btn:not(:first-child):not(:last-child),.input-group .form-control:not(:first-child):not(:last-child){border-radius:0}.input-group-addon,.input-group-btn{width:1%;white-space:nowrap;vertical-align:middle}.input-group-addon{padding:6px 12px;font-size:14px;font-weight:normal;line-height:1;color:#555;text-align:center;background-color:#eee;border:1px solid #ccc;border-radius:4px}.input-group-addon.input-sm{padding:5px 10px;font-size:12px;border-radius:3px}.input-group-addon.input-lg{padding:10px 16px;font-size:18px;border-radius:6px}.input-group-addon input[type="radio"],.input-group-addon input[type="checkbox"]{margin-top:0}.input-group .form-control:first-child,.input-group-addon:first-child,.input-group-btn:first-child>.btn,.input-group-btn:first-child>.btn-group>.btn,.input-group-btn:first-child>.dropdown-toggle,.input-group-btn:last-child>.btn:not(:last-child):not(.dropdown-toggle),.input-group-btn:last-child>.btn-group:not(:last-child)>.btn{border-bottom-right-radius:0;border-top-right-radius:0}.input-group-addon:first-child{border-right:0}.input-group .form-control:last-child,.input-group-addon:last-child,.input-group-btn:last-child>.btn,.input-group-btn:last-child>.btn-group>.btn,.input-group-btn:last-child>.dropdown-toggle,.input-group-btn:first-child>.btn:not(:first-child),.input-group-btn:first-child>.btn-group:not(:first-child)>.btn{border-bottom-left-radius:0;border-top-left-radius:0}.input-group-addon:last-child{border-left:0}.input-group-btn{position:relative;font-size:0;white-space:nowrap}.input-group-btn>.btn{position:relative}.input-group-btn>.btn+.btn{margin-left:-1px}.input-group-btn>.btn:hover,.input-group-btn>.btn:focus,.input-group-btn>.btn:active{z-index:2}.input-group-btn:first-child>.btn,.input-group-btn:first-child>.btn-group{margin-right:-1px}.input-group-btn:last-child>.btn,.input-group-btn:last-child>.btn-group{margin-left:-1px}.nav{margin-bottom:0;padding-left:0;list-style:none}.nav>li{position:relative;display:block}.nav>li>a{position:relative;display:block;padding:10px 15px}.nav>li>a:hover,.nav>li>a:focus{text-decoration:none;background-color:#eee}.nav>li.disabled>a{color:#999}.nav>li.disabled>a:hover,.nav>li.disabled>a:focus{color:#999;text-decoration:none;background-color:transparent;cursor:not-allowed}.nav .open>a,.nav .open>a:hover,.nav .open>a:focus{background-color:#eee;border-color:#5289b3}.nav .nav-divider{height:1px;margin:9px 0;overflow:hidden;background-color:#e5e5e5}.nav>li>a>img{max-width:none}.nav-tabs{border-bottom:1px solid #ddd}.nav-tabs>li{float:left;margin-bottom:-1px}.nav-tabs>li>a{margin-right:2px;line-height:1.42857143;border:1px solid transparent;border-radius:4px 4px 0 0}.nav-tabs>li>a:hover{border-color:#eee #eee #ddd}.nav-tabs>li.active>a,.nav-tabs>li.active>a:hover,.nav-tabs>li.active>a:focus{color:#555;background-color:#fff;border:1px solid #ddd;border-bottom-color:transparent;cursor:default}.nav-tabs.nav-justified{width:100%;border-bottom:0}.nav-tabs.nav-justified>li{float:none}.nav-tabs.nav-justified>li>a{text-align:center;margin-bottom:5px}.nav-tabs.nav-justified>.dropdown .dropdown-menu{top:auto;left:auto}@media (min-width:768px){.nav-tabs.nav-justified>li{display:table-cell;width:1%}.nav-tabs.nav-justified>li>a{margin-bottom:0}}.nav-tabs.nav-justified>li>a{margin-right:0;border-radius:4px}.nav-tabs.nav-justified>.active>a,.nav-tabs.nav-justified>.active>a:hover,.nav-tabs.nav-justified>.active>a:focus{border:1px solid #ddd}@media (min-width:768px){.nav-tabs.nav-justified>li>a{border-bottom:1px solid #ddd;border-radius:4px 4px 0 0}.nav-tabs.nav-justified>.active>a,.nav-tabs.nav-justified>.active>a:hover,.nav-tabs.nav-justified>.active>a:focus{border-bottom-color:#fff}}.nav-pills>li{float:left}.nav-pills>li>a{border-radius:4px}.nav-pills>li+li{margin-left:2px}.nav-pills>li.active>a,.nav-pills>li.active>a:hover,.nav-pills>li.active>a:focus{color:#fff;background-color:#5289b3}.nav-stacked>li{float:none}.nav-stacked>li+li{margin-top:2px;margin-left:0}.nav-justified{width:100%}.nav-justified>li{float:none}.nav-justified>li>a{text-align:center;margin-bottom:5px}.nav-justified>.dropdown .dropdown-menu{top:auto;left:auto}@media (min-width:768px){.nav-justified>li{display:table-cell;width:1%}.nav-justified>li>a{margin-bottom:0}}.nav-tabs-justified{border-bottom:0}.nav-tabs-justified>li>a{margin-right:0;border-radius:4px}.nav-tabs-justified>.active>a,.nav-tabs-justified>.active>a:hover,.nav-tabs-justified>.active>a:focus{border:1px solid #ddd}@media (min-width:768px){.nav-tabs-justified>li>a{border-bottom:1px solid #ddd;border-radius:4px 4px 0 0}.nav-tabs-justified>.active>a,.nav-tabs-justified>.active>a:hover,.nav-tabs-justified>.active>a:focus{border-bottom-color:#fff}}.tab-content>.tab-pane{display:none}.tab-content>.active{display:block}.nav-tabs .dropdown-menu{margin-top:-1px;border-top-right-radius:0;border-top-left-radius:0}.navbar{position:relative;min-height:50px;margin-bottom:20px;border:1px solid transparent}@media (min-width:768px){.navbar{border-radius:4px}}@media (min-width:768px){.navbar-header{float:left}}.navbar-collapse{max-height:340px;overflow-x:visible;padding-right:15px;padding-left:15px;border-top:1px solid transparent;box-shadow:inset 0 1px 0 rgba(255,255,255,0.1);-webkit-overflow-scrolling:touch}.navbar-collapse.in{overflow-y:auto}@media (min-width:768px){.navbar-collapse{width:auto;border-top:0;box-shadow:none}.navbar-collapse.collapse{display:block !important;height:auto !important;padding-bottom:0;overflow:visible !important}.navbar-collapse.in{overflow-y:visible}.navbar-fixed-top .navbar-collapse,.navbar-static-top .navbar-collapse,.navbar-fixed-bottom .navbar-collapse{padding-left:0;padding-right:0}}.container>.navbar-header,.container-fluid>.navbar-header,.container>.navbar-collapse,.container-fluid>.navbar-collapse{margin-right:-15px;margin-left:-15px}@media (min-width:768px){.container>.navbar-header,.container-fluid>.navbar-header,.container>.navbar-collapse,.container-fluid>.navbar-collapse{margin-right:0;margin-left:0}}.navbar-static-top{z-index:1000;border-width:0 0 1px}@media (min-width:768px){.navbar-static-top{border-radius:0}}.navbar-fixed-top,.navbar-fixed-bottom{position:fixed;right:0;left:0;z-index:1030}@media (min-width:768px){.navbar-fixed-top,.navbar-fixed-bottom{border-radius:0}}.navbar-fixed-top{top:0;border-width:0 0 1px}.navbar-fixed-bottom{bottom:0;margin-bottom:0;border-width:1px 0 0}.navbar-brand{float:left;padding:15px 15px;font-size:18px;line-height:20px;height:50px}.navbar-brand:hover,.navbar-brand:focus{text-decoration:none}@media (min-width:768px){.navbar>.container .navbar-brand,.navbar>.container-fluid .navbar-brand{margin-left:-15px}}.navbar-toggle{position:relative;float:right;margin-right:15px;padding:9px 10px;margin-top:8px;margin-bottom:8px;background-color:transparent;background-image:none;border:1px solid transparent;border-radius:4px}.navbar-toggle:focus{outline:none}.navbar-toggle .icon-bar{display:block;width:22px;height:2px;border-radius:1px}.navbar-toggle .icon-bar+.icon-bar{margin-top:4px}@media (min-width:768px){.navbar-toggle{display:none}}.navbar-nav{margin:7.5px -15px}.navbar-nav>li>a{padding-top:10px;padding-bottom:10px;line-height:20px}@media (max-width:767px){.navbar-nav .open .dropdown-menu{position:static;float:none;width:auto;margin-top:0;background-color:transparent;border:0;box-shadow:none}.navbar-nav .open .dropdown-menu>li>a,.navbar-nav .open .dropdown-menu .dropdown-header{padding:5px 15px 5px 25px}.navbar-nav .open .dropdown-menu>li>a{line-height:20px}.navbar-nav .open .dropdown-menu>li>a:hover,.navbar-nav .open .dropdown-menu>li>a:focus{background-image:none}}@media (min-width:768px){.navbar-nav{float:left;margin:0}.navbar-nav>li{float:left}.navbar-nav>li>a{padding-top:15px;padding-bottom:15px}.navbar-nav.navbar-right:last-child{margin-right:-15px}}@media (min-width:768px){.navbar-left{float:left !important}.navbar-right{float:right !important}}.navbar-form{margin-left:-15px;margin-right:-15px;padding:10px 15px;border-top:1px solid transparent;border-bottom:1px solid transparent;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,0.1),0 1px 0 rgba(255,255,255,0.1);box-shadow:inset 0 1px 0 rgba(255,255,255,0.1),0 1px 0 rgba(255,255,255,0.1);margin-top:8px;margin-bottom:8px}@media (min-width:768px){.navbar-form .form-group{display:inline-block;margin-bottom:0;vertical-align:middle}.navbar-form .form-control{display:inline-block;width:auto;vertical-align:middle}.navbar-form .input-group>.form-control{width:100%}.navbar-form .control-label{margin-bottom:0;vertical-align:middle}.navbar-form .radio,.navbar-form .checkbox{display:inline-block;margin-top:0;margin-bottom:0;padding-left:0;vertical-align:middle}.navbar-form .radio input[type="radio"],.navbar-form .checkbox input[type="checkbox"]{float:none;margin-left:0}.navbar-form .has-feedback .form-control-feedback{top:0}}@media (max-width:767px){.navbar-form .form-group{margin-bottom:5px}}@media (min-width:768px){.navbar-form{width:auto;border:0;margin-left:0;margin-right:0;padding-top:0;padding-bottom:0;-webkit-box-shadow:none;box-shadow:none}.navbar-form.navbar-right:last-child{margin-right:-15px}}.navbar-nav>li>.dropdown-menu{margin-top:0;border-top-right-radius:0;border-top-left-radius:0}.navbar-fixed-bottom .navbar-nav>li>.dropdown-menu{border-bottom-right-radius:0;border-bottom-left-radius:0}.navbar-btn{margin-top:8px;margin-bottom:8px}.navbar-btn.btn-sm{margin-top:10px;margin-bottom:10px}.navbar-btn.btn-xs{margin-top:14px;margin-bottom:14px}.navbar-text{margin-top:15px;margin-bottom:15px}@media (min-width:768px){.navbar-text{float:left;margin-left:15px;margin-right:15px}.navbar-text.navbar-right:last-child{margin-right:0}}.navbar-default{background-color:#f8f8f8;border-color:#e7e7e7}.navbar-default .navbar-brand{color:#777}.navbar-default .navbar-brand:hover,.navbar-default .navbar-brand:focus{color:#5e5e5e;background-color:transparent}.navbar-default .navbar-text{color:#777}.navbar-default .navbar-nav>li>a{color:#777}.navbar-default .navbar-nav>li>a:hover,.navbar-default .navbar-nav>li>a:focus{color:#333;background-color:transparent}.navbar-default .navbar-nav>.active>a,.navbar-default .navbar-nav>.active>a:hover,.navbar-default .navbar-nav>.active>a:focus{color:#555;background-color:#e7e7e7}.navbar-default .navbar-nav>.disabled>a,.navbar-default .navbar-nav>.disabled>a:hover,.navbar-default .navbar-nav>.disabled>a:focus{color:#ccc;background-color:transparent}.navbar-default .navbar-toggle{border-color:#ddd}.navbar-default .navbar-toggle:hover,.navbar-default .navbar-toggle:focus{background-color:#ddd}.navbar-default .navbar-toggle .icon-bar{background-color:#888}.navbar-default .navbar-collapse,.navbar-default .navbar-form{border-color:#e7e7e7}.navbar-default .navbar-nav>.open>a,.navbar-default .navbar-nav>.open>a:hover,.navbar-default .navbar-nav>.open>a:focus{background-color:#e7e7e7;color:#555}@media (max-width:767px){.navbar-default .navbar-nav .open .dropdown-menu>li>a{color:#777}.navbar-default .navbar-nav .open .dropdown-menu>li>a:hover,.navbar-default .navbar-nav .open .dropdown-menu>li>a:focus{color:#333;background-color:transparent}.navbar-default .navbar-nav .open .dropdown-menu>.active>a,.navbar-default .navbar-nav .open .dropdown-menu>.active>a:hover,.navbar-default .navbar-nav .open .dropdown-menu>.active>a:focus{color:#555;background-color:#e7e7e7}.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a,.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a:hover,.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a:focus{color:#ccc;background-color:transparent}}.navbar-default .navbar-link{color:#777}.navbar-default .navbar-link:hover{color:#333}.navbar-inverse{background-color:#222;border-color:#080808}.navbar-inverse .navbar-brand{color:#999}.navbar-inverse .navbar-brand:hover,.navbar-inverse .navbar-brand:focus{color:#fff;background-color:transparent}.navbar-inverse .navbar-text{color:#999}.navbar-inverse .navbar-nav>li>a{color:#999}.navbar-inverse .navbar-nav>li>a:hover,.navbar-inverse .navbar-nav>li>a:focus{color:#fff;background-color:transparent}.navbar-inverse .navbar-nav>.active>a,.navbar-inverse .navbar-nav>.active>a:hover,.navbar-inverse .navbar-nav>.active>a:focus{color:#fff;background-color:#080808}.navbar-inverse .navbar-nav>.disabled>a,.navbar-inverse .navbar-nav>.disabled>a:hover,.navbar-inverse .navbar-nav>.disabled>a:focus{color:#444;background-color:transparent}.navbar-inverse .navbar-toggle{border-color:#333}.navbar-inverse .navbar-toggle:hover,.navbar-inverse .navbar-toggle:focus{background-color:#333}.navbar-inverse .navbar-toggle .icon-bar{background-color:#fff}.navbar-inverse .navbar-collapse,.navbar-inverse .navbar-form{border-color:#101010}.navbar-inverse .navbar-nav>.open>a,.navbar-inverse .navbar-nav>.open>a:hover,.navbar-inverse .navbar-nav>.open>a:focus{background-color:#080808;color:#fff}@media (max-width:767px){.navbar-inverse .navbar-nav .open .dropdown-menu>.dropdown-header{border-color:#080808}.navbar-inverse .navbar-nav .open .dropdown-menu .divider{background-color:#080808}.navbar-inverse .navbar-nav .open .dropdown-menu>li>a{color:#999}.navbar-inverse .navbar-nav .open .dropdown-menu>li>a:hover,.navbar-inverse .navbar-nav .open .dropdown-menu>li>a:focus{color:#fff;background-color:transparent}.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a,.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a:hover,.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a:focus{color:#fff;background-color:#080808}.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a,.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a:hover,.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a:focus{color:#444;background-color:transparent}}.navbar-inverse .navbar-link{color:#999}.navbar-inverse .navbar-link:hover{color:#fff}.breadcrumb{padding:8px 15px;margin-bottom:20px;list-style:none;background-color:#f5f5f5;border-radius:4px}.breadcrumb>li{display:inline-block}.breadcrumb>li+li:before{content:"/\00a0";padding:0 5px;color:#ccc}.breadcrumb>.active{color:#999}.pagination{display:inline-block;padding-left:0;margin:20px 0;border-radius:4px}.pagination>li{display:inline}.pagination>li>a,.pagination>li>span{position:relative;float:left;padding:6px 12px;line-height:1.42857143;text-decoration:none;color:#5289b3;background-color:#fff;border:1px solid #ddd;margin-left:-1px}.pagination>li:first-child>a,.pagination>li:first-child>span{margin-left:0;border-bottom-left-radius:4px;border-top-left-radius:4px}.pagination>li:last-child>a,.pagination>li:last-child>span{border-bottom-right-radius:4px;border-top-right-radius:4px}.pagination>li>a:hover,.pagination>li>span:hover,.pagination>li>a:focus,.pagination>li>span:focus{color:#386180;background-color:#eee;border-color:#ddd}.pagination>.active>a,.pagination>.active>span,.pagination>.active>a:hover,.pagination>.active>span:hover,.pagination>.active>a:focus,.pagination>.active>span:focus{z-index:2;color:#fff;background-color:#5289b3;border-color:#5289b3;cursor:default}.pagination>.disabled>span,.pagination>.disabled>span:hover,.pagination>.disabled>span:focus,.pagination>.disabled>a,.pagination>.disabled>a:hover,.pagination>.disabled>a:focus{color:#999;background-color:#fff;border-color:#ddd;cursor:not-allowed}.pagination-lg>li>a,.pagination-lg>li>span{padding:10px 16px;font-size:18px}.pagination-lg>li:first-child>a,.pagination-lg>li:first-child>span{border-bottom-left-radius:6px;border-top-left-radius:6px}.pagination-lg>li:last-child>a,.pagination-lg>li:last-child>span{border-bottom-right-radius:6px;border-top-right-radius:6px}.pagination-sm>li>a,.pagination-sm>li>span{padding:5px 10px;font-size:12px}.pagination-sm>li:first-child>a,.pagination-sm>li:first-child>span{border-bottom-left-radius:3px;border-top-left-radius:3px}.pagination-sm>li:last-child>a,.pagination-sm>li:last-child>span{border-bottom-right-radius:3px;border-top-right-radius:3px}.pager{padding-left:0;margin:20px 0;list-style:none;text-align:center}.pager li{display:inline}.pager li>a,.pager li>span{display:inline-block;padding:5px 14px;background-color:#fff;border:1px solid #ddd;border-radius:15px}.pager li>a:hover,.pager li>a:focus{text-decoration:none;background-color:#eee}.pager .next>a,.pager .next>span{float:right}.pager .previous>a,.pager .previous>span{float:left}.pager .disabled>a,.pager .disabled>a:hover,.pager .disabled>a:focus,.pager .disabled>span{color:#999;background-color:#fff;cursor:not-allowed}.label{display:inline;padding:.2em .6em .3em;font-size:75%;font-weight:bold;line-height:1;color:#fff;text-align:center;white-space:nowrap;vertical-align:baseline;border-radius:.25em}.label[href]:hover,.label[href]:focus{color:#fff;text-decoration:none;cursor:pointer}.label:empty{display:none}.btn .label{position:relative;top:-1px}.label-default{background-color:#999}.label-default[href]:hover,.label-default[href]:focus{background-color:#808080}.label-primary{background-color:#5289b3}.label-primary[href]:hover,.label-primary[href]:focus{background-color:#406e92}.label-success{background-color:#70a829}.label-success[href]:hover,.label-success[href]:focus{background-color:#557f1f}.label-info{background-color:#5bc0de}.label-info[href]:hover,.label-info[href]:focus{background-color:#31b0d5}.label-warning{background-color:#f9c861}.label-warning[href]:hover,.label-warning[href]:focus{background-color:#f7b730}.label-danger{background-color:#d9534f}.label-danger[href]:hover,.label-danger[href]:focus{background-color:#c9302c}.badge{display:inline-block;min-width:10px;padding:3px 7px;font-size:12px;font-weight:bold;color:#fff;line-height:1;vertical-align:baseline;white-space:nowrap;text-align:center;background-color:#999;border-radius:10px}.badge:empty{display:none}.btn .badge{position:relative;top:-1px}.btn-xs .badge{top:0;padding:1px 5px}a.badge:hover,a.badge:focus{color:#fff;text-decoration:none;cursor:pointer}a.list-group-item.active>.badge,.nav-pills>.active>a>.badge{color:#5289b3;background-color:#fff}.nav-pills>li>a>.badge{margin-left:3px}.jumbotron{padding:30px;margin-bottom:30px;color:inherit;background-color:#eee}.jumbotron h1,.jumbotron .h1{color:inherit}.jumbotron p{margin-bottom:15px;font-size:21px;font-weight:200}.container .jumbotron{border-radius:6px}.jumbotron .container{max-width:100%}@media screen and (min-width:768px){.jumbotron{padding-top:48px;padding-bottom:48px}.container .jumbotron{padding-left:60px;padding-right:60px}.jumbotron h1,.jumbotron .h1{font-size:63px}}.thumbnail{display:block;padding:4px;margin-bottom:20px;line-height:1.42857143;background-color:#fff;border:1px solid #ddd;border-radius:4px;-webkit-transition:all .2s ease-in-out;transition:all .2s ease-in-out}.thumbnail>img,.thumbnail a>img{margin-left:auto;margin-right:auto}a.thumbnail:hover,a.thumbnail:focus,a.thumbnail.active{border-color:#5289b3}.thumbnail .caption{padding:9px;color:#333}.alert{padding:15px;margin-bottom:20px;border:1px solid transparent;border-radius:4px}.alert h4{margin-top:0;color:inherit}.alert .alert-link{font-weight:bold}.alert>p,.alert>ul{margin-bottom:0}.alert>p+p{margin-top:5px}.alert-dismissable{padding-right:35px}.alert-dismissable .close{position:relative;top:-2px;right:-21px;color:inherit}.alert-success{background-color:#dff0d8;border-color:#d6e9c6;color:#3c763d}.alert-success hr{border-top-color:#c9e2b3}.alert-success .alert-link{color:#2b542c}.alert-info{background-color:#d9edf7;border-color:#bce8f1;color:#31708f}.alert-info hr{border-top-color:#a6e1ec}.alert-info .alert-link{color:#245269}.alert-warning{background-color:#fcf8e3;border-color:#faebcc;color:#8a6d3b}.alert-warning hr{border-top-color:#f7e1b5}.alert-warning .alert-link{color:#66512c}.alert-danger{background-color:#f2dede;border-color:#ebccd1;color:#a94442}.alert-danger hr{border-top-color:#e4b9c0}.alert-danger .alert-link{color:#843534}@-webkit-keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}@keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}.progress{overflow:hidden;height:20px;margin-bottom:20px;background-color:#f5f5f5;border-radius:4px;-webkit-box-shadow:inset 0 1px 2px rgba(0,0,0,0.1);box-shadow:inset 0 1px 2px rgba(0,0,0,0.1)}.progress-bar{float:left;width:0%;height:100%;font-size:12px;line-height:20px;color:#fff;text-align:center;background-color:#5289b3;-webkit-box-shadow:inset 0 -1px 0 rgba(0,0,0,0.15);box-shadow:inset 0 -1px 0 rgba(0,0,0,0.15);-webkit-transition:width .6s ease;transition:width .6s ease}.progress-striped .progress-bar{background-image:-webkit-linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent);background-image:linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent);background-size:40px 40px}.progress.active .progress-bar{-webkit-animation:progress-bar-stripes 2s linear infinite;animation:progress-bar-stripes 2s linear infinite}.progress-bar-success{background-color:#70a829}.progress-striped .progress-bar-success{background-image:-webkit-linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent);background-image:linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent)}.progress-bar-info{background-color:#5bc0de}.progress-striped .progress-bar-info{background-image:-webkit-linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent);background-image:linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent)}.progress-bar-warning{background-color:#f9c861}.progress-striped .progress-bar-warning{background-image:-webkit-linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent);background-image:linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent)}.progress-bar-danger{background-color:#d9534f}.progress-striped .progress-bar-danger{background-image:-webkit-linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent);background-image:linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent)}.media,.media-body{overflow:hidden;zoom:1}.media,.media .media{margin-top:15px}.media:first-child{margin-top:0}.media-object{display:block}.media-heading{margin:0 0 5px}.media>.pull-left{margin-right:10px}.media>.pull-right{margin-left:10px}.media-list{padding-left:0;list-style:none}.list-group{margin-bottom:20px;padding-left:0}.list-group-item{position:relative;display:block;padding:10px 15px;margin-bottom:-1px;background-color:#fff;border:1px solid #ddd}.list-group-item:first-child{border-top-right-radius:4px;border-top-left-radius:4px}.list-group-item:last-child{margin-bottom:0;border-bottom-right-radius:4px;border-bottom-left-radius:4px}.list-group-item>.badge{float:right}.list-group-item>.badge+.badge{margin-right:5px}a.list-group-item{color:#555}a.list-group-item .list-group-item-heading{color:#333}a.list-group-item:hover,a.list-group-item:focus{text-decoration:none;background-color:#f5f5f5}a.list-group-item.active,a.list-group-item.active:hover,a.list-group-item.active:focus{z-index:2;color:#fff;background-color:#5289b3;border-color:#5289b3}a.list-group-item.active .list-group-item-heading,a.list-group-item.active:hover .list-group-item-heading,a.list-group-item.active:focus .list-group-item-heading{color:inherit}a.list-group-item.active .list-group-item-text,a.list-group-item.active:hover .list-group-item-text,a.list-group-item.active:focus .list-group-item-text{color:#e0eaf1}.list-group-item-success{color:#3c763d;background-color:#dff0d8}a.list-group-item-success{color:#3c763d}a.list-group-item-success .list-group-item-heading{color:inherit}a.list-group-item-success:hover,a.list-group-item-success:focus{color:#3c763d;background-color:#d0e9c6}a.list-group-item-success.active,a.list-group-item-success.active:hover,a.list-group-item-success.active:focus{color:#fff;background-color:#3c763d;border-color:#3c763d}.list-group-item-info{color:#31708f;background-color:#d9edf7}a.list-group-item-info{color:#31708f}a.list-group-item-info .list-group-item-heading{color:inherit}a.list-group-item-info:hover,a.list-group-item-info:focus{color:#31708f;background-color:#c4e3f3}a.list-group-item-info.active,a.list-group-item-info.active:hover,a.list-group-item-info.active:focus{color:#fff;background-color:#31708f;border-color:#31708f}.list-group-item-warning{color:#8a6d3b;background-color:#fcf8e3}a.list-group-item-warning{color:#8a6d3b}a.list-group-item-warning .list-group-item-heading{color:inherit}a.list-group-item-warning:hover,a.list-group-item-warning:focus{color:#8a6d3b;background-color:#faf2cc}a.list-group-item-warning.active,a.list-group-item-warning.active:hover,a.list-group-item-warning.active:focus{color:#fff;background-color:#8a6d3b;border-color:#8a6d3b}.list-group-item-danger{color:#a94442;background-color:#f2dede}a.list-group-item-danger{color:#a94442}a.list-group-item-danger .list-group-item-heading{color:inherit}a.list-group-item-danger:hover,a.list-group-item-danger:focus{color:#a94442;background-color:#ebcccc}a.list-group-item-danger.active,a.list-group-item-danger.active:hover,a.list-group-item-danger.active:focus{color:#fff;background-color:#a94442;border-color:#a94442}.list-group-item-heading{margin-top:0;margin-bottom:5px}.list-group-item-text{margin-bottom:0;line-height:1.3}.panel{margin-bottom:20px;background-color:#fff;border:1px solid transparent;border-radius:4px;-webkit-box-shadow:0 1px 1px rgba(0,0,0,0.05);box-shadow:0 1px 1px rgba(0,0,0,0.05)}.panel-body{padding:15px}.panel-heading{padding:10px 15px;border-bottom:1px solid transparent;border-top-right-radius:3px;border-top-left-radius:3px}.panel-heading>.dropdown .dropdown-toggle{color:inherit}.panel-title{margin-top:0;margin-bottom:0;font-size:16px;color:inherit}.panel-title>a{color:inherit}.panel-footer{padding:10px 15px;background-color:#f5f5f5;border-top:1px solid #ddd;border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel>.list-group{margin-bottom:0}.panel>.list-group .list-group-item{border-width:1px 0;border-radius:0}.panel>.list-group:first-child .list-group-item:first-child{border-top:0;border-top-right-radius:3px;border-top-left-radius:3px}.panel>.list-group:last-child .list-group-item:last-child{border-bottom:0;border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel-heading+.list-group .list-group-item:first-child{border-top-width:0}.panel>.table,.panel>.table-responsive>.table{margin-bottom:0}.panel>.table:first-child,.panel>.table-responsive:first-child>.table:first-child{border-top-right-radius:3px;border-top-left-radius:3px}.panel>.table:first-child>thead:first-child>tr:first-child td:first-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child td:first-child,.panel>.table:first-child>tbody:first-child>tr:first-child td:first-child,.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child td:first-child,.panel>.table:first-child>thead:first-child>tr:first-child th:first-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child th:first-child,.panel>.table:first-child>tbody:first-child>tr:first-child th:first-child,.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child th:first-child{border-top-left-radius:3px}.panel>.table:first-child>thead:first-child>tr:first-child td:last-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child td:last-child,.panel>.table:first-child>tbody:first-child>tr:first-child td:last-child,.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child td:last-child,.panel>.table:first-child>thead:first-child>tr:first-child th:last-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child th:last-child,.panel>.table:first-child>tbody:first-child>tr:first-child th:last-child,.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child th:last-child{border-top-right-radius:3px}.panel>.table:last-child,.panel>.table-responsive:last-child>.table:last-child{border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel>.table:last-child>tbody:last-child>tr:last-child td:first-child,.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child td:first-child,.panel>.table:last-child>tfoot:last-child>tr:last-child td:first-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child td:first-child,.panel>.table:last-child>tbody:last-child>tr:last-child th:first-child,.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child th:first-child,.panel>.table:last-child>tfoot:last-child>tr:last-child th:first-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child th:first-child{border-bottom-left-radius:3px}.panel>.table:last-child>tbody:last-child>tr:last-child td:last-child,.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child td:last-child,.panel>.table:last-child>tfoot:last-child>tr:last-child td:last-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child td:last-child,.panel>.table:last-child>tbody:last-child>tr:last-child th:last-child,.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child th:last-child,.panel>.table:last-child>tfoot:last-child>tr:last-child th:last-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child th:last-child{border-bottom-right-radius:3px}.panel>.panel-body+.table,.panel>.panel-body+.table-responsive{border-top:1px solid #ddd}.panel>.table>tbody:first-child>tr:first-child th,.panel>.table>tbody:first-child>tr:first-child td{border-top:0}.panel>.table-bordered,.panel>.table-responsive>.table-bordered{border:0}.panel>.table-bordered>thead>tr>th:first-child,.panel>.table-responsive>.table-bordered>thead>tr>th:first-child,.panel>.table-bordered>tbody>tr>th:first-child,.panel>.table-responsive>.table-bordered>tbody>tr>th:first-child,.panel>.table-bordered>tfoot>tr>th:first-child,.panel>.table-responsive>.table-bordered>tfoot>tr>th:first-child,.panel>.table-bordered>thead>tr>td:first-child,.panel>.table-responsive>.table-bordered>thead>tr>td:first-child,.panel>.table-bordered>tbody>tr>td:first-child,.panel>.table-responsive>.table-bordered>tbody>tr>td:first-child,.panel>.table-bordered>tfoot>tr>td:first-child,.panel>.table-responsive>.table-bordered>tfoot>tr>td:first-child{border-left:0}.panel>.table-bordered>thead>tr>th:last-child,.panel>.table-responsive>.table-bordered>thead>tr>th:last-child,.panel>.table-bordered>tbody>tr>th:last-child,.panel>.table-responsive>.table-bordered>tbody>tr>th:last-child,.panel>.table-bordered>tfoot>tr>th:last-child,.panel>.table-responsive>.table-bordered>tfoot>tr>th:last-child,.panel>.table-bordered>thead>tr>td:last-child,.panel>.table-responsive>.table-bordered>thead>tr>td:last-child,.panel>.table-bordered>tbody>tr>td:last-child,.panel>.table-responsive>.table-bordered>tbody>tr>td:last-child,.panel>.table-bordered>tfoot>tr>td:last-child,.panel>.table-responsive>.table-bordered>tfoot>tr>td:last-child{border-right:0}.panel>.table-bordered>thead>tr:first-child>td,.panel>.table-responsive>.table-bordered>thead>tr:first-child>td,.panel>.table-bordered>tbody>tr:first-child>td,.panel>.table-responsive>.table-bordered>tbody>tr:first-child>td,.panel>.table-bordered>thead>tr:first-child>th,.panel>.table-responsive>.table-bordered>thead>tr:first-child>th,.panel>.table-bordered>tbody>tr:first-child>th,.panel>.table-responsive>.table-bordered>tbody>tr:first-child>th{border-bottom:0}.panel>.table-bordered>tbody>tr:last-child>td,.panel>.table-responsive>.table-bordered>tbody>tr:last-child>td,.panel>.table-bordered>tfoot>tr:last-child>td,.panel>.table-responsive>.table-bordered>tfoot>tr:last-child>td,.panel>.table-bordered>tbody>tr:last-child>th,.panel>.table-responsive>.table-bordered>tbody>tr:last-child>th,.panel>.table-bordered>tfoot>tr:last-child>th,.panel>.table-responsive>.table-bordered>tfoot>tr:last-child>th{border-bottom:0}.panel>.table-responsive{border:0;margin-bottom:0}.panel-group{margin-bottom:20px}.panel-group .panel{margin-bottom:0;border-radius:4px;overflow:hidden}.panel-group .panel+.panel{margin-top:5px}.panel-group .panel-heading{border-bottom:0}.panel-group .panel-heading+.panel-collapse .panel-body{border-top:1px solid #ddd}.panel-group .panel-footer{border-top:0}.panel-group .panel-footer+.panel-collapse .panel-body{border-bottom:1px solid #ddd}.panel-default{border-color:#ddd}.panel-default>.panel-heading{color:#333;background-color:#f5f5f5;border-color:#ddd}.panel-default>.panel-heading+.panel-collapse .panel-body{border-top-color:#ddd}.panel-default>.panel-footer+.panel-collapse .panel-body{border-bottom-color:#ddd}.panel-primary{border-color:#5289b3}.panel-primary>.panel-heading{color:#fff;background-color:#5289b3;border-color:#5289b3}.panel-primary>.panel-heading+.panel-collapse .panel-body{border-top-color:#5289b3}.panel-primary>.panel-footer+.panel-collapse .panel-body{border-bottom-color:#5289b3}.panel-success{border-color:#d6e9c6}.panel-success>.panel-heading{color:#3c763d;background-color:#dff0d8;border-color:#d6e9c6}.panel-success>.panel-heading+.panel-collapse .panel-body{border-top-color:#d6e9c6}.panel-success>.panel-footer+.panel-collapse .panel-body{border-bottom-color:#d6e9c6}.panel-info{border-color:#bce8f1}.panel-info>.panel-heading{color:#31708f;background-color:#d9edf7;border-color:#bce8f1}.panel-info>.panel-heading+.panel-collapse .panel-body{border-top-color:#bce8f1}.panel-info>.panel-footer+.panel-collapse .panel-body{border-bottom-color:#bce8f1}.panel-warning{border-color:#faebcc}.panel-warning>.panel-heading{color:#8a6d3b;background-color:#fcf8e3;border-color:#faebcc}.panel-warning>.panel-heading+.panel-collapse .panel-body{border-top-color:#faebcc}.panel-warning>.panel-footer+.panel-collapse .panel-body{border-bottom-color:#faebcc}.panel-danger{border-color:#ebccd1}.panel-danger>.panel-heading{color:#a94442;background-color:#f2dede;border-color:#ebccd1}.panel-danger>.panel-heading+.panel-collapse .panel-body{border-top-color:#ebccd1}.panel-danger>.panel-footer+.panel-collapse .panel-body{border-bottom-color:#ebccd1}.well{min-height:20px;padding:19px;margin-bottom:20px;background-color:#f5f5f5;border:1px solid #e3e3e3;border-radius:4px;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.05);box-shadow:inset 0 1px 1px rgba(0,0,0,0.05)}.well blockquote{border-color:#ddd;border-color:rgba(0,0,0,0.15)}.well-lg{padding:24px;border-radius:6px}.well-sm{padding:9px;border-radius:3px}.close{float:right;font-size:21px;font-weight:bold;line-height:1;color:#000;text-shadow:0 1px 0 #fff;opacity:.2;filter:alpha(opacity=20)}.close:hover,.close:focus{color:#000;text-decoration:none;cursor:pointer;opacity:.5;filter:alpha(opacity=50)}button.close{padding:0;cursor:pointer;background:transparent;border:0;-webkit-appearance:none}.modal-open{overflow:hidden}.modal{display:none;overflow:auto;overflow-y:scroll;position:fixed;top:0;right:0;bottom:0;left:0;z-index:1050;-webkit-overflow-scrolling:touch;outline:0}.modal.fade .modal-dialog{-webkit-transform:translate(0, -25%);-ms-transform:translate(0, -25%);transform:translate(0, -25%);-webkit-transition:-webkit-transform 0.3s ease-out;-moz-transition:-moz-transform 0.3s ease-out;-o-transition:-o-transform 0.3s ease-out;transition:transform 0.3s ease-out}.modal.in .modal-dialog{-webkit-transform:translate(0, 0);-ms-transform:translate(0, 0);transform:translate(0, 0)}.modal-dialog{position:relative;width:auto;margin:10px}.modal-content{position:relative;background-color:#fff;border:1px solid #999;border:1px solid rgba(0,0,0,0.2);border-radius:6px;-webkit-box-shadow:0 3px 9px rgba(0,0,0,0.5);box-shadow:0 3px 9px rgba(0,0,0,0.5);background-clip:padding-box;outline:none}.modal-backdrop{position:fixed;top:0;right:0;bottom:0;left:0;z-index:1040;background-color:#000}.modal-backdrop.fade{opacity:0;filter:alpha(opacity=0)}.modal-backdrop.in{opacity:.5;filter:alpha(opacity=50)}.modal-header{padding:15px;border-bottom:1px solid #e5e5e5;min-height:16.42857143px}.modal-header .close{margin-top:-2px}.modal-title{margin:0;line-height:1.42857143}.modal-body{position:relative;padding:20px}.modal-footer{margin-top:15px;padding:19px 20px 20px;text-align:right;border-top:1px solid #e5e5e5}.modal-footer .btn+.btn{margin-left:5px;margin-bottom:0}.modal-footer .btn-group .btn+.btn{margin-left:-1px}.modal-footer .btn-block+.btn-block{margin-left:0}@media (min-width:768px){.modal-dialog{width:600px;margin:30px auto}.modal-content{-webkit-box-shadow:0 5px 15px rgba(0,0,0,0.5);box-shadow:0 5px 15px rgba(0,0,0,0.5)}.modal-sm{width:300px}}@media (min-width:992px){.modal-lg{width:900px}}.tooltip{position:absolute;z-index:1030;display:block;visibility:visible;font-size:12px;line-height:1.4;opacity:0;filter:alpha(opacity=0)}.tooltip.in{opacity:.9;filter:alpha(opacity=90)}.tooltip.top{margin-top:-3px;padding:5px 0}.tooltip.right{margin-left:3px;padding:0 5px}.tooltip.bottom{margin-top:3px;padding:5px 0}.tooltip.left{margin-left:-3px;padding:0 5px}.tooltip-inner{max-width:200px;padding:3px 8px;color:#fff;text-align:center;text-decoration:none;background-color:#000;border-radius:4px}.tooltip-arrow{position:absolute;width:0;height:0;border-color:transparent;border-style:solid}.tooltip.top .tooltip-arrow{bottom:0;left:50%;margin-left:-5px;border-width:5px 5px 0;border-top-color:#000}.tooltip.top-left .tooltip-arrow{bottom:0;left:5px;border-width:5px 5px 0;border-top-color:#000}.tooltip.top-right .tooltip-arrow{bottom:0;right:5px;border-width:5px 5px 0;border-top-color:#000}.tooltip.right .tooltip-arrow{top:50%;left:0;margin-top:-5px;border-width:5px 5px 5px 0;border-right-color:#000}.tooltip.left .tooltip-arrow{top:50%;right:0;margin-top:-5px;border-width:5px 0 5px 5px;border-left-color:#000}.tooltip.bottom .tooltip-arrow{top:0;left:50%;margin-left:-5px;border-width:0 5px 5px;border-bottom-color:#000}.tooltip.bottom-left .tooltip-arrow{top:0;left:5px;border-width:0 5px 5px;border-bottom-color:#000}.tooltip.bottom-right .tooltip-arrow{top:0;right:5px;border-width:0 5px 5px;border-bottom-color:#000}.popover{position:absolute;top:0;left:0;z-index:1010;display:none;max-width:276px;padding:1px;text-align:left;background-color:#fff;background-clip:padding-box;border:1px solid #ccc;border:1px solid rgba(0,0,0,0.2);border-radius:6px;-webkit-box-shadow:0 5px 10px rgba(0,0,0,0.2);box-shadow:0 5px 10px rgba(0,0,0,0.2);white-space:normal}.popover.top{margin-top:-10px}.popover.right{margin-left:10px}.popover.bottom{margin-top:10px}.popover.left{margin-left:-10px}.popover-title{margin:0;padding:8px 14px;font-size:14px;font-weight:normal;line-height:18px;background-color:#f7f7f7;border-bottom:1px solid #ebebeb;border-radius:5px 5px 0 0}.popover-content{padding:9px 14px}.popover>.arrow,.popover>.arrow:after{position:absolute;display:block;width:0;height:0;border-color:transparent;border-style:solid}.popover>.arrow{border-width:11px}.popover>.arrow:after{border-width:10px;content:""}.popover.top>.arrow{left:50%;margin-left:-11px;border-bottom-width:0;border-top-color:#999;border-top-color:rgba(0,0,0,0.25);bottom:-11px}.popover.top>.arrow:after{content:" ";bottom:1px;margin-left:-10px;border-bottom-width:0;border-top-color:#fff}.popover.right>.arrow{top:50%;left:-11px;margin-top:-11px;border-left-width:0;border-right-color:#999;border-right-color:rgba(0,0,0,0.25)}.popover.right>.arrow:after{content:" ";left:1px;bottom:-10px;border-left-width:0;border-right-color:#fff}.popover.bottom>.arrow{left:50%;margin-left:-11px;border-top-width:0;border-bottom-color:#999;border-bottom-color:rgba(0,0,0,0.25);top:-11px}.popover.bottom>.arrow:after{content:" ";top:1px;margin-left:-10px;border-top-width:0;border-bottom-color:#fff}.popover.left>.arrow{top:50%;right:-11px;margin-top:-11px;border-right-width:0;border-left-color:#999;border-left-color:rgba(0,0,0,0.25)}.popover.left>.arrow:after{content:" ";right:1px;border-right-width:0;border-left-color:#fff;bottom:-10px}.carousel{position:relative}.carousel-inner{position:relative;overflow:hidden;width:100%}.carousel-inner>.item{display:none;position:relative;-webkit-transition:.6s ease-in-out left;transition:.6s ease-in-out left}.carousel-inner>.item>img,.carousel-inner>.item>a>img{line-height:1}.carousel-inner>.active,.carousel-inner>.next,.carousel-inner>.prev{display:block}.carousel-inner>.active{left:0}.carousel-inner>.next,.carousel-inner>.prev{position:absolute;top:0;width:100%}.carousel-inner>.next{left:100%}.carousel-inner>.prev{left:-100%}.carousel-inner>.next.left,.carousel-inner>.prev.right{left:0}.carousel-inner>.active.left{left:-100%}.carousel-inner>.active.right{left:100%}.carousel-control{position:absolute;top:0;left:0;bottom:0;width:15%;opacity:.5;filter:alpha(opacity=50);font-size:20px;color:#fff;text-align:center;text-shadow:0 1px 2px rgba(0,0,0,0.6)}.carousel-control.left{background-image:-webkit-linear-gradient(left, color-stop(rgba(0,0,0,0.5) 0), color-stop(rgba(0,0,0,0.0001) 100%));background-image:linear-gradient(to right, rgba(0,0,0,0.5) 0, rgba(0,0,0,0.0001) 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#80000000', endColorstr='#00000000', GradientType=1)}.carousel-control.right{left:auto;right:0;background-image:-webkit-linear-gradient(left, color-stop(rgba(0,0,0,0.0001) 0), color-stop(rgba(0,0,0,0.5) 100%));background-image:linear-gradient(to right, rgba(0,0,0,0.0001) 0, rgba(0,0,0,0.5) 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#00000000', endColorstr='#80000000', GradientType=1)}.carousel-control:hover,.carousel-control:focus{outline:none;color:#fff;text-decoration:none;opacity:.9;filter:alpha(opacity=90)}.carousel-control .icon-prev,.carousel-control .icon-next,.carousel-control .glyphicon-chevron-left,.carousel-control .glyphicon-chevron-right{position:absolute;top:50%;z-index:5;display:inline-block}.carousel-control .icon-prev,.carousel-control .glyphicon-chevron-left{left:50%}.carousel-control .icon-next,.carousel-control .glyphicon-chevron-right{right:50%}.carousel-control .icon-prev,.carousel-control .icon-next{width:20px;height:20px;margin-top:-10px;margin-left:-10px;font-family:serif}.carousel-control .icon-prev:before{content:'\2039'}.carousel-control .icon-next:before{content:'\203a'}.carousel-indicators{position:absolute;bottom:10px;left:50%;z-index:15;width:60%;margin-left:-30%;padding-left:0;list-style:none;text-align:center}.carousel-indicators li{display:inline-block;width:10px;height:10px;margin:1px;text-indent:-999px;border:1px solid #fff;border-radius:10px;cursor:pointer;background-color:#000 \9;background-color:rgba(0,0,0,0)}.carousel-indicators .active{margin:0;width:12px;height:12px;background-color:#fff}.carousel-caption{position:absolute;left:15%;right:15%;bottom:20px;z-index:10;padding-top:20px;padding-bottom:20px;color:#fff;text-align:center;text-shadow:0 1px 2px rgba(0,0,0,0.6)}.carousel-caption .btn{text-shadow:none}@media screen and (min-width:768px){.carousel-control .glyphicon-chevron-left,.carousel-control .glyphicon-chevron-right,.carousel-control .icon-prev,.carousel-control .icon-next{width:30px;height:30px;margin-top:-15px;margin-left:-15px;font-size:30px}.carousel-caption{left:20%;right:20%;padding-bottom:30px}.carousel-indicators{bottom:20px}}.clearfix:before,.clearfix:after,.container:before,.container:after,.container-fluid:before,.container-fluid:after,.row:before,.row:after,.form-horizontal .form-group:before,.form-horizontal .form-group:after,.btn-toolbar:before,.btn-toolbar:after,.btn-group-vertical>.btn-group:before,.btn-group-vertical>.btn-group:after,.nav:before,.nav:after,.navbar:before,.navbar:after,.navbar-header:before,.navbar-header:after,.navbar-collapse:before,.navbar-collapse:after,.pager:before,.pager:after,.panel-body:before,.panel-body:after,.modal-footer:before,.modal-footer:after{content:" ";display:table}.clearfix:after,.container:after,.container-fluid:after,.row:after,.form-horizontal .form-group:after,.btn-toolbar:after,.btn-group-vertical>.btn-group:after,.nav:after,.navbar:after,.navbar-header:after,.navbar-collapse:after,.pager:after,.panel-body:after,.modal-footer:after{clear:both}.center-block{display:block;margin-left:auto;margin-right:auto}.pull-right{float:right !important}.pull-left{float:left !important}.hide{display:none !important}.show{display:block !important}.invisible{visibility:hidden}.text-hide{font:0/0 a;color:transparent;text-shadow:none;background-color:transparent;border:0}.hidden{display:none !important;visibility:hidden !important}.affix{position:fixed}@-ms-viewport{width:device-width}.visible-xs,.visible-sm,.visible-md,.visible-lg{display:none !important}@media (max-width:767px){.visible-xs{display:block !important}table.visible-xs{display:table}tr.visible-xs{display:table-row !important}th.visible-xs,td.visible-xs{display:table-cell !important}}@media (min-width:768px) and (max-width:991px){.visible-sm{display:block !important}table.visible-sm{display:table}tr.visible-sm{display:table-row !important}th.visible-sm,td.visible-sm{display:table-cell !important}}@media (min-width:992px) and (max-width:1199px){.visible-md{display:block !important}table.visible-md{display:table}tr.visible-md{display:table-row !important}th.visible-md,td.visible-md{display:table-cell !important}}@media (min-width:1200px){.visible-lg{display:block !important}table.visible-lg{display:table}tr.visible-lg{display:table-row !important}th.visible-lg,td.visible-lg{display:table-cell !important}}@media (max-width:767px){.hidden-xs{display:none !important}}@media (min-width:768px) and (max-width:991px){.hidden-sm{display:none !important}}@media (min-width:992px) and (max-width:1199px){.hidden-md{display:none !important}}@media (min-width:1200px){.hidden-lg{display:none !important}}.visible-print{display:none !important}@media print{.visible-print{display:block !important}table.visible-print{display:table}tr.visible-print{display:table-row !important}th.visible-print,td.visible-print{display:table-cell !important}}@media print{.hidden-print{display:none !important}} \ No newline at end of file diff --git a/sut-market/market-web/src/main/resources/static/css/market-styles.css b/sut-market/market-web/src/main/resources/static/css/market-styles.css new file mode 100644 index 0000000..c9162c4 --- /dev/null +++ b/sut-market/market-web/src/main/resources/static/css/market-styles.css @@ -0,0 +1,661 @@ +/* +#5289b3 +#70a829 +#f9c861 +http://getbootstrap.com/customize/?id=9227663 +*/ + +* { + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; +} + +body, html { + height: 100%; + min-height: 100%; + width: 100%; + min-width: 100%; +} + +body { + background-color: #999; +} + +body, +h1, +h2, +h3, +h4, +h5, +h6, +.h1, +.h2, +.h3, +.h4, +.h5, +.h6, +button, +a.btn { + font-family: Arial, Helvetica, sans-serif!important; + letter-spacing: 0.003em; +} + +h1, +h2, +h3, +span.btn, +.control-label { + letter-spacing: 0.015em!important; + word-spacing: 0.08em!important; + font-weight: normal; +} + +ul.discharged > li, +ol.discharged > li { + padding-bottom: 0.5em; +} + +a, a:hover { + text-decoration: none; + color: #777; +} +a:hover, +a:focus { + text-decoration: underline; +} + +h1 { + font-size: 2.0em!important; + margin: 1.3em 0 0.9em 0!important; +} + +h2 { + font-size: 1.5em!important; + margin: 1.5em 0 1em 0!important; +} + +h3 { + font-weight: normal; +} + +h5 { + font-weight: bold!important; +} + +.table-marked > thead > tr > th { + font-weight: normal!important; +} + +.custom-alert-box { + display: inline-block; + width: auto; + max-width: 180px; +} + +.alert { + padding: 3px 8px; + margin-bottom: 0; + font-size: 0.9em; + width: auto!important; +} + +.btn-default .badge { + background-color: #558bb4; + font-size: 0.9em; +} + +.pure-img-responsive { + max-width: 100%; + height: auto; +} + +.carousel-wrapper { + padding-top: 10px; +} + +.modal-sm { + width: 350px; +} + +.popover-medium { + max-width: 350px; +} + +code { + color: #3f6887; + background-color: #eaf2f7; +} + +.custom-pagination > li > a, +.custom-pagination > li > span { + border: 0px; + color: #777; +} +.custom-pagination > .active > span { + background: none; + border: 1px solid #777; + color: #777; + margin-top: -1px; + border-radius: 0; +} +.pagination > li > a:hover, +.pagination > li > a:focus { + text-decoration: underline; +} +.pagination > li > a:hover, +.pagination > li > a:focus, +.pagination > li > span:hover, +.pagination > li > span { + color: #777; + background-color: white; +} +.pagination > li.disabled > a { + text-decoration: none; +} +.pagination > .active > a, +.pagination > .active > span, +.pagination > .active > a:hover, +.pagination > .active > span:hover, +.pagination > .active > a:focus, +.pagination > .active > span:focus { + z-index: 2; + color: #777; + background-color: white; + border-color: #777; + cursor: default; + text-decoration: none; + border-radius: 0!important; +} + +.dropdown-menu { + min-width: 50px; +} + +.bs-callout { + border-left: 3px solid; + margin: 10px 0; + padding: 5px 15px; +} +.bs-callout-success { + color: rgb(43, 84, 44); + border-color: rgb(60, 118, 61); + background-color: rgb(223, 240, 216); +} +.bs-callout-danger { + color: rgb(169, 68, 66); + border-color: rgb(217, 83, 79); + background-color: rgb(253, 247, 247); +} + + +/*------------------------------ Страница категории */ + +.product-item { + padding: 0 0 10px 0; + margin-top: 0!important; +} +.product-price { + min-width: 120px; + text-align: right; + padding-bottom: 10px; +} +.price-block { + min-width: 120px; + text-align: right; + padding: 0 0 10px 15px; +} +.product-label { + font-size: 1.4em!important; +} +.product-description { + padding-top: 10px; + font-size: 0.95em; +} +.region-description { + margin-bottom: 30px; +} +.region-image { + margin: 0 0 10px 15px; +} + +/*------------------------------ LAYOUT CSS */ +#layout { + margin: 0; + padding: 0; + height: 100%; + width: 100%; +} +#layout, #backend-layout { + display: table; +} +.row { + display: table-row; + &:before, &:after { + content: none; + } + height: auto; + min-height: 100%; /* fallback for browsers that doesn't support "vh" */ + min-height: 100vh; /* vh == 1% of viewport height */ + margin: 0; +} +#backend-layout { + width: 800px; + margin: 0 auto 0 auto; + height: 100%; + width: 100%; + text-align: left; +} +#sidebar, #content { + display: table-cell; +} +#sidebar { + padding: 0; + margin: 0; + background-color: rgb(61, 79, 93); + color: #fff; + vertical-align: top; + position: absolute; + right: 0; + left: 0; + height: 210px; + text-align: center; + padding-top: 25px; +} +body.frontend { + background-color: rgb(61, 79, 93); +} +#content { + height: 100%; + /*height: auto; + min-height: 100%; + min-height: 100vh;*/ + margin-top: 210px; + padding: 0; + vertical-align: top; + background-color: white; +} +.content-column { + height: 100%; + width: 100%; + background-color: white; +} +.posts { + padding: 2em 1em 0; +} + +/*------------------------------ Меню - начало */ + +.header { + margin-bottom: 30px; +} +.header-regions { + width: 100%; + clear: both; + bottom: 0; + position: absolute; +} +.region-item { + display: inline-block; + padding: 8px 8px; + margin: 0 2px; + border-top-left-radius: 8px; + border-top-right-radius: 8px; + letter-spacing: 0.04em; +} +.region-item-active { + background-color: white; + color: black; +} +.region-item-title { + color: white; + display: none; +} +.regionText { + line-height: 10px; + font-size: small; +} +.region-item a { + color: white; +} +.region-item-active a { +} +/*------------------------------ Меню - завершение */ + + +.brand-title, +.brand-tagline { + margin: 0 0 10px 0; +} +.brand-title, +.brand-title a { + color: white; + font-size: 1.5em; + letter-spacing: 0.04em; + word-spacing: 0.15em; + text-transform: uppercase; +} +.brand-title a:hover { + color: white; + text-decoration: underline; +} +.brand-tagline { + font-size: 1.3em; + letter-spacing: 0.03em; + color: rgb(176, 202, 219); +} + +.nav-list { + margin: 0; + padding: 0; + list-style: none; +} +.nav-item { + display: inline-block; + *display: inline; + zoom: 1; +} +.nav-item a { + background: transparent; + border: 2px solid rgb(176, 202, 219); + color: #fff; + margin-top: 1em; + letter-spacing: 0.05em; + text-transform: uppercase; + font-size: 85%; +} +.nav-item a:hover, +.nav-item a:focus { + border: 2px solid rgb(61, 146, 201); + text-decoration: none; +} + +.content-subhead { + text-transform: uppercase; + color: #aaa; + border-bottom: 1px solid #eee; + padding: 0.4em 0; + font-size: 80%; + font-weight: 500; + letter-spacing: 0.1em; +} + +.post { + padding-bottom: 2em; +} +.post-title { + font-size: 2em; + color: #222; + margin-bottom: 0.2em; +} +.post-avatar { + border-radius: 50px; + float: right; + margin-left: 1em; +} +.post-description { + font-family: Georgia, "Cambria", serif; + color: #444; + line-height: 1.8em; +} +.post-meta { + color: #999; + font-size: 90%; + margin: 0; +} + +.post-region { + margin: 0 0.1em; + padding: 0.3em 1em; + color: #fff; + background: #999; + font-size: 80%; +} +.post-region-design { + background: #5aba59; +} +.post-region-pure { + background: #4d85d1; +} +.post-region-yui { + background: #8156a7; +} +.post-region-js { + background: #df2d4f; +} + +.post-images { + margin: 1em 0; +} +.post-image-meta { + margin-top: -3.5em; + margin-left: 1em; + color: #fff; + text-shadow: 0 1px 1px #333; +} + +.footer { + text-align: center; + padding: 30px 0 15px 0; +} +.footer a { + font-size: 0.9em; +} +.footer li { + padding: 0 10px; +} + + +/*------------------------------ Index page region-boxes */ + +#indexRightColumn { + text-align: left; + width: 100%; + float: left; +} +.region-preview-container { + margin: 10px -11px 0 -11px; +} +.regionBox { + height: 192px; + width: 192px; + margin: 10px; + float: left; + display: inline-block; +} +.regionLabel { + position: absolute; + opacity: 0.4; + height: 53px; + width: 190px; + margin: 138px 0 0 1px; +} +.regionLabelText { + color: white; + position: absolute; + line-height: 20px; + margin: 141px 0 0 7px; +} +.regionTitle { + font-size: large; + text-transform: uppercase; + line-height: 26px; +} +.regionImage { + padding: 1px; +} +/* Safari - disables underline for region boxes on index page */ +.regionBox a { text-decoration: none } + +/*------------------------------ Color schemes */ + +.scheme-purple {background-color: #990033;} +.scheme-grey {background-color: #536370;} +.scheme-black {background-color: black;} +.scheme-brown {background-color: #330000;} +.scheme-yellow {background-color: #ffcc33;} +.scheme-green {background-color: #336600;} +.scheme-blue {background-color: #003366;} +.scheme-red {background-color: #990000;} + +.border-purple {border-color: #990033;} +.border-grey {border-color: #777;} +.border-black {border-color: black;} +.border-brown {border-color: #330000;} +.border-yellow {border-color: #ffcc33;} +.border-green {border-color: #336600;} +.border-blue {border-color: #003366;} +.border-red {border-color: #990000;} + + +/*------------------------------ Carousel override */ + +.carousel-control { + font-size: xx-large; +} +.glyphicon-chevron-left:before { + content: "<"; +} +.glyphicon-chevron-right:before { + content: ">"; +} +.carousel-control.left, +.carousel-control.right { + background-image: none; +} +.carousel-indicators { + bottom: 0; + font-size: xx-large; +} +.carousel-caption { + top: 0; + left: 0; + right: 0; + padding: 10px 0 0 15px; + text-align: left; +} + +/*------------------------------ Navigation override */ + +.navbar { + border-width: 0 0 1px 0; + border-radius: 0; +} +.table-small-text > thead > tr > th, +.table-small-text > tbody > tr > th, +.table-small-text > tfoot > tr > th, +.table-small-text > thead > tr > td, +.table-small-text > tbody > tr > td, +.table-small-text > tfoot > tr > td { + font-size: smaller; + padding: 5px; +} + +.table > thead > tr > th, +.table > tbody > tr > th, +.table > tfoot > tr > th { + letter-spacing: 0.02em; + word-spacing: 0.08em; +} + +tr.nopadding > td { + padding: 0!important; + border: 0!important; +} + +.help-block { + margin-bottom: 0; + font-size: smaller; + padding-left: 4px; +} + + +#regionLeftColumn { + float: right; + margin-top: 25px; + text-align: right; +} + +.product-unit { + padding: 20px 0; +} + +.headerWidget { + padding: 10px 10px 0 10px; + vertical-align: middle; + height: 20px; +} + +#widgetCart { + height: 25px; + letter-spacing: normal; + float: left; +} + +#widgetAccount { + height: 25px; + letter-spacing: normal; + float: right; +} + +#loginForm { + padding: 40px 0; +} + + +@media (min-width: 48em) { + + #sidebar, #content { + position: relative; + float: none; + } + #content { + background-color: rgb(61, 79, 93); + } + #backend-layout { + max-width: 850px; + } + .header { + min-width: 200px; + margin: 17% 1.5em 15% 0; + text-align: right; + } + #frontend-content { + max-width: 700px; + } + #backend-content { + max-width: 800px; + display: table-cell; + } + .posts { + padding: 0 3em 0; + } + + .header-regions { + position: relative; + width: auto; + float: right; + text-align: right; + } + .region-item { + display: block; + text-align: left; + padding: 10px 15px 10px 10px; + margin: 8px 0; + border-top-right-radius: 0; + border-bottom-left-radius: 8px; + } + .region-item-active { + background-color: white; + color: black; + margin-left: -20px; + } + .region-item-title { + text-align: right; + font-size: 1.2em; + } + .regionText { + line-height: 20px; + font-size: 1.3em; + } +} diff --git a/sut-market/market-web/src/main/resources/static/fonts/glyphicons-halflings-regular.eot b/sut-market/market-web/src/main/resources/static/fonts/glyphicons-halflings-regular.eot new file mode 100644 index 0000000..423bd5d Binary files /dev/null and b/sut-market/market-web/src/main/resources/static/fonts/glyphicons-halflings-regular.eot differ diff --git a/sut-market/market-web/src/main/resources/static/fonts/glyphicons-halflings-regular.svg b/sut-market/market-web/src/main/resources/static/fonts/glyphicons-halflings-regular.svg new file mode 100644 index 0000000..4469488 --- /dev/null +++ b/sut-market/market-web/src/main/resources/static/fonts/glyphicons-halflings-regular.svg @@ -0,0 +1,229 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/sut-market/market-web/src/main/resources/static/fonts/glyphicons-halflings-regular.ttf b/sut-market/market-web/src/main/resources/static/fonts/glyphicons-halflings-regular.ttf new file mode 100644 index 0000000..a498ef4 Binary files /dev/null and b/sut-market/market-web/src/main/resources/static/fonts/glyphicons-halflings-regular.ttf differ diff --git a/sut-market/market-web/src/main/resources/static/fonts/glyphicons-halflings-regular.woff b/sut-market/market-web/src/main/resources/static/fonts/glyphicons-halflings-regular.woff new file mode 100644 index 0000000..d83c539 Binary files /dev/null and b/sut-market/market-web/src/main/resources/static/fonts/glyphicons-halflings-regular.woff differ diff --git a/sut-market/market-web/src/main/resources/static/img/carousel/Balvenie-orig.jpg b/sut-market/market-web/src/main/resources/static/img/carousel/Balvenie-orig.jpg new file mode 100644 index 0000000..e8f85e9 Binary files /dev/null and b/sut-market/market-web/src/main/resources/static/img/carousel/Balvenie-orig.jpg differ diff --git a/sut-market/market-web/src/main/resources/static/img/carousel/Balvenie.jpg b/sut-market/market-web/src/main/resources/static/img/carousel/Balvenie.jpg new file mode 100644 index 0000000..d93cd78 Binary files /dev/null and b/sut-market/market-web/src/main/resources/static/img/carousel/Balvenie.jpg differ diff --git a/sut-market/market-web/src/main/resources/static/img/carousel/Laphroaig-orig.jpg b/sut-market/market-web/src/main/resources/static/img/carousel/Laphroaig-orig.jpg new file mode 100644 index 0000000..d4e9248 Binary files /dev/null and b/sut-market/market-web/src/main/resources/static/img/carousel/Laphroaig-orig.jpg differ diff --git a/sut-market/market-web/src/main/resources/static/img/carousel/Laphroaig.jpg b/sut-market/market-web/src/main/resources/static/img/carousel/Laphroaig.jpg new file mode 100644 index 0000000..2fcc1f9 Binary files /dev/null and b/sut-market/market-web/src/main/resources/static/img/carousel/Laphroaig.jpg differ diff --git a/sut-market/market-web/src/main/resources/static/img/checkout-flow.png b/sut-market/market-web/src/main/resources/static/img/checkout-flow.png new file mode 100644 index 0000000..01e1496 Binary files /dev/null and b/sut-market/market-web/src/main/resources/static/img/checkout-flow.png differ diff --git a/sut-market/market-web/src/main/resources/static/img/database-model.png b/sut-market/market-web/src/main/resources/static/img/database-model.png new file mode 100644 index 0000000..b3a0e3a Binary files /dev/null and b/sut-market/market-web/src/main/resources/static/img/database-model.png differ diff --git a/sut-market/market-web/src/main/resources/static/img/products/Ardbeg/Ten.jpg b/sut-market/market-web/src/main/resources/static/img/products/Ardbeg/Ten.jpg new file mode 100644 index 0000000..195bde1 Binary files /dev/null and b/sut-market/market-web/src/main/resources/static/img/products/Ardbeg/Ten.jpg differ diff --git a/sut-market/market-web/src/main/resources/static/img/products/Ardbeg/Uigeadail.jpg b/sut-market/market-web/src/main/resources/static/img/products/Ardbeg/Uigeadail.jpg new file mode 100644 index 0000000..9c52665 Binary files /dev/null and b/sut-market/market-web/src/main/resources/static/img/products/Ardbeg/Uigeadail.jpg differ diff --git a/sut-market/market-web/src/main/resources/static/img/products/Balvenie/12 y.o. Doublewood.jpg b/sut-market/market-web/src/main/resources/static/img/products/Balvenie/12 y.o. Doublewood.jpg new file mode 100644 index 0000000..b15cc08 Binary files /dev/null and b/sut-market/market-web/src/main/resources/static/img/products/Balvenie/12 y.o. Doublewood.jpg differ diff --git a/sut-market/market-web/src/main/resources/static/img/products/Caol Ila/12 y.o..jpg b/sut-market/market-web/src/main/resources/static/img/products/Caol Ila/12 y.o..jpg new file mode 100644 index 0000000..866ec2f Binary files /dev/null and b/sut-market/market-web/src/main/resources/static/img/products/Caol Ila/12 y.o..jpg differ diff --git a/sut-market/market-web/src/main/resources/static/img/products/Dalwhinnie/15 y.o..jpg b/sut-market/market-web/src/main/resources/static/img/products/Dalwhinnie/15 y.o..jpg new file mode 100644 index 0000000..8194484 Binary files /dev/null and b/sut-market/market-web/src/main/resources/static/img/products/Dalwhinnie/15 y.o..jpg differ diff --git a/sut-market/market-web/src/main/resources/static/img/products/Glenkinchie/12 y.o..jpg b/sut-market/market-web/src/main/resources/static/img/products/Glenkinchie/12 y.o..jpg new file mode 100644 index 0000000..825ccfa Binary files /dev/null and b/sut-market/market-web/src/main/resources/static/img/products/Glenkinchie/12 y.o..jpg differ diff --git a/sut-market/market-web/src/main/resources/static/img/products/Lagavulin/16 y.o..jpg b/sut-market/market-web/src/main/resources/static/img/products/Lagavulin/16 y.o..jpg new file mode 100644 index 0000000..759f852 Binary files /dev/null and b/sut-market/market-web/src/main/resources/static/img/products/Lagavulin/16 y.o..jpg differ diff --git a/sut-market/market-web/src/main/resources/static/img/products/Laphroaig/Quarter Cask.jpg b/sut-market/market-web/src/main/resources/static/img/products/Laphroaig/Quarter Cask.jpg new file mode 100644 index 0000000..b8a3e84 Binary files /dev/null and b/sut-market/market-web/src/main/resources/static/img/products/Laphroaig/Quarter Cask.jpg differ diff --git a/sut-market/market-web/src/main/resources/static/img/products/Springbank/12 y.o. Cask Strength Batch 6.jpg b/sut-market/market-web/src/main/resources/static/img/products/Springbank/12 y.o. Cask Strength Batch 6.jpg new file mode 100644 index 0000000..4b0c3bd Binary files /dev/null and b/sut-market/market-web/src/main/resources/static/img/products/Springbank/12 y.o. Cask Strength Batch 6.jpg differ diff --git a/sut-market/market-web/src/main/resources/static/img/products/Springbank/18 y.o..jpg b/sut-market/market-web/src/main/resources/static/img/products/Springbank/18 y.o..jpg new file mode 100644 index 0000000..606123e Binary files /dev/null and b/sut-market/market-web/src/main/resources/static/img/products/Springbank/18 y.o..jpg differ diff --git a/sut-market/market-web/src/main/resources/static/img/products/Talisker/10 y.o..jpg b/sut-market/market-web/src/main/resources/static/img/products/Talisker/10 y.o..jpg new file mode 100644 index 0000000..32e2d22 Binary files /dev/null and b/sut-market/market-web/src/main/resources/static/img/products/Talisker/10 y.o..jpg differ diff --git a/sut-market/market-web/src/main/resources/static/img/products/default-whisky.jpg b/sut-market/market-web/src/main/resources/static/img/products/default-whisky.jpg new file mode 100644 index 0000000..671abe4 Binary files /dev/null and b/sut-market/market-web/src/main/resources/static/img/products/default-whisky.jpg differ diff --git a/sut-market/market-web/src/main/resources/static/img/regions/Campbeltown-preview.jpg b/sut-market/market-web/src/main/resources/static/img/regions/Campbeltown-preview.jpg new file mode 100644 index 0000000..a4f587e Binary files /dev/null and b/sut-market/market-web/src/main/resources/static/img/regions/Campbeltown-preview.jpg differ diff --git a/sut-market/market-web/src/main/resources/static/img/regions/Campbeltown.jpg b/sut-market/market-web/src/main/resources/static/img/regions/Campbeltown.jpg new file mode 100644 index 0000000..ed258db Binary files /dev/null and b/sut-market/market-web/src/main/resources/static/img/regions/Campbeltown.jpg differ diff --git a/sut-market/market-web/src/main/resources/static/img/regions/Highland-preview.jpg b/sut-market/market-web/src/main/resources/static/img/regions/Highland-preview.jpg new file mode 100644 index 0000000..f5b39e1 Binary files /dev/null and b/sut-market/market-web/src/main/resources/static/img/regions/Highland-preview.jpg differ diff --git a/sut-market/market-web/src/main/resources/static/img/regions/Highland.jpg b/sut-market/market-web/src/main/resources/static/img/regions/Highland.jpg new file mode 100644 index 0000000..b948096 Binary files /dev/null and b/sut-market/market-web/src/main/resources/static/img/regions/Highland.jpg differ diff --git a/sut-market/market-web/src/main/resources/static/img/regions/Island-preview.jpg b/sut-market/market-web/src/main/resources/static/img/regions/Island-preview.jpg new file mode 100644 index 0000000..059f171 Binary files /dev/null and b/sut-market/market-web/src/main/resources/static/img/regions/Island-preview.jpg differ diff --git a/sut-market/market-web/src/main/resources/static/img/regions/Island.jpg b/sut-market/market-web/src/main/resources/static/img/regions/Island.jpg new file mode 100644 index 0000000..549ad32 Binary files /dev/null and b/sut-market/market-web/src/main/resources/static/img/regions/Island.jpg differ diff --git a/sut-market/market-web/src/main/resources/static/img/regions/Islay-preview.jpg b/sut-market/market-web/src/main/resources/static/img/regions/Islay-preview.jpg new file mode 100644 index 0000000..8903dc7 Binary files /dev/null and b/sut-market/market-web/src/main/resources/static/img/regions/Islay-preview.jpg differ diff --git a/sut-market/market-web/src/main/resources/static/img/regions/Islay.jpg b/sut-market/market-web/src/main/resources/static/img/regions/Islay.jpg new file mode 100644 index 0000000..3ef158a Binary files /dev/null and b/sut-market/market-web/src/main/resources/static/img/regions/Islay.jpg differ diff --git a/sut-market/market-web/src/main/resources/static/img/regions/Lowland-preview.jpg b/sut-market/market-web/src/main/resources/static/img/regions/Lowland-preview.jpg new file mode 100644 index 0000000..9ccdc7f Binary files /dev/null and b/sut-market/market-web/src/main/resources/static/img/regions/Lowland-preview.jpg differ diff --git a/sut-market/market-web/src/main/resources/static/img/regions/Lowland.jpg b/sut-market/market-web/src/main/resources/static/img/regions/Lowland.jpg new file mode 100644 index 0000000..6654c38 Binary files /dev/null and b/sut-market/market-web/src/main/resources/static/img/regions/Lowland.jpg differ diff --git a/sut-market/market-web/src/main/resources/static/img/regions/Speyside-preview.jpg b/sut-market/market-web/src/main/resources/static/img/regions/Speyside-preview.jpg new file mode 100644 index 0000000..b45d356 Binary files /dev/null and b/sut-market/market-web/src/main/resources/static/img/regions/Speyside-preview.jpg differ diff --git a/sut-market/market-web/src/main/resources/static/img/regions/Speyside.jpg b/sut-market/market-web/src/main/resources/static/img/regions/Speyside.jpg new file mode 100644 index 0000000..5b4f1be Binary files /dev/null and b/sut-market/market-web/src/main/resources/static/img/regions/Speyside.jpg differ diff --git a/sut-market/market-web/src/main/resources/static/img/regions/src/Campbeltown.jpg b/sut-market/market-web/src/main/resources/static/img/regions/src/Campbeltown.jpg new file mode 100644 index 0000000..ed258db Binary files /dev/null and b/sut-market/market-web/src/main/resources/static/img/regions/src/Campbeltown.jpg differ diff --git a/sut-market/market-web/src/main/resources/static/img/regions/src/Highland_.jpg b/sut-market/market-web/src/main/resources/static/img/regions/src/Highland_.jpg new file mode 100644 index 0000000..12595cf Binary files /dev/null and b/sut-market/market-web/src/main/resources/static/img/regions/src/Highland_.jpg differ diff --git a/sut-market/market-web/src/main/resources/static/img/regions/src/Island_.jpeg b/sut-market/market-web/src/main/resources/static/img/regions/src/Island_.jpeg new file mode 100644 index 0000000..aab82f7 Binary files /dev/null and b/sut-market/market-web/src/main/resources/static/img/regions/src/Island_.jpeg differ diff --git a/sut-market/market-web/src/main/resources/static/img/regions/src/Islay_.jpg b/sut-market/market-web/src/main/resources/static/img/regions/src/Islay_.jpg new file mode 100644 index 0000000..d4e9248 Binary files /dev/null and b/sut-market/market-web/src/main/resources/static/img/regions/src/Islay_.jpg differ diff --git a/sut-market/market-web/src/main/resources/static/img/regions/src/Lowland_.jpg b/sut-market/market-web/src/main/resources/static/img/regions/src/Lowland_.jpg new file mode 100644 index 0000000..82f57ff Binary files /dev/null and b/sut-market/market-web/src/main/resources/static/img/regions/src/Lowland_.jpg differ diff --git a/sut-market/market-web/src/main/resources/static/img/regions/src/Speyside.jpg b/sut-market/market-web/src/main/resources/static/img/regions/src/Speyside.jpg new file mode 100644 index 0000000..5b4f1be Binary files /dev/null and b/sut-market/market-web/src/main/resources/static/img/regions/src/Speyside.jpg differ diff --git a/sut-market/market-web/src/main/resources/static/js/bootstrap.min.js b/sut-market/market-web/src/main/resources/static/js/bootstrap.min.js new file mode 100644 index 0000000..f8bc01a --- /dev/null +++ b/sut-market/market-web/src/main/resources/static/js/bootstrap.min.js @@ -0,0 +1,7 @@ +/*! + * Bootstrap v3.1.1 (http://getbootstrap.com) + * Copyright 2011-2014 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + */ + ++function(a){"use strict";var b='[data-dismiss="alert"]',c=function(c){a(c).on("click",b,this.close)};c.prototype.close=function(b){function f(){e.trigger("closed.bs.alert").remove()}var c=a(this),d=c.attr("data-target");d||(d=c.attr("href"),d=d&&d.replace(/.*(?=#[^\s]*$)/,""));var e=a(d);b&&b.preventDefault(),e.length||(e=c.hasClass("alert")?c:c.parent()),e.trigger(b=a.Event("close.bs.alert"));if(b.isDefaultPrevented())return;e.removeClass("in"),a.support.transition&&e.hasClass("fade")?e.one(a.support.transition.end,f).emulateTransitionEnd(150):f()};var d=a.fn.alert;a.fn.alert=function(b){return this.each(function(){var d=a(this),e=d.data("bs.alert");e||d.data("bs.alert",e=new c(this)),typeof b=="string"&&e[b].call(d)})},a.fn.alert.Constructor=c,a.fn.alert.noConflict=function(){return a.fn.alert=d,this},a(document).on("click.bs.alert.data-api",b,c.prototype.close)}(jQuery),+function(a){"use strict";var b=function(c,d){this.$element=a(c),this.options=a.extend({},b.DEFAULTS,d),this.isLoading=!1};b.DEFAULTS={loadingText:"loading..."},b.prototype.setState=function(b){var c="disabled",d=this.$element,e=d.is("input")?"val":"html",f=d.data();b+="Text",f.resetText||d.data("resetText",d[e]()),d[e](f[b]||this.options[b]),setTimeout(a.proxy(function(){b=="loadingText"?(this.isLoading=!0,d.addClass(c).attr(c,c)):this.isLoading&&(this.isLoading=!1,d.removeClass(c).removeAttr(c))},this),0)},b.prototype.toggle=function(){var a=!0,b=this.$element.closest('[data-toggle="buttons"]');if(b.length){var c=this.$element.find("input");c.prop("type")=="radio"&&(c.prop("checked")&&this.$element.hasClass("active")?a=!1:b.find(".active").removeClass("active")),a&&c.prop("checked",!this.$element.hasClass("active")).trigger("change")}a&&this.$element.toggleClass("active")};var c=a.fn.button;a.fn.button=function(c){return this.each(function(){var d=a(this),e=d.data("bs.button"),f=typeof c=="object"&&c;e||d.data("bs.button",e=new b(this,f)),c=="toggle"?e.toggle():c&&e.setState(c)})},a.fn.button.Constructor=b,a.fn.button.noConflict=function(){return a.fn.button=c,this},a(document).on("click.bs.button.data-api","[data-toggle^=button]",function(b){var c=a(b.target);c.hasClass("btn")||(c=c.closest(".btn")),c.button("toggle"),b.preventDefault()})}(jQuery),+function(a){"use strict";var b=function(b,c){this.$element=a(b),this.$indicators=this.$element.find(".carousel-indicators"),this.options=c,this.paused=this.sliding=this.interval=this.$active=this.$items=null,this.options.pause=="hover"&&this.$element.on("mouseenter",a.proxy(this.pause,this)).on("mouseleave",a.proxy(this.cycle,this))};b.DEFAULTS={interval:5e3,pause:"hover",wrap:!0},b.prototype.cycle=function(b){return b||(this.paused=!1),this.interval&&clearInterval(this.interval),this.options.interval&&!this.paused&&(this.interval=setInterval(a.proxy(this.next,this),this.options.interval)),this},b.prototype.getActiveIndex=function(){return this.$active=this.$element.find(".item.active"),this.$items=this.$active.parent().children(),this.$items.index(this.$active)},b.prototype.to=function(b){var c=this,d=this.getActiveIndex();if(b>this.$items.length-1||b<0)return;return this.sliding?this.$element.one("slid.bs.carousel",function(){c.to(b)}):d==b?this.pause().cycle():this.slide(b>d?"next":"prev",a(this.$items[b]))},b.prototype.pause=function(b){return b||(this.paused=!0),this.$element.find(".next, .prev").length&&a.support.transition&&(this.$element.trigger(a.support.transition.end),this.cycle(!0)),this.interval=clearInterval(this.interval),this},b.prototype.next=function(){if(this.sliding)return;return this.slide("next")},b.prototype.prev=function(){if(this.sliding)return;return this.slide("prev")},b.prototype.slide=function(b,c){var d=this.$element.find(".item.active"),e=c||d[b](),f=this.interval,g=b=="next"?"left":"right",h=b=="next"?"first":"last",i=this;if(!e.length){if(!this.options.wrap)return;e=this.$element.find(".item")[h]()}if(e.hasClass("active"))return this.sliding=!1;var j=a.Event("slide.bs.carousel",{relatedTarget:e[0],direction:g});this.$element.trigger(j);if(j.isDefaultPrevented())return;return this.sliding=!0,f&&this.pause(),this.$indicators.length&&(this.$indicators.find(".active").removeClass("active"),this.$element.one("slid.bs.carousel",function(){var b=a(i.$indicators.children()[i.getActiveIndex()]);b&&b.addClass("active")})),a.support.transition&&this.$element.hasClass("slide")?(e.addClass(b),e[0].offsetWidth,d.addClass(g),e.addClass(g),d.one(a.support.transition.end,function(){e.removeClass([b,g].join(" ")).addClass("active"),d.removeClass(["active",g].join(" ")),i.sliding=!1,setTimeout(function(){i.$element.trigger("slid.bs.carousel")},0)}).emulateTransitionEnd(d.css("transition-duration").slice(0,-1)*1e3)):(d.removeClass("active"),e.addClass("active"),this.sliding=!1,this.$element.trigger("slid.bs.carousel")),f&&this.cycle(),this};var c=a.fn.carousel;a.fn.carousel=function(c){return this.each(function(){var d=a(this),e=d.data("bs.carousel"),f=a.extend({},b.DEFAULTS,d.data(),typeof c=="object"&&c),g=typeof c=="string"?c:f.slide;e||d.data("bs.carousel",e=new b(this,f)),typeof c=="number"?e.to(c):g?e[g]():f.interval&&e.pause().cycle()})},a.fn.carousel.Constructor=b,a.fn.carousel.noConflict=function(){return a.fn.carousel=c,this},a(document).on("click.bs.carousel.data-api","[data-slide], [data-slide-to]",function(b){var c=a(this),d,e=a(c.attr("data-target")||(d=c.attr("href"))&&d.replace(/.*(?=#[^\s]+$)/,"")),f=a.extend({},e.data(),c.data()),g=c.attr("data-slide-to");g&&(f.interval=!1),e.carousel(f),(g=c.attr("data-slide-to"))&&e.data("bs.carousel").to(g),b.preventDefault()}),a(window).on("load",function(){a('[data-ride="carousel"]').each(function(){var b=a(this);b.carousel(b.data())})})}(jQuery),+function(a){function e(d){a(b).remove(),a(c).each(function(){var b=f(a(this)),c={relatedTarget:this};if(!b.hasClass("open"))return;b.trigger(d=a.Event("hide.bs.dropdown",c));if(d.isDefaultPrevented())return;b.removeClass("open").trigger("hidden.bs.dropdown",c)})}function f(b){var c=b.attr("data-target");c||(c=b.attr("href"),c=c&&/#[A-Za-z]/.test(c)&&c.replace(/.*(?=#[^\s]*$)/,""));var d=c&&a(c);return d&&d.length?d:b.parent()}"use strict";var b=".dropdown-backdrop",c="[data-toggle=dropdown]",d=function(b){a(b).on("click.bs.dropdown",this.toggle)};d.prototype.toggle=function(b){var c=a(this);if(c.is(".disabled, :disabled"))return;var d=f(c),g=d.hasClass("open");e();if(!g){"ontouchstart"in document.documentElement&&!d.closest(".navbar-nav").length&&a('

'}),b.prototype=a.extend({},a.fn.tooltip.Constructor.prototype),b.prototype.constructor=b,b.prototype.getDefaults=function(){return b.DEFAULTS},b.prototype.setContent=function(){var a=this.tip(),b=this.getTitle(),c=this.getContent();a.find(".popover-title")[this.options.html?"html":"text"](b),a.find(".popover-content")[this.options.html?typeof c=="string"?"html":"append":"text"](c),a.removeClass("fade top bottom left right in"),a.find(".popover-title").html()||a.find(".popover-title").hide()},b.prototype.hasContent=function(){return this.getTitle()||this.getContent()},b.prototype.getContent=function(){var a=this.$element,b=this.options;return a.attr("data-content")||(typeof b.content=="function"?b.content.call(a[0]):b.content)},b.prototype.arrow=function(){return this.$arrow=this.$arrow||this.tip().find(".arrow")},b.prototype.tip=function(){return this.$tip||(this.$tip=a(this.options.template)),this.$tip};var c=a.fn.popover;a.fn.popover=function(c){return this.each(function(){var d=a(this),e=d.data("bs.popover"),f=typeof c=="object"&&c;if(!e&&c=="destroy")return;e||d.data("bs.popover",e=new b(this,f)),typeof c=="string"&&e[c]()})},a.fn.popover.Constructor=b,a.fn.popover.noConflict=function(){return a.fn.popover=c,this}}(jQuery),+function(a){"use strict";var b=function(c,d){this.options=a.extend({},b.DEFAULTS,d),this.$window=a(window).on("scroll.bs.affix.data-api",a.proxy(this.checkPosition,this)).on("click.bs.affix.data-api",a.proxy(this.checkPositionWithEventLoop,this)),this.$element=a(c),this.affixed=this.unpin=this.pinnedOffset=null,this.checkPosition()};b.RESET="affix affix-top affix-bottom",b.DEFAULTS={offset:0},b.prototype.getPinnedOffset=function(){if(this.pinnedOffset)return this.pinnedOffset;this.$element.removeClass(b.RESET).addClass("affix");var a=this.$window.scrollTop(),c=this.$element.offset();return this.pinnedOffset=c.top-a},b.prototype.checkPositionWithEventLoop=function(){setTimeout(a.proxy(this.checkPosition,this),1)},b.prototype.checkPosition=function(){if(!this.$element.is(":visible"))return;var c=a(document).height(),d=this.$window.scrollTop(),e=this.$element.offset(),f=this.options.offset,g=f.top,h=f.bottom;this.affixed=="top"&&(e.top+=d),typeof f!="object"&&(h=g=f),typeof g=="function"&&(g=f.top(this.$element)),typeof h=="function"&&(h=f.bottom(this.$element));var i=this.unpin!=null&&d+this.unpin<=e.top?!1:h!=null&&e.top+this.$element.height()>=c-h?"bottom":g!=null&&d<=g?"top":!1;if(this.affixed===i)return;this.unpin&&this.$element.css("top","");var j="affix"+(i?"-"+i:""),k=a.Event(j+".bs.affix");this.$element.trigger(k);if(k.isDefaultPrevented())return;this.affixed=i,this.unpin=i=="bottom"?this.getPinnedOffset():null,this.$element.removeClass(b.RESET).addClass(j).trigger(a.Event(j.replace("affix","affixed"))),i=="bottom"&&this.$element.offset({top:c-h-this.$element.height()})};var c=a.fn.affix;a.fn.affix=function(c){return this.each(function(){var d=a(this),e=d.data("bs.affix"),f=typeof c=="object"&&c;e||d.data("bs.affix",e=new b(this,f)),typeof c=="string"&&e[c]()})},a.fn.affix.Constructor=b,a.fn.affix.noConflict=function(){return a.fn.affix=c,this},a(window).on("load",function(){a('[data-spy="affix"]').each(function(){var b=a(this),c=b.data();c.offset=c.offset||{},c.offsetBottom&&(c.offset.bottom=c.offsetBottom),c.offsetTop&&(c.offset.top=c.offsetTop),b.affix(c)})})}(jQuery),+function(a){"use strict";var b=function(c,d){this.$element=a(c),this.options=a.extend({},b.DEFAULTS,d),this.transitioning=null,this.options.parent&&(this.$parent=a(this.options.parent)),this.options.toggle&&this.toggle()};b.DEFAULTS={toggle:!0},b.prototype.dimension=function(){var a=this.$element.hasClass("width");return a?"width":"height"},b.prototype.show=function(){if(this.transitioning||this.$element.hasClass("in"))return;var b=a.Event("show.bs.collapse");this.$element.trigger(b);if(b.isDefaultPrevented())return;var c=this.$parent&&this.$parent.find("> .panel > .in");if(c&&c.length){var d=c.data("bs.collapse");if(d&&d.transitioning)return;c.collapse("hide"),d||c.data("bs.collapse",null)}var e=this.dimension();this.$element.removeClass("collapse").addClass("collapsing")[e](0),this.transitioning=1;var f=function(){this.$element.removeClass("collapsing").addClass("collapse in")[e]("auto"),this.transitioning=0,this.$element.trigger("shown.bs.collapse")};if(!a.support.transition)return f.call(this);var g=a.camelCase(["scroll",e].join("-"));this.$element.one(a.support.transition.end,a.proxy(f,this)).emulateTransitionEnd(350)[e](this.$element[0][g])},b.prototype.hide=function(){if(this.transitioning||!this.$element.hasClass("in"))return;var b=a.Event("hide.bs.collapse");this.$element.trigger(b);if(b.isDefaultPrevented())return;var c=this.dimension();this.$element[c](this.$element[c]())[0].offsetHeight,this.$element.addClass("collapsing").removeClass("collapse").removeClass("in"),this.transitioning=1;var d=function(){this.transitioning=0,this.$element.trigger("hidden.bs.collapse").removeClass("collapsing").addClass("collapse")};if(!a.support.transition)return d.call(this);this.$element[c](0).one(a.support.transition.end,a.proxy(d,this)).emulateTransitionEnd(350)},b.prototype.toggle=function(){this[this.$element.hasClass("in")?"hide":"show"]()};var c=a.fn.collapse;a.fn.collapse=function(c){return this.each(function(){var d=a(this),e=d.data("bs.collapse"),f=a.extend({},b.DEFAULTS,d.data(),typeof c=="object"&&c);!e&&f.toggle&&c=="show"&&(c=!c),e||d.data("bs.collapse",e=new b(this,f)),typeof c=="string"&&e[c]()})},a.fn.collapse.Constructor=b,a.fn.collapse.noConflict=function(){return a.fn.collapse=c,this},a(document).on("click.bs.collapse.data-api","[data-toggle=collapse]",function(b){var c=a(this),d,e=c.attr("data-target")||b.preventDefault()||(d=c.attr("href"))&&d.replace(/.*(?=#[^\s]+$)/,""),f=a(e),g=f.data("bs.collapse"),h=g?"toggle":c.data(),i=c.attr("data-parent"),j=i&&a(i);if(!g||!g.transitioning)j&&j.find('[data-toggle=collapse][data-parent="'+i+'"]').not(c).addClass("collapsed"),c[f.hasClass("in")?"addClass":"removeClass"]("collapsed");f.collapse(h)})}(jQuery),+function(a){function b(){var a=document.createElement("bootstrap"),b={WebkitTransition:"webkitTransitionEnd",MozTransition:"transitionend",OTransition:"oTransitionEnd otransitionend",transition:"transitionend"};for(var c in b)if(a.style[c]!==undefined)return{end:b[c]};return!1}"use strict",a.fn.emulateTransitionEnd=function(b){var c=!1,d=this;a(this).one(a.support.transition.end,function(){c=!0});var e=function(){c||a(d).trigger(a.support.transition.end)};return setTimeout(e,b),this},a(function(){a.support.transition=b()})}(jQuery) \ No newline at end of file diff --git a/sut-market/market-web/src/main/resources/static/js/jquery-1.9.0.min.js b/sut-market/market-web/src/main/resources/static/js/jquery-1.9.0.min.js new file mode 100644 index 0000000..50d1b22 --- /dev/null +++ b/sut-market/market-web/src/main/resources/static/js/jquery-1.9.0.min.js @@ -0,0 +1,4 @@ +/*! jQuery v1.9.0 | (c) 2005, 2012 jQuery Foundation, Inc. | jquery.org/license */(function(e,t){"use strict";function n(e){var t=e.length,n=st.type(e);return st.isWindow(e)?!1:1===e.nodeType&&t?!0:"array"===n||"function"!==n&&(0===t||"number"==typeof t&&t>0&&t-1 in e)}function r(e){var t=Tt[e]={};return st.each(e.match(lt)||[],function(e,n){t[n]=!0}),t}function i(e,n,r,i){if(st.acceptData(e)){var o,a,s=st.expando,u="string"==typeof n,l=e.nodeType,c=l?st.cache:e,f=l?e[s]:e[s]&&s;if(f&&c[f]&&(i||c[f].data)||!u||r!==t)return f||(l?e[s]=f=K.pop()||st.guid++:f=s),c[f]||(c[f]={},l||(c[f].toJSON=st.noop)),("object"==typeof n||"function"==typeof n)&&(i?c[f]=st.extend(c[f],n):c[f].data=st.extend(c[f].data,n)),o=c[f],i||(o.data||(o.data={}),o=o.data),r!==t&&(o[st.camelCase(n)]=r),u?(a=o[n],null==a&&(a=o[st.camelCase(n)])):a=o,a}}function o(e,t,n){if(st.acceptData(e)){var r,i,o,a=e.nodeType,u=a?st.cache:e,l=a?e[st.expando]:st.expando;if(u[l]){if(t&&(r=n?u[l]:u[l].data)){st.isArray(t)?t=t.concat(st.map(t,st.camelCase)):t in r?t=[t]:(t=st.camelCase(t),t=t in r?[t]:t.split(" "));for(i=0,o=t.length;o>i;i++)delete r[t[i]];if(!(n?s:st.isEmptyObject)(r))return}(n||(delete u[l].data,s(u[l])))&&(a?st.cleanData([e],!0):st.support.deleteExpando||u!=u.window?delete u[l]:u[l]=null)}}}function a(e,n,r){if(r===t&&1===e.nodeType){var i="data-"+n.replace(Nt,"-$1").toLowerCase();if(r=e.getAttribute(i),"string"==typeof r){try{r="true"===r?!0:"false"===r?!1:"null"===r?null:+r+""===r?+r:wt.test(r)?st.parseJSON(r):r}catch(o){}st.data(e,n,r)}else r=t}return r}function s(e){var t;for(t in e)if(("data"!==t||!st.isEmptyObject(e[t]))&&"toJSON"!==t)return!1;return!0}function u(){return!0}function l(){return!1}function c(e,t){do e=e[t];while(e&&1!==e.nodeType);return e}function f(e,t,n){if(t=t||0,st.isFunction(t))return st.grep(e,function(e,r){var i=!!t.call(e,r,e);return i===n});if(t.nodeType)return st.grep(e,function(e){return e===t===n});if("string"==typeof t){var r=st.grep(e,function(e){return 1===e.nodeType});if(Wt.test(t))return st.filter(t,r,!n);t=st.filter(t,r)}return st.grep(e,function(e){return st.inArray(e,t)>=0===n})}function p(e){var t=zt.split("|"),n=e.createDocumentFragment();if(n.createElement)for(;t.length;)n.createElement(t.pop());return n}function d(e,t){return e.getElementsByTagName(t)[0]||e.appendChild(e.ownerDocument.createElement(t))}function h(e){var t=e.getAttributeNode("type");return e.type=(t&&t.specified)+"/"+e.type,e}function g(e){var t=nn.exec(e.type);return t?e.type=t[1]:e.removeAttribute("type"),e}function m(e,t){for(var n,r=0;null!=(n=e[r]);r++)st._data(n,"globalEval",!t||st._data(t[r],"globalEval"))}function y(e,t){if(1===t.nodeType&&st.hasData(e)){var n,r,i,o=st._data(e),a=st._data(t,o),s=o.events;if(s){delete a.handle,a.events={};for(n in s)for(r=0,i=s[n].length;i>r;r++)st.event.add(t,n,s[n][r])}a.data&&(a.data=st.extend({},a.data))}}function v(e,t){var n,r,i;if(1===t.nodeType){if(n=t.nodeName.toLowerCase(),!st.support.noCloneEvent&&t[st.expando]){r=st._data(t);for(i in r.events)st.removeEvent(t,i,r.handle);t.removeAttribute(st.expando)}"script"===n&&t.text!==e.text?(h(t).text=e.text,g(t)):"object"===n?(t.parentNode&&(t.outerHTML=e.outerHTML),st.support.html5Clone&&e.innerHTML&&!st.trim(t.innerHTML)&&(t.innerHTML=e.innerHTML)):"input"===n&&Zt.test(e.type)?(t.defaultChecked=t.checked=e.checked,t.value!==e.value&&(t.value=e.value)):"option"===n?t.defaultSelected=t.selected=e.defaultSelected:("input"===n||"textarea"===n)&&(t.defaultValue=e.defaultValue)}}function b(e,n){var r,i,o=0,a=e.getElementsByTagName!==t?e.getElementsByTagName(n||"*"):e.querySelectorAll!==t?e.querySelectorAll(n||"*"):t;if(!a)for(a=[],r=e.childNodes||e;null!=(i=r[o]);o++)!n||st.nodeName(i,n)?a.push(i):st.merge(a,b(i,n));return n===t||n&&st.nodeName(e,n)?st.merge([e],a):a}function x(e){Zt.test(e.type)&&(e.defaultChecked=e.checked)}function T(e,t){if(t in e)return t;for(var n=t.charAt(0).toUpperCase()+t.slice(1),r=t,i=Nn.length;i--;)if(t=Nn[i]+n,t in e)return t;return r}function w(e,t){return e=t||e,"none"===st.css(e,"display")||!st.contains(e.ownerDocument,e)}function N(e,t){for(var n,r=[],i=0,o=e.length;o>i;i++)n=e[i],n.style&&(r[i]=st._data(n,"olddisplay"),t?(r[i]||"none"!==n.style.display||(n.style.display=""),""===n.style.display&&w(n)&&(r[i]=st._data(n,"olddisplay",S(n.nodeName)))):r[i]||w(n)||st._data(n,"olddisplay",st.css(n,"display")));for(i=0;o>i;i++)n=e[i],n.style&&(t&&"none"!==n.style.display&&""!==n.style.display||(n.style.display=t?r[i]||"":"none"));return e}function C(e,t,n){var r=mn.exec(t);return r?Math.max(0,r[1]-(n||0))+(r[2]||"px"):t}function k(e,t,n,r,i){for(var o=n===(r?"border":"content")?4:"width"===t?1:0,a=0;4>o;o+=2)"margin"===n&&(a+=st.css(e,n+wn[o],!0,i)),r?("content"===n&&(a-=st.css(e,"padding"+wn[o],!0,i)),"margin"!==n&&(a-=st.css(e,"border"+wn[o]+"Width",!0,i))):(a+=st.css(e,"padding"+wn[o],!0,i),"padding"!==n&&(a+=st.css(e,"border"+wn[o]+"Width",!0,i)));return a}function E(e,t,n){var r=!0,i="width"===t?e.offsetWidth:e.offsetHeight,o=ln(e),a=st.support.boxSizing&&"border-box"===st.css(e,"boxSizing",!1,o);if(0>=i||null==i){if(i=un(e,t,o),(0>i||null==i)&&(i=e.style[t]),yn.test(i))return i;r=a&&(st.support.boxSizingReliable||i===e.style[t]),i=parseFloat(i)||0}return i+k(e,t,n||(a?"border":"content"),r,o)+"px"}function S(e){var t=V,n=bn[e];return n||(n=A(e,t),"none"!==n&&n||(cn=(cn||st("