From 32a3eed8d34484c639930b2673a718ec5c813ef4 Mon Sep 17 00:00:00 2001 From: AxelNordov <53081068+AxelNordov@users.noreply.github.com> Date: Tue, 3 Aug 2021 17:28:56 +0300 Subject: [PATCH 01/10] Issue 422 update endpoint posts by important image (#428) * update logic findPublishedNotImportantPostsWithImportantImageFirstAndFilters * some changes * add tests --- .../dokazovi/controller/PostController.java | 32 ++++++++++++------- .../dokazovi/repositories/PostRepository.java | 25 +++++++++++++++ .../dokazovi/service/PostService.java | 3 ++ .../service/impl/PostServiceImpl.java | 9 +++++- .../controller/PostControllerTest.java | 20 ++++-------- .../service/impl/PostServiceImplTest.java | 23 +++++++++++++ 6 files changed, 86 insertions(+), 26 deletions(-) diff --git a/src/main/java/com/softserveinc/dokazovi/controller/PostController.java b/src/main/java/com/softserveinc/dokazovi/controller/PostController.java index 7f29647e..23a785d4 100644 --- a/src/main/java/com/softserveinc/dokazovi/controller/PostController.java +++ b/src/main/java/com/softserveinc/dokazovi/controller/PostController.java @@ -129,7 +129,7 @@ public ResponseEntity> findImportant(Pageable pageable) { public ResponseEntity setPostAsImportant( @ApiParam(value = "Multiple comma-separated posts IDs (new order), e.g. ?posts=1,2,3,4...", type = "string") - @RequestParam(required = true) Set posts) { + @RequestParam Set posts) { ApiResponseMessage apiResponseMessage; try { apiResponseMessage = ApiResponseMessage.builder() @@ -395,20 +395,30 @@ public Integer getPostViewCount(@RequestParam String url) { } /** - * Gets latest posts which have important image url + * Gets all published posts sorted by important image url presence then by createdAt + * filtered by directions, by post types and by origins * - * @param pageable interface for pagination information - * @return found posts filtered by not empty important image url and HttpStatus 'OK' + * @param pageable interface for pagination information + * @param directions direction's ids by which the search is performed + * @param types the type's ids by which the search is performed + * @param origins the origin's ids by which the search is performed + * @return found posts and HttpStatus 'OK' */ - @ApiPageable - @ApiOperation(value = "Get Posts With Important Image URL") + @ApiOperation(value = "Get published posts sorted by important image url presence then by createdAt, " + + "filtered by directions, types and origins.") @GetMapping(POST_GET_BY_IMPORTANT_IMAGE) - public ResponseEntity> findAllByImportantImageUrl ( - @PageableDefault(size = 16) Pageable pageable) { - Page posts = postService.getAllByImportantImageUrl(pageable); - return ResponseEntity.status((posts.getTotalElements() != 0) ? HttpStatus.OK : HttpStatus.NOT_FOUND) - .body(posts); + public ResponseEntity> findPublishedNotImportantPostsSortedByImportantImagePresence( + @PageableDefault Pageable pageable, + @ApiParam(value = "Multiple comma-separated direction IDs, e.g. ?directions=1,2,3,4", type = "string") + @RequestParam(defaultValue = "") Set directions, + @ApiParam(value = "Multiple comma-separated post types IDs, e.g. ?types=1,2,3,4", type = "string") + @RequestParam(defaultValue = "") Set types, + @ApiParam(value = "Multiple comma-separated origins IDs, e.g. ?origins=1,2,3,4...", type = "string") + @RequestParam(defaultValue = "") Set origins) { + Page posts = postService.findPublishedNotImportantPostsWithFiltersSortedByImportantImagePresence( + directions, types, origins, pageable); + return ResponseEntity.status(HttpStatus.OK).body(posts); } } diff --git a/src/main/java/com/softserveinc/dokazovi/repositories/PostRepository.java b/src/main/java/com/softserveinc/dokazovi/repositories/PostRepository.java index e0dd419d..6a0674ad 100644 --- a/src/main/java/com/softserveinc/dokazovi/repositories/PostRepository.java +++ b/src/main/java/com/softserveinc/dokazovi/repositories/PostRepository.java @@ -153,4 +153,29 @@ Page findAllByDirectionsAndByPostTypesAndByOrigins(Set type + " ORDER BY (P1.IMPORTANT_IMAGE_URL <> '' AND P1.IMPORTANT_IMAGE_URL IS NOT NULL) DESC, " + " P1.CREATED_AT DESC ") Page findAllByImportantImageUrlDesc (Pageable pageable); + + @Query(nativeQuery = true, + value = "SELECT P1.* FROM POSTS P1 " + + "WHERE P1.STATUS = :#{#postStatus.name()} " + + " AND P1.IMPORTANT = :important " + + " AND CASE WHEN :typesIds IS NOT NULL " + + " THEN P1.TYPE_ID IN (:typesIds) " + + " ELSE P1.POST_ID IS NOT NULL " + + " END " + + " AND CASE WHEN :directionsIds IS NOT NULL " + + " THEN P1.POST_ID IN " + + " (SELECT POST_ID FROM POSTS_DIRECTIONS WHERE DIRECTION_ID IN (:directionsIds)) " + + " ELSE P1.POST_ID IS NOT NULL " + + " END " + + " AND CASE WHEN :originsIds IS NOT NULL " + + " THEN P1.POST_ID IN " + + " (SELECT POST_ID FROM POSTS_ORIGINS WHERE ORIGIN_ID IN (:originsIds)) " + + " ELSE P1.POST_ID IS NOT NULL " + + " END " + + "ORDER BY (P1.IMPORTANT_IMAGE_URL <> '' AND P1.IMPORTANT_IMAGE_URL IS NOT NULL) DESC, " + + " P1.CREATED_AT DESC " + ) + Page findByDirectionsAndTypesAndOriginsAndStatusAndImportantSortedByImportantImagePresence( + Set directionsIds, Set typesIds, Set originsIds, PostStatus postStatus, + Boolean important, Pageable pageable); } diff --git a/src/main/java/com/softserveinc/dokazovi/service/PostService.java b/src/main/java/com/softserveinc/dokazovi/service/PostService.java index 39e8d221..250bab45 100644 --- a/src/main/java/com/softserveinc/dokazovi/service/PostService.java +++ b/src/main/java/com/softserveinc/dokazovi/service/PostService.java @@ -46,4 +46,7 @@ Page findAllByExpertAndTypeAndStatus(Integer expertId, Set typ Integer getPostViewCount(String url); Page getAllByImportantImageUrl(Pageable pageable); + + Page findPublishedNotImportantPostsWithFiltersSortedByImportantImagePresence( + Set directionIds, Set typeIds, Set originIds, Pageable pageable); } diff --git a/src/main/java/com/softserveinc/dokazovi/service/impl/PostServiceImpl.java b/src/main/java/com/softserveinc/dokazovi/service/impl/PostServiceImpl.java index 60ecd6c7..87eb1284 100644 --- a/src/main/java/com/softserveinc/dokazovi/service/impl/PostServiceImpl.java +++ b/src/main/java/com/softserveinc/dokazovi/service/impl/PostServiceImpl.java @@ -322,4 +322,11 @@ public Integer getPostViewCount(String url) { public Page getAllByImportantImageUrl(Pageable pageable) { return postRepository.findAllByImportantImageUrlDesc(pageable).map(postMapper::toPostDTO); } -} \ No newline at end of file + + @Override + public Page findPublishedNotImportantPostsWithFiltersSortedByImportantImagePresence( + Set directions, Set types, Set origins, Pageable pageable) { + return postRepository.findByDirectionsAndTypesAndOriginsAndStatusAndImportantSortedByImportantImagePresence( + directions, types, origins, PostStatus.PUBLISHED, false, pageable).map(postMapper::toPostDTO); + } +} diff --git a/src/test/java/com/softserveinc/dokazovi/controller/PostControllerTest.java b/src/test/java/com/softserveinc/dokazovi/controller/PostControllerTest.java index be159efc..014d1853 100644 --- a/src/test/java/com/softserveinc/dokazovi/controller/PostControllerTest.java +++ b/src/test/java/com/softserveinc/dokazovi/controller/PostControllerTest.java @@ -36,7 +36,7 @@ import org.springframework.test.web.servlet.setup.MockMvcBuilders; import org.springframework.validation.Validator; -import java.util.ArrayList; +import java.util.HashSet; import java.util.List; import java.util.Set; @@ -501,24 +501,16 @@ void getPostViewCount() throws Exception { } @Test - void findAllByImportantImageUrl_isNotFound() throws Exception { - Pageable pageable = PageRequest.of(0, 16); - Page page = new PageImpl<>(new ArrayList<>()); - Mockito.when(postService.getAllByImportantImageUrl(pageable)).thenReturn(page); - mockMvc.perform(MockMvcRequestBuilders - .get(POST + POST_GET_BY_IMPORTANT_IMAGE)) - .andExpect(MockMvcResultMatchers.status().isNotFound()); - } - - @Test - void findAllByImportantImageUrl_isOk() throws Exception { - Pageable pageable = PageRequest.of(0, 16); + void findPublishedNotImportantPostsSortedByImportantImagePresence_isOk() throws Exception { + Pageable pageable = PageRequest.of(0, 12); PostDTO postDTO = PostDTO.builder() .id(1) .importantImageUrl("http://test.test") .build(); Page page = new PageImpl<>(List.of(postDTO)); - Mockito.when(postService.getAllByImportantImageUrl(pageable)).thenReturn(page); + Mockito.when(postService.findPublishedNotImportantPostsWithFiltersSortedByImportantImagePresence( + new HashSet<>(), new HashSet<>(), new HashSet<>(), pageable)) + .thenReturn(page); mockMvc.perform(MockMvcRequestBuilders .get(POST + POST_GET_BY_IMPORTANT_IMAGE)) .andExpect(MockMvcResultMatchers.status().isOk()); diff --git a/src/test/java/com/softserveinc/dokazovi/service/impl/PostServiceImplTest.java b/src/test/java/com/softserveinc/dokazovi/service/impl/PostServiceImplTest.java index 62593dc3..74b83218 100644 --- a/src/test/java/com/softserveinc/dokazovi/service/impl/PostServiceImplTest.java +++ b/src/test/java/com/softserveinc/dokazovi/service/impl/PostServiceImplTest.java @@ -50,6 +50,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anySet; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.times; @@ -938,4 +939,26 @@ void getPostViewCount() { assertEquals(1, postService.getPostViewCount("some")); } + + @Test + void findPublishedNotImportantPostsWithFiltersSortedByImportantImagePresence_isOk() { + Pageable pageable = PageRequest.of(0, 12); + when(postRepository.findByDirectionsAndTypesAndOriginsAndStatusAndImportantSortedByImportantImagePresence( + anySet(), anySet(), anySet(), any(PostStatus.class), anyBoolean(), any(Pageable.class))) + .thenReturn(postEntityPage); + postService.findPublishedNotImportantPostsWithFiltersSortedByImportantImagePresence( + new HashSet<>(), new HashSet<>(), new HashSet<>(), pageable); + verify(postMapper, times(2)).toPostDTO(any(PostEntity.class)); + } + + @Test + void findPublishedNotImportantPostsWithFiltersSortedByImportantImagePresence_NotFound() { + Pageable pageable = PageRequest.of(0, 12); + when(postRepository.findByDirectionsAndTypesAndOriginsAndStatusAndImportantSortedByImportantImagePresence( + anySet(), anySet(), anySet(), any(PostStatus.class), anyBoolean(), any(Pageable.class))) + .thenReturn(Page.empty()); + postService.findPublishedNotImportantPostsWithFiltersSortedByImportantImagePresence( + Set.of(1), new HashSet<>(), new HashSet<>(), pageable); + verify(postMapper, times(0)).toPostDTO(any(PostEntity.class)); + } } From 21e9a53fbd9fd7a1e41feb3ca1fa26ebc2d7c039 Mon Sep 17 00:00:00 2001 From: AxelNordov <53081068+AxelNordov@users.noreply.github.com> Date: Sat, 7 Aug 2021 17:51:22 +0300 Subject: [PATCH 02/10] update query for getting posts by important image (#431) --- .../com/softserveinc/dokazovi/repositories/PostRepository.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/softserveinc/dokazovi/repositories/PostRepository.java b/src/main/java/com/softserveinc/dokazovi/repositories/PostRepository.java index 6a0674ad..c9c43862 100644 --- a/src/main/java/com/softserveinc/dokazovi/repositories/PostRepository.java +++ b/src/main/java/com/softserveinc/dokazovi/repositories/PostRepository.java @@ -173,7 +173,7 @@ Page findAllByDirectionsAndByPostTypesAndByOrigins(Set type + " ELSE P1.POST_ID IS NOT NULL " + " END " + "ORDER BY (P1.IMPORTANT_IMAGE_URL <> '' AND P1.IMPORTANT_IMAGE_URL IS NOT NULL) DESC, " - + " P1.CREATED_AT DESC " + + " P1.CREATED_AT DESC, P1.POST_ID " ) Page findByDirectionsAndTypesAndOriginsAndStatusAndImportantSortedByImportantImagePresence( Set directionsIds, Set typesIds, Set originsIds, PostStatus postStatus, From b296e5f2400f85693e501d2a1f7c597fce0c0c22 Mon Sep 17 00:00:00 2001 From: AxelNordov <53081068+AxelNordov@users.noreply.github.com> Date: Mon, 9 Aug 2021 13:23:34 +0300 Subject: [PATCH 03/10] Issue 430 fix getting posts by important image (#432) * update query for getting posts by important image --- .../dokazovi/repositories/PostRepository.java | 10 +--------- .../com/softserveinc/dokazovi/service/PostService.java | 2 -- .../dokazovi/service/impl/PostServiceImpl.java | 5 ----- 3 files changed, 1 insertion(+), 16 deletions(-) diff --git a/src/main/java/com/softserveinc/dokazovi/repositories/PostRepository.java b/src/main/java/com/softserveinc/dokazovi/repositories/PostRepository.java index c9c43862..3285ed20 100644 --- a/src/main/java/com/softserveinc/dokazovi/repositories/PostRepository.java +++ b/src/main/java/com/softserveinc/dokazovi/repositories/PostRepository.java @@ -146,14 +146,6 @@ Page findAllByDirectionsAndByPostTypesAndByOrigins(Set type + " ORDER BY CREATED_AT DESC ") Page findLatestByOriginVideo(Pageable pageable); - @Query(nativeQuery = true, - value = "SELECT P1.* FROM POSTS P1 " - + " WHERE P1.STATUS = 'PUBLISHED' " - + " AND P1.IMPORTANT = FALSE" - + " ORDER BY (P1.IMPORTANT_IMAGE_URL <> '' AND P1.IMPORTANT_IMAGE_URL IS NOT NULL) DESC, " - + " P1.CREATED_AT DESC ") - Page findAllByImportantImageUrlDesc (Pageable pageable); - @Query(nativeQuery = true, value = "SELECT P1.* FROM POSTS P1 " + "WHERE P1.STATUS = :#{#postStatus.name()} " @@ -173,7 +165,7 @@ Page findAllByDirectionsAndByPostTypesAndByOrigins(Set type + " ELSE P1.POST_ID IS NOT NULL " + " END " + "ORDER BY (P1.IMPORTANT_IMAGE_URL <> '' AND P1.IMPORTANT_IMAGE_URL IS NOT NULL) DESC, " - + " P1.CREATED_AT DESC, P1.POST_ID " + + " P1.PUBLISHED_AT DESC, P1.POST_ID " ) Page findByDirectionsAndTypesAndOriginsAndStatusAndImportantSortedByImportantImagePresence( Set directionsIds, Set typesIds, Set originsIds, PostStatus postStatus, diff --git a/src/main/java/com/softserveinc/dokazovi/service/PostService.java b/src/main/java/com/softserveinc/dokazovi/service/PostService.java index 250bab45..f0ffd1cf 100644 --- a/src/main/java/com/softserveinc/dokazovi/service/PostService.java +++ b/src/main/java/com/softserveinc/dokazovi/service/PostService.java @@ -45,8 +45,6 @@ Page findAllByExpertAndTypeAndStatus(Integer expertId, Set typ Integer getPostViewCount(String url); - Page getAllByImportantImageUrl(Pageable pageable); - Page findPublishedNotImportantPostsWithFiltersSortedByImportantImagePresence( Set directionIds, Set typeIds, Set originIds, Pageable pageable); } diff --git a/src/main/java/com/softserveinc/dokazovi/service/impl/PostServiceImpl.java b/src/main/java/com/softserveinc/dokazovi/service/impl/PostServiceImpl.java index 87eb1284..5e2a045d 100644 --- a/src/main/java/com/softserveinc/dokazovi/service/impl/PostServiceImpl.java +++ b/src/main/java/com/softserveinc/dokazovi/service/impl/PostServiceImpl.java @@ -318,11 +318,6 @@ public Integer getPostViewCount(String url) { return googleAnalytics.getPostViewCount(url); } - @Override - public Page getAllByImportantImageUrl(Pageable pageable) { - return postRepository.findAllByImportantImageUrlDesc(pageable).map(postMapper::toPostDTO); - } - @Override public Page findPublishedNotImportantPostsWithFiltersSortedByImportantImagePresence( Set directions, Set types, Set origins, Pageable pageable) { From 3cb5f18c03791a6010473ab3e9ba4d758902e757 Mon Sep 17 00:00:00 2001 From: AlexandrShpyha <48212099+AlexandrShpyha@users.noreply.github.com> Date: Tue, 10 Aug 2021 18:03:23 +0300 Subject: [PATCH 04/10] Add integration test to post controller (#427) * set up gradle file for integration test * first successful integration test run with database * added database cleaning after each test * solved conflicts with dependencies * added first consistent integration tests * covered post and put controllers, corrected ForbiddenPermissionsException http status * little refactoring and added new tests * covered almost all methods * covered almost all methods * deleted code for checking duration * refactored several tests * fixed Project.xml changes * fixed style error in RequestsBodies.java * clean-up unused code --- .idea/codeStyles/Project.xml | 2 +- build.gradle | 175 +++++--- .../DokazoviApplicationIntegrationTests.java | 11 + .../PostControllerIntegrationTest.java | 386 ++++++++++++++++++ .../dokazovi/controller/RequestsBodies.java | 55 +++ .../resources/application.properties | 37 ++ .../resources/db/insertBasicInformation.sql | 98 +++++ .../resources/db/postsData.sql | 55 +++ .../dokazovi/dto/origin/OriginDTO.java | 4 +- .../ForbiddenPermissionsException.java | 4 + .../dokazovi/mapper/OriginMapperTest.java | 2 +- 11 files changed, 760 insertions(+), 69 deletions(-) create mode 100644 src/integrationTest/java/com/softserveinc/dokazovi/DokazoviApplicationIntegrationTests.java create mode 100644 src/integrationTest/java/com/softserveinc/dokazovi/controller/PostControllerIntegrationTest.java create mode 100644 src/integrationTest/java/com/softserveinc/dokazovi/controller/RequestsBodies.java create mode 100644 src/integrationTest/resources/application.properties create mode 100644 src/integrationTest/resources/db/insertBasicInformation.sql create mode 100644 src/integrationTest/resources/db/postsData.sql diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml index 477c7055..71396f8a 100644 --- a/.idea/codeStyles/Project.xml +++ b/.idea/codeStyles/Project.xml @@ -46,4 +46,4 @@ - \ No newline at end of file + diff --git a/build.gradle b/build.gradle index 2ab3f74e..160aab5c 100644 --- a/build.gradle +++ b/build.gradle @@ -1,103 +1,148 @@ plugins { - id 'org.springframework.boot' version '2.3.4.RELEASE' - id 'io.spring.dependency-management' version '1.0.10.RELEASE' - id 'java' - id 'war' - id 'idea' - id 'checkstyle' - id 'jacoco' - id 'org.sonarqube' version '3.0' + id 'org.springframework.boot' version '2.3.4.RELEASE' + id 'io.spring.dependency-management' version '1.0.10.RELEASE' + id 'java' + id 'war' + id 'idea' + id 'checkstyle' + id 'jacoco' + id 'org.sonarqube' version '3.0' } group = 'com.softserveinc' sourceCompatibility = '11' +sourceSets { + integrationTest { + compileClasspath += sourceSets.main.output + runtimeClasspath += sourceSets.main.output + } +} + +idea { + module { + testSourceDirs += project.sourceSets.integrationTest.java.srcDirs + testSourceDirs += project.sourceSets.integrationTest.resources.srcDirs + } +} + +task integrationTest(type: Test) { + description("Runs integration tests") + group("verification") + testClassesDirs = sourceSets.integrationTest.output.classesDirs + classpath = sourceSets.integrationTest.runtimeClasspath + useJUnitPlatform() +} + + bootRun { - main = "com.softserveinc.dokazovi.DokazoviApplication" + main = "com.softserveinc.dokazovi.DokazoviApplication" } checkstyleMain { - configFile = file("${rootDir}/checkstyle.xml") + configFile = file("${rootDir}/checkstyle.xml") } checkstyleTest { - configFile = file("${rootDir}/checkstyle.xml") + configFile = file("${rootDir}/checkstyle.xml") } -configurations { - compileOnly { - extendsFrom annotationProcessor - } +checkstyleIntegrationTest { + configFile = file("${rootDir}/checkstyle.xml") } repositories { - mavenCentral() + mavenCentral() } springBoot { - buildInfo() + buildInfo() } -dependencies { - compile group: 'org.springframework.security', name: 'spring-security-oauth2-client' - compile group: 'io.jsonwebtoken', name: 'jjwt', version: '0.9.1' - compile group: 'javax.validation', name: 'validation-api', version: '2.0.0.Final' - compile group: 'javax.mail', name: 'javax.mail-api', version: '1.6.2' - - implementation 'org.springframework.boot:spring-boot-starter-data-jpa' - implementation 'org.springframework.boot:spring-boot-starter-web' - implementation 'org.springframework.boot:spring-boot-starter-security' - implementation 'org.springframework.boot:spring-boot-starter-validation' - implementation 'org.springframework.boot:spring-boot-starter-mail:2.3.12.RELEASE' - implementation group: 'com.google.apis', name: 'google-api-services-analytics', version: 'v3-rev161-1.25.0' - implementation group: 'com.google.api-client', name: 'google-api-client', version: '1.31.5' +configurations { + integrationTestImplementation.extendsFrom testImplementation + integrationTestRuntimeOnly.extendsFrom testRuntime + compileOnly.extendsFrom annotationProcessor +} - implementation 'org.flywaydb:flyway-core' - implementation "org.mapstruct:mapstruct:${MAPPER_VERSION}" - annotationProcessor "org.mapstruct:mapstruct-processor:${MAPPER_VERSION}" - testAnnotationProcessor "org.mapstruct:mapstruct-processor:${MAPPER_VERSION}" - compileOnly "org.projectlombok:lombok" - annotationProcessor "org.projectlombok:lombok" +configurations.all { + resolutionStrategy.eachDependency { details -> + if (details.requested.group == 'org.codehaus.groovy') { + details.useVersion '3.0.8' + } + } +} - implementation 'io.springfox:springfox-boot-starter:3.0.0' - runtimeOnly 'org.postgresql:postgresql' +dependencies { + implementation group: 'org.springframework.security', name: 'spring-security-oauth2-client' + implementation group: 'io.jsonwebtoken', name: 'jjwt', version: '0.9.1' + implementation group: 'javax.validation', name: 'validation-api', version: '2.0.0.Final' + implementation group: 'javax.mail', name: 'javax.mail-api', version: '1.6.2' + + implementation 'org.springframework.boot:spring-boot-starter-data-jpa' + implementation 'org.springframework.boot:spring-boot-starter-web' + implementation 'org.springframework.boot:spring-boot-starter-security' + implementation 'org.springframework.boot:spring-boot-starter-validation' + implementation 'org.springframework.boot:spring-boot-starter-mail:2.3.12.RELEASE' + implementation group: 'com.google.apis', name: 'google-api-services-analytics', version: 'v3-rev161-1.25.0' + implementation group: 'com.google.api-client', name: 'google-api-client', version: '1.31.5' - testImplementation group: 'org.powermock', name: 'powermock-module-junit4', version: '2.0.9' - testImplementation group: 'org.powermock', name: 'powermock-api-mockito2', version: '2.0.9' - testImplementation "com.h2database:h2" - testImplementation('org.springframework.boot:spring-boot-starter-test') { - exclude group: 'org.junit.vintage', module: 'junit-vintage-engine' - } + implementation 'org.flywaydb:flyway-core' + implementation "org.mapstruct:mapstruct:${MAPPER_VERSION}" + annotationProcessor "org.mapstruct:mapstruct-processor:${MAPPER_VERSION}" + testAnnotationProcessor "org.mapstruct:mapstruct-processor:${MAPPER_VERSION}" + integrationTestAnnotationProcessor "org.mapstruct:mapstruct-processor:${MAPPER_VERSION}" + compileOnly "org.projectlombok:lombok" + annotationProcessor "org.projectlombok:lombok" + + implementation 'io.springfox:springfox-boot-starter:3.0.0' + implementation 'org.postgresql:postgresql' + + testImplementation group: 'org.powermock', name: 'powermock-module-junit4', version: '2.0.9' + testImplementation group: 'org.powermock', name: 'powermock-api-mockito2', version: '2.0.9' + testImplementation "com.h2database:h2" + testImplementation('org.springframework.boot:spring-boot-starter-test') { + exclude group: 'org.junit.vintage', module: 'junit-vintage-engine' + } + + integrationTestImplementation('io.rest-assured:rest-assured') { + exclude group: 'com.sun.xml.bind', module: 'jaxb-osgi' + } + integrationTestImplementation group: 'org.testcontainers', name: 'postgresql', version: '1.15.3' + integrationTestImplementation group: 'org.testcontainers', name: 'junit-jupiter', version: '1.15.3' + integrationTestImplementation group: 'org.testcontainers', name: 'database-commons', version: '1.15.3' } +check.dependsOn integrationTest + test { - useJUnitPlatform() + useJUnitPlatform() } jacoco { - toolVersion = "0.8.5" + toolVersion = "0.8.5" } jacocoTestReport { - dependsOn test - reports { - xml.enabled true - } + dependsOn test, integrationTest + reports { + xml.enabled true + } } sonarqube { - tasks['sonarqube'].dependsOn jacocoTestReport - properties { - property "sonar.host.url", "https://sonarcloud.io" - property "sonar.projectKey", "ita-social-projects_dokazovi-be" - property "sonar.organization", "ita-social-projects" - property "sonar.jacoco.reportPaths", "$buildDir/reports/jacoco/test/jacocoTestReport.xml" - property "sonar.coverage.exclusions", "**/*.sql," + - "src/main/java/com/softserveinc/dokazovi/entity/**," + - "src/main/java/com/softserveinc/dokazovi/dto/**," + - "src/main/java/com/softserveinc/dokazovi/exception/**," + - "src/main/java/com/softserveinc/dokazovi/security/**," + - "src/main/java/com/softserveinc/dokazovi/analytics/**," + - "src/main/java/com/softserveinc/dokazovi/config/**" - } -} \ No newline at end of file + tasks['sonarqube'].dependsOn jacocoTestReport + properties { + property "sonar.host.url", "https://sonarcloud.io" + property "sonar.projectKey", "ita-social-projects_dokazovi-be" + property "sonar.organization", "ita-social-projects" + property "sonar.jacoco.reportPaths", "$buildDir/reports/jacoco/test/jacocoTestReport.xml" + property "sonar.coverage.exclusions", "**/*.sql," + + "src/main/java/com/softserveinc/dokazovi/entity/**," + + "src/main/java/com/softserveinc/dokazovi/dto/**," + + "src/main/java/com/softserveinc/dokazovi/exception/**," + + "src/main/java/com/softserveinc/dokazovi/security/**," + + "src/main/java/com/softserveinc/dokazovi/analytics/**," + + "src/main/java/com/softserveinc/dokazovi/config/**" + } +} diff --git a/src/integrationTest/java/com/softserveinc/dokazovi/DokazoviApplicationIntegrationTests.java b/src/integrationTest/java/com/softserveinc/dokazovi/DokazoviApplicationIntegrationTests.java new file mode 100644 index 00000000..8d042d06 --- /dev/null +++ b/src/integrationTest/java/com/softserveinc/dokazovi/DokazoviApplicationIntegrationTests.java @@ -0,0 +1,11 @@ +package com.softserveinc.dokazovi; + +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; + +@SpringBootTest +class DokazoviApplicationIntegrationTests { + @Test + void contextLoads() { + } +} diff --git a/src/integrationTest/java/com/softserveinc/dokazovi/controller/PostControllerIntegrationTest.java b/src/integrationTest/java/com/softserveinc/dokazovi/controller/PostControllerIntegrationTest.java new file mode 100644 index 00000000..6e99892f --- /dev/null +++ b/src/integrationTest/java/com/softserveinc/dokazovi/controller/PostControllerIntegrationTest.java @@ -0,0 +1,386 @@ +package com.softserveinc.dokazovi.controller; + +import com.softserveinc.dokazovi.DokazoviApplication; +import com.softserveinc.dokazovi.dto.payload.LoginRequest; +import com.softserveinc.dokazovi.dto.post.PostDTO; +import com.softserveinc.dokazovi.entity.enumerations.PostStatus; +import io.restassured.RestAssured; +import org.flywaydb.core.Flyway; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.web.server.LocalServerPort; +import org.springframework.http.HttpStatus; +import org.springframework.test.context.jdbc.Sql; +import org.springframework.test.context.junit.jupiter.SpringExtension; + +import javax.annotation.PostConstruct; +import java.util.List; + +import static com.softserveinc.dokazovi.controller.EndPoints.AUTH; +import static com.softserveinc.dokazovi.controller.EndPoints.AUTH_LOGIN; +import static com.softserveinc.dokazovi.controller.EndPoints.POST; +import static com.softserveinc.dokazovi.controller.EndPoints.POST_ALL_POSTS; +import static com.softserveinc.dokazovi.controller.EndPoints.POST_GET_POST_BY_ID; +import static com.softserveinc.dokazovi.controller.EndPoints.POST_IMPORTANT; +import static com.softserveinc.dokazovi.controller.EndPoints.POST_LATEST_BY_DIRECTION; +import static com.softserveinc.dokazovi.controller.EndPoints.POST_LATEST_BY_EXPERT; +import static com.softserveinc.dokazovi.controller.EndPoints.POST_LATEST_BY_EXPERT_AND_STATUS; +import static com.softserveinc.dokazovi.controller.RequestsBodies.BODY_FOR_POST_SAVE; +import static com.softserveinc.dokazovi.controller.RequestsBodies.BODY_FOR_POST_UPDATE; +import static io.restassured.RestAssured.given; +import static io.restassured.RestAssured.when; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.allOf; +import static org.hamcrest.Matchers.contains; +import static org.hamcrest.Matchers.either; +import static org.hamcrest.Matchers.empty; +import static org.hamcrest.Matchers.emptyOrNullString; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.everyItem; +import static org.hamcrest.Matchers.hasItem; +import static org.hamcrest.Matchers.hasProperty; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.not; + +@ExtendWith(SpringExtension.class) +@SpringBootTest(classes = DokazoviApplication.class, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) +@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE) +@Sql(value = {"/db/insertBasicInformation.sql"}) +public class PostControllerIntegrationTest { + + @Autowired + Flyway flyway; + + @LocalServerPort + private int port; + + @PostConstruct + void init() { + RestAssured.port = port; + } + + @AfterEach + void cleanDatabase() { + flyway.clean(); + flyway.migrate(); + } + + @Test + public void savePostWithoutAuthentication() { + given() + .contentType("application/json") + .body(BODY_FOR_POST_SAVE) + .when() + .post(POST) + .then() + .statusCode(HttpStatus.UNAUTHORIZED.value()); + } + + @Test + public void saveOwnPostWithAuthentication() { + given() + .auth().oauth2(getAccessToken("ivan@mail.com","ivan")) + .contentType("application/json") + .body(BODY_FOR_POST_SAVE) + .when() + .post(POST) + .then() + .statusCode(HttpStatus.CREATED.value()); + } + + @Test + public void saveNotOwnPost() { + given() + .auth().oauth2(getAccessToken("fedot@mail.com","fedot")) + .contentType("application/json") + .body(BODY_FOR_POST_SAVE) + .when() + .post(POST) + .then() + .statusCode(HttpStatus.FORBIDDEN.value()); + } + + @Test + public void saveNotOwnPostByAdmin() { + given() + .auth().oauth2(getAccessToken("admin@mail.com","admin")) + .contentType("application/json") + .body(BODY_FOR_POST_SAVE) + .when() + .post(POST) + .then() + .statusCode(HttpStatus.CREATED.value()); + } + + @Test + public void updateNotExistingPost() { + given() + .auth().oauth2(getAccessToken("admin@mail.com","admin")) + .contentType("application/json") + .body(BODY_FOR_POST_UPDATE) + .when() + .put(POST) + .then() + .statusCode(HttpStatus.OK.value()) + .body("success", is(false)) + .body("message", is("Entity not found")); + } + + @Test + @Sql(value = {"/db/insertBasicInformation.sql", "/db/postsData.sql"}) + public void updateExistingPostByAuthor() { + given() + .auth().oauth2(getAccessToken("ivan@mail.com","ivan")) + .contentType("application/json") + .body(BODY_FOR_POST_UPDATE) + .when() + .put(POST) + .then() + .statusCode(HttpStatus.OK.value()) + .body("success", is(true)); + } + + @Test + @Sql(value = {"/db/insertBasicInformation.sql", "/db/postsData.sql"}) + public void updateNotOwnPostByAdmin() { + given() + .auth().oauth2(getAccessToken("admin@mail.com","admin")) + .contentType("application/json") + .body(BODY_FOR_POST_UPDATE) + .when() + .put(POST) + .then() + .statusCode(HttpStatus.OK.value()) + .body("success", equalTo(true)); + } + + @Test + @Sql(value = {"/db/insertBasicInformation.sql", "/db/postsData.sql"}) + public void updateNotOwnPost() { + given() + .auth().oauth2(getAccessToken("fedot@mail.com","fedot")) + .contentType("application/json") + .body(BODY_FOR_POST_UPDATE) + .when() + .put(POST) + .then() + .statusCode(HttpStatus.OK.value()) + .body("success", equalTo(false)) + .body("message", equalTo("Forbidden permission")); + } + + @Test + public void updatePostWithoutAuthentication() { + given() + .contentType("application/json") + .body(BODY_FOR_POST_UPDATE) + .when() + .put(POST) + .then() + .statusCode(HttpStatus.UNAUTHORIZED.value()); + } + + @Test + public void deletePostWithoutAuthentication() { + given() + .contentType("application/json") + .body(BODY_FOR_POST_UPDATE) + .pathParam("postId",1) + .when() + .delete(POST + POST_GET_POST_BY_ID) + .then() + .statusCode(HttpStatus.UNAUTHORIZED.value()); + } + + @Test + @Sql(value = {"/db/insertBasicInformation.sql", "/db/postsData.sql"}) + public void deleteNotOwnPost() { + given() + .auth().oauth2(getAccessToken("fedot@mail.com","fedot")) + .contentType("application/json") + .body(BODY_FOR_POST_UPDATE) + .pathParam("postId",1) + .when() + .delete(POST + POST_GET_POST_BY_ID) + .then() + .statusCode(HttpStatus.FORBIDDEN.value()); + } + + @Test + @Sql(value = {"/db/insertBasicInformation.sql", "/db/postsData.sql"}) + public void deleteNotOwnPostByAdmin() { + given() + .auth().oauth2(getAccessToken("admin@mail.com","admin")) + .contentType("application/json") + .body(BODY_FOR_POST_UPDATE) + .pathParam("postId",1) + .when() + .delete(POST + POST_GET_POST_BY_ID) + .then() + .statusCode(HttpStatus.OK.value()); + } + + @Test + @Sql(value = {"/db/insertBasicInformation.sql", "/db/postsData.sql"}) + public void deleteOwnPost() { + given() + .auth().oauth2(getAccessToken("ivan@mail.com","ivan")) + .contentType("application/json") + .body(BODY_FOR_POST_UPDATE) + .pathParam("postId",1) + .when() + .delete(POST + POST_GET_POST_BY_ID) + .then() + .statusCode(HttpStatus.OK.value()); + } + + @Test + public void deleteNotExistingPost() { + given() + .auth().oauth2(getAccessToken("fedot@mail.com","fedot")) + .contentType("application/json") + .body(BODY_FOR_POST_UPDATE) + .pathParam("postId",1) + .when() + .delete(POST + POST_GET_POST_BY_ID) + .then() + .statusCode(HttpStatus.OK.value()) + .body("success", is(false)) + .body("message", is("Post with 1 not found")); + } + + @Test + @Sql(value = {"/db/insertBasicInformation.sql", "/db/postsData.sql"}) + public void getPostById() { + PostDTO post = given() + .pathParam("postId", 2) + .when() + .get(POST + POST_GET_POST_BY_ID) + .then() + .statusCode(HttpStatus.OK.value()) + .extract() + .jsonPath().getObject("", PostDTO.class); + + assertThat(post, allOf( + hasProperty("id", is(2)), + hasProperty("title", is("Another title")))); + } + + @Test + @Sql(value = {"/db/insertBasicInformation.sql", "/db/postsData.sql"}) + public void getLatestPostsByExpertAndStatus() { + List posts = given() + .param("expert", 1) + .param("status", PostStatus.PUBLISHED) + .when() + .get(POST + POST_LATEST_BY_EXPERT_AND_STATUS) + .then() + .statusCode(HttpStatus.OK.value()) + .extract() + .jsonPath().getList("content", PostDTO.class); + + assertThat(posts, everyItem(allOf( + hasProperty("author", hasProperty("id", is(1))), + hasProperty("publishedAt", not(emptyOrNullString()))))); + } + + @Test + @Sql(value = {"/db/insertBasicInformation.sql", "/db/postsData.sql"}) + public void getAllPostsWithDirectionsThreeAndFive() { + List posts = given() + .param("directions", 3, 5) + .param("types", 1, 2, 3) + .param("origins", 1, 2, 3) + .when() + .get(POST + POST_ALL_POSTS) + .then() + .statusCode(HttpStatus.OK.value()) + .extract() + .jsonPath().getList("content", PostDTO.class); + + assertThat(posts, not(empty())); + assertThat(posts, + everyItem(hasProperty("directions", + hasItem(hasProperty("id", either(is(3)).or(is(5))))))); + } + + @Test + @Sql(value = {"/db/insertBasicInformation.sql", "/db/postsData.sql"}) + public void getLatestPostsByDirection() { + List posts = given() + .param("direction", 3) + .when() + .get(POST + POST_LATEST_BY_DIRECTION) + .then() + .statusCode(HttpStatus.OK.value()) + .extract() + .jsonPath().getList("content", PostDTO.class); + + assertThat(posts, + everyItem(hasProperty("directions", hasItem(hasProperty("id", is(3)))))); + } + + @Test + @Sql(value = {"/db/insertBasicInformation.sql", "/db/postsData.sql"}) + public void getLatestPostsByExpert() { + List posts = given() + .param("expert", 1) + .when() + .get(POST + POST_LATEST_BY_EXPERT) + .then() + .statusCode(HttpStatus.OK.value()) + .extract() + .jsonPath().getList("content", PostDTO.class); + + assertThat(posts, everyItem(hasProperty("author", hasProperty("id", is(1))))); + } + + @Test + @Sql(value = {"/db/insertBasicInformation.sql", "/db/postsData.sql"}) + public void getAllLatestPosts() { + List posts = when() + .get(POST + POST_ALL_POSTS) + .then() + .statusCode(HttpStatus.OK.value()) + .extract() + .jsonPath().getList("content", PostDTO.class); + + assertThat(posts, not(empty())); + assertThat(posts, everyItem(hasProperty("publishedAt", not(emptyOrNullString())))); + } + + @Test + @Sql(value = {"/db/insertBasicInformation.sql", "/db/postsData.sql"}) + public void getAllImportantPosts() { + List posts = when() + .get(POST + POST_IMPORTANT) + .then() + .statusCode(HttpStatus.OK.value()) + .extract() + .jsonPath().getList("content", PostDTO.class); + + assertThat(posts, contains(hasProperty("id", is(1)))); + } + + String getAccessToken(String testEmail, String testPassword) { + LoginRequest request = new LoginRequest(); + request.setEmail(testEmail); + request.setPassword(testPassword); + + return given() + .contentType("application/json") + .body(request) + .when() + .post(AUTH + AUTH_LOGIN) + .then() + .contentType("application/json") + .extract() + .path("accessToken"); + } + +} + diff --git a/src/integrationTest/java/com/softserveinc/dokazovi/controller/RequestsBodies.java b/src/integrationTest/java/com/softserveinc/dokazovi/controller/RequestsBodies.java new file mode 100644 index 00000000..2f35ef39 --- /dev/null +++ b/src/integrationTest/java/com/softserveinc/dokazovi/controller/RequestsBodies.java @@ -0,0 +1,55 @@ +package com.softserveinc.dokazovi.controller; + +public final class RequestsBodies { + + private RequestsBodies() { + } + + public final static String BODY_FOR_POST_SAVE = "{\n" + + " \"authorId\": 1,\n" + + " \"content\": \"string\",\n" + + " \"directions\": [\n" + + " {\n" + + " \"id\": 1\n" + + " }\n" + + " ],\n" + + " \"importantImageUrl\": \"string\",\n" + + " \"origins\": [\n" + + " {\n" + + " \"id\": 1\n" + + " }\n" + + " ],\n" + + " \"preview\": \"string\",\n" + + " \"previewImageUrl\": \"string\",\n" + + " \"title\": \"string\",\n" + + " \"type\": {\n" + + " \"id\": 1\n" + + " },\n" + + " \"videoUrl\": \"string\"\n" + + "}"; + + public final static String BODY_FOR_POST_UPDATE = "{\n" + + " \"authorId\": 1,\n" + + " \"content\": \"string\",\n" + + " \"directions\": [\n" + + " {\n" + + " \"id\": 1\n" + + " }\n" + + " ],\n" + + " \"id\": 1,\n" + + " \"importantImageUrl\": \"string\",\n" + + " \"origins\": [\n" + + " {\n" + + " \"id\": 1\n" + + " }\n" + + " ],\n" + + " \"preview\": \"string\",\n" + + " \"previewImageUrl\": \"string\",\n" + + " \"title\": \"string\",\n" + + " \"type\": {\n" + + " \"id\": 1\n" + + " },\n" + + " \"videoUrl\": \"string\"\n" + + "}"; + +} diff --git a/src/integrationTest/resources/application.properties b/src/integrationTest/resources/application.properties new file mode 100644 index 00000000..10f4e08d --- /dev/null +++ b/src/integrationTest/resources/application.properties @@ -0,0 +1,37 @@ +info.build.version=${BUILD_VERSION:0.0.0} +endpoints.cors=${ALLOWED_ORIGIN: http://localhost:3000, https://dokazovi-fe.herokuapp.com} +analytics.creds=${GOOGLE_CREDENTIALS} + +#------------------------- +# Database PostgresSQL +#------------------------- +spring.datasource.url=${DATASOURCE_URL:jdbc:tc:postgresql:13.3:///test} +spring.datasource.username=${DATASOURCE_USER:test_user} +spring.datasource.password=${DATASOURCE_PASSWORD:test_password} +spring.jpa.database-platform=${SPRING_JPA_DATABASE_PLATFORM:org.hibernate.dialect.PostgreSQLDialect} +spring.jpa.hibernate.ddl-auto=none + +#------------------------- +# JPA +#------------------------- +spring.jpa.show-sql=false + + +#------------------------- +# FileSystem settings +#------------------------- +url.assets.path = ${URL_RESOURCE_PATH:assets} +fs.root = ${FS_RESOURCE_PATH:C://dokazovi//assets//} +fs.images = ${fs.root}images +################### JavaMail Configuration ########################## +host.url=${HOST_URL:http://localhost:8080} +support.email=${JAVA_MAIL_SUPPORT} +spring.mail.host=smtp.gmail.com +spring.mail.port=465 +spring.mail.protocol=smtps +spring.mail.username=${JAVA_MAIL_USERNAME} +spring.mail.password=${JAVA_MAIL_PASSWORD} +spring.mail.properties.mail.transport.protocol=smtps +spring.mail.properties.mail.smtps.auth=true +spring.mail.properties.mail.smtps.starttls.enable=true +spring.mail.properties.mail.smtps.timeout=8000 diff --git a/src/integrationTest/resources/db/insertBasicInformation.sql b/src/integrationTest/resources/db/insertBasicInformation.sql new file mode 100644 index 00000000..1ba3ce35 --- /dev/null +++ b/src/integrationTest/resources/db/insertBasicInformation.sql @@ -0,0 +1,98 @@ +INSERT INTO public.tags (tag) +VALUES ('Профілактика'); +INSERT INTO public.tags (tag) +VALUES ('Комплекс'); +INSERT INTO public.tags (tag) +VALUES ('Ковід'); +INSERT INTO public.tags (tag) +VALUES ('Допомога'); +INSERT INTO public.tags (tag) +VALUES ('Міокард'); +INSERT INTO public.tags (tag) +VALUES ('ЕКГ'); + +INSERT INTO public.origins (name, parameters) +VALUES ('Думка експерта', null); +INSERT INTO public.origins (name, parameters) +VALUES ('Медитека', null); +INSERT INTO public.origins (name, parameters) +VALUES ('Переклад', null); + +INSERT INTO public.post_types (name) +VALUES ('Стаття'); +INSERT INTO public.post_types (name) +VALUES ('Відео'); +INSERT INTO public.post_types (name) +VALUES ('Допис'); +INSERT INTO public.post_types (name) +VALUES ('Переклад'); + +INSERT INTO public.roles (role_name) +VALUES ('Administrator'); +INSERT INTO public.roles (role_name) +VALUES ('Moderator'); +INSERT INTO public.roles (role_name) +VALUES ('Doctor'); + +INSERT INTO public.directions (label, color, name) +VALUES ('Covid-19', '#ef5350', 'covid-19'); +INSERT INTO public.directions (label, color, name) +VALUES ('Офтальмологія', '#98ef50', 'ophthalmology'); +INSERT INTO public.directions (label, color, name) +VALUES ('Хірургія', '#7aebbf', 'surgery'); +INSERT INTO public.directions (label, color, name) +VALUES ('Терапія', '#ffee58', 'therapy'); +INSERT INTO public.directions (label, color, name) +VALUES ('Вірусологія', '#da80e8', 'virology'); +INSERT INTO public.directions (label, color, name) +VALUES ('Кардіологія', '#00ffff', 'cardiology'); +INSERT INTO public.directions (label, color, name) +VALUES ('Педіатрія', '#993333', 'pediatrics'); + +INSERT INTO public.role_permission (role_id, permissions) +VALUES (1, 'SAVE_OWN_PUBLICATION'); +INSERT INTO public.role_permission (role_id, permissions) +VALUES (1, 'SAVE_TAG'); +INSERT INTO public.role_permission (role_id, permissions) +VALUES (2, 'SAVE_OWN_PUBLICATION'); +INSERT INTO public.role_permission (role_id, permissions) +VALUES (2, 'SAVE_TAG'); +INSERT INTO public.role_permission (role_id, permissions) +VALUES (3, 'SAVE_OWN_PUBLICATION'); +INSERT INTO public.role_permission (role_id, permissions) +VALUES (3, 'SAVE_TAG'); +INSERT INTO public.role_permission (role_id, permissions) +VALUES (1, 'DELETE_POST'); +INSERT INTO public.role_permission (role_id, permissions) +VALUES (2, 'DELETE_POST'); +INSERT INTO public.role_permission (role_id, permissions) +VALUES (3, 'DELETE_OWN_POST'); +INSERT INTO public.role_permission (role_id, permissions) +VALUES (1, 'UPDATE_POST'); +INSERT INTO public.role_permission (role_id, permissions) +VALUES (2, 'UPDATE_POST'); +INSERT INTO public.role_permission (role_id, permissions) +VALUES (3, 'UPDATE_OWN_POST'); +INSERT INTO public.role_permission (role_id, permissions) +VALUES (1, 'SAVE_PUBLICATION'); +INSERT INTO public.role_permission (role_id, permissions) +VALUES (1, 'SET_IMPORTANCE'); +INSERT INTO public.role_permission (role_id, permissions) +VALUES (1, 'SAVE_PLATFORM_INFORMATION'); +INSERT INTO public.role_permission (role_id, permissions) +VALUES (1, 'UPDATE_PLATFORM_INFORMATION'); + +INSERT INTO public.users (email, password, status, first_name, last_name, phone, created_at, avatar, enabled, + role_id) +VALUES ('ivan@mail.com', '$2y$10$ishgf6hBdlEQwE8Ld1ktkOOPsINMgE7CviFi1qxRaiOgvUdg3RCTy', 'ACTIVE', 'Іван', 'Іванов', + '+380969696969', '2021-02-16 03:56:37.332925', 'https://i.pravatar.cc/300?img=3', true, 3), + ('fedot@mail.com', '$2y$10$.oKUlohR31I8wni/Qi8nwu9cJti4P5ddg6oq6FIK4H7r/jmmK44sG', 'ACTIVE', 'Федот', + 'Федотенко', '+380956761119', '2021-02-16 03:56:37.394884', 'https://i.pravatar.cc/300?img=55', true, 3), + ('admin@mail.com', '$2y$10$GtQSp.P.EyAtCgUD2zWLW.01OBz409TGPl/Jo3U30Tig3YbbpIFv2', 'ACTIVE', 'Gregory', + 'House', '+380939393939', '2021-02-16 03:56:37.332925', null, true, 1); + + +INSERT INTO public.providers (provider_name, email, user_id_by_provider, user_id) +VALUES ('LOCAL', 'ivan@mail.com', '1', 1), + ('LOCAL', 'fedot@mail.com', '2', 2), + ('LOCAL', 'admin@mail.com', '3', 3); diff --git a/src/integrationTest/resources/db/postsData.sql b/src/integrationTest/resources/db/postsData.sql new file mode 100644 index 00000000..2611eb1a --- /dev/null +++ b/src/integrationTest/resources/db/postsData.sql @@ -0,0 +1,55 @@ +INSERT INTO PUBLIC.POSTS (TITLE, PREVIEW, CONTENT, VIDEO_URL, PREVIEW_IMAGE_URL, AUTHOR_ID, TYPE_ID, + CREATED_AT, MODIFIED_AT, PUBLISHED_AT, STATUS, IMPORTANT) +VALUES ('Title', 'Preview', 'Content', NULL, 'https://i.imgur.com/1VU2fe5.png', 1, 1, + '06.06.2021 03:56:37.700332', '06.06.2021 03:56:37.700332', '06.06.2021 03:56:37.700332', + 'PUBLISHED', TRUE), + ('Another title', 'Preview', 'Content', NULL, 'https://i.imgur.com/1VU2fe5.png', 2, 2, + '06.06.2021 03:56:37.700332', '06.06.2021 03:56:37.700332', '06.06.2021 03:56:37.700332', + 'PUBLISHED', FALSE), + ('Third title', 'Preview', 'Content', NULL, 'https://i.imgur.com/1VU2fe5.png', 1, 2, + '06.06.2021 03:56:37.700332', '06.06.2021 03:56:37.700332', '06.06.2021 03:56:37.700332', + 'PUBLISHED', NULL), + ('Fourth title MODERATION_SECOND_SIGN', 'Preview', 'Content', NULL, 'https://i.imgur.com/1VU2fe5.png', 1, 2, + '06.06.2021 03:56:37.700332', '06.06.2021 03:56:37.700332', NULL, + 'MODERATION_SECOND_SIGN', TRUE), + ('Fifth title MODERATION_FIRST_SIGN', 'Preview', 'Content', NULL, 'https://i.imgur.com/1VU2fe5.png', 1, 2, + '06.06.2021 03:56:37.700332', '06.06.2021 03:56:37.700332', NULL, + 'MODERATION_FIRST_SIGN', FALSE), + ('Sixth title DRAFT', 'Preview', 'Content', NULL, 'https://i.imgur.com/1VU2fe5.png', 1, 1, + '06.06.2021 03:56:37.700332', '06.06.2021 03:56:37.700332', NULL, + 'DRAFT', TRUE); + + +INSERT INTO public.posts_directions (post_id, direction_id) +VALUES (1, 2), + (2, 1), + (2, 3), + (3, 3), + (3, 5), + (3, 6), + (4, 1), + (4, 2), + (4, 4), + (5, 1), + (5, 2), + (5, 4), + (6, 1), + (6, 2), + (6, 4); + +INSERT INTO public.posts_origins(post_id, origin_id) +VALUES (1, 1), + (2, 2), + (3, 1), + (4, 3), + (5, 1), + (6, 3); + +INSERT INTO public.posts_tags(post_id, tag_id) +VALUES (1, 1), + (1, 2), + (2, 3), + (2, 4), + (2, 2), + (4, 6), + (4, 5); diff --git a/src/main/java/com/softserveinc/dokazovi/dto/origin/OriginDTO.java b/src/main/java/com/softserveinc/dokazovi/dto/origin/OriginDTO.java index 72eb0d17..b91c6594 100644 --- a/src/main/java/com/softserveinc/dokazovi/dto/origin/OriginDTO.java +++ b/src/main/java/com/softserveinc/dokazovi/dto/origin/OriginDTO.java @@ -23,6 +23,6 @@ public class OriginDTO { @NotBlank(message = "Origin name cannot be empty") private String name; - @NotBlank(message = "Origin parameter cannot be empty") - private String parameter; + @NotBlank(message = "Origin parameters cannot be empty") + private String parameters; } diff --git a/src/main/java/com/softserveinc/dokazovi/exception/ForbiddenPermissionsException.java b/src/main/java/com/softserveinc/dokazovi/exception/ForbiddenPermissionsException.java index 2e565235..bb1fd8cb 100644 --- a/src/main/java/com/softserveinc/dokazovi/exception/ForbiddenPermissionsException.java +++ b/src/main/java/com/softserveinc/dokazovi/exception/ForbiddenPermissionsException.java @@ -1,5 +1,9 @@ package com.softserveinc.dokazovi.exception; +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.ResponseStatus; + +@ResponseStatus(HttpStatus.FORBIDDEN) public class ForbiddenPermissionsException extends RuntimeException { private static final String MESSAGE = "Forbidden permission"; diff --git a/src/test/java/com/softserveinc/dokazovi/mapper/OriginMapperTest.java b/src/test/java/com/softserveinc/dokazovi/mapper/OriginMapperTest.java index d3d63f34..834eb161 100644 --- a/src/test/java/com/softserveinc/dokazovi/mapper/OriginMapperTest.java +++ b/src/test/java/com/softserveinc/dokazovi/mapper/OriginMapperTest.java @@ -28,6 +28,6 @@ void toOriginDTO_whenMaps_thenCorrect() { assertEquals(originDTO.getId(), originEntity.getId()); assertEquals(originDTO.getName(), originEntity.getName()); - assertEquals(originDTO.getParameter(), originEntity.getParameters()); + assertEquals(originDTO.getParameters(), originEntity.getParameters()); } } From 5568e8b09bb7c1179185e2684af729416c5902e6 Mon Sep 17 00:00:00 2001 From: Ihor Zakharko Date: Thu, 12 Aug 2021 15:48:49 +0300 Subject: [PATCH 05/10] Issue 433 administrator page security (#434) * add annotation to method in the PostController * remove unused imports * add endpoint get-authorities * implement endpoint get-authorities in the UserController * refactor endpoint get-authorities in the UserController * add documenatation to te method getAuthorities * add test getAuthoritiesTestNotFound to UserController * add test getAuthoritiesTestOk to the UserControllerTest * add annotation @Repository to the PassordResetTokenRepository * add assertion equals getAuthoritiesTestOk * add assertion null * change test method * remove test method and add check condition * add check condition * add new custom methodArgumentResolver --- .../dokazovi/controller/EndPoints.java | 1 + .../dokazovi/controller/PostController.java | 4 +- .../dokazovi/controller/UserController.java | 25 +++++++++ .../PasswordResetTokenRepository.java | 2 + .../controller/UserControllerTest.java | 55 ++++++++++++++++++- 5 files changed, 83 insertions(+), 4 deletions(-) diff --git a/src/main/java/com/softserveinc/dokazovi/controller/EndPoints.java b/src/main/java/com/softserveinc/dokazovi/controller/EndPoints.java index 2ddeae5c..ec4b9e60 100644 --- a/src/main/java/com/softserveinc/dokazovi/controller/EndPoints.java +++ b/src/main/java/com/softserveinc/dokazovi/controller/EndPoints.java @@ -27,6 +27,7 @@ public final class EndPoints { public static final String USER_RANDOM_EXPERTS = "/random-experts"; public static final String USER_ALL_EXPERTS = "/all-experts"; public static final String USER_GET_USER_BY_ID = "/{userId}"; + public static final String USER_GET_AUTHORITIES = "/get-authorities"; public static final String POST_GET_USER_BY_ID = "/{userId}"; public static final String USER_GET_CURRENT_USER = "/me"; public static final String TAG = "/tag"; diff --git a/src/main/java/com/softserveinc/dokazovi/controller/PostController.java b/src/main/java/com/softserveinc/dokazovi/controller/PostController.java index 23a785d4..42b947ac 100644 --- a/src/main/java/com/softserveinc/dokazovi/controller/PostController.java +++ b/src/main/java/com/softserveinc/dokazovi/controller/PostController.java @@ -406,8 +406,10 @@ public Integer getPostViewCount(@RequestParam String url) { */ @ApiPageable @ApiOperation(value = "Get published posts sorted by important image url presence then by createdAt, " - + "filtered by directions, types and origins.") + + "filtered by directions, types and origins.", + authorizations = {@Authorization(value = "Authorization")}) @GetMapping(POST_GET_BY_IMPORTANT_IMAGE) + @PreAuthorize("hasAuthority('SET_IMPORTANCE')") public ResponseEntity> findPublishedNotImportantPostsSortedByImportantImagePresence( @PageableDefault Pageable pageable, @ApiParam(value = "Multiple comma-separated direction IDs, e.g. ?directions=1,2,3,4", type = "string") diff --git a/src/main/java/com/softserveinc/dokazovi/controller/UserController.java b/src/main/java/com/softserveinc/dokazovi/controller/UserController.java index a413ed16..677b4ceb 100644 --- a/src/main/java/com/softserveinc/dokazovi/controller/UserController.java +++ b/src/main/java/com/softserveinc/dokazovi/controller/UserController.java @@ -24,6 +24,7 @@ import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; +import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; @@ -35,12 +36,14 @@ import org.springframework.web.bind.annotation.RestController; import javax.validation.Valid; +import java.util.Collection; import java.util.Set; import static com.softserveinc.dokazovi.controller.EndPoints.USER; import static com.softserveinc.dokazovi.controller.EndPoints.USER_ALL_EXPERTS; import static com.softserveinc.dokazovi.controller.EndPoints.USER_CHANGE_PASSWORD; import static com.softserveinc.dokazovi.controller.EndPoints.USER_CHECK_TOKEN; +import static com.softserveinc.dokazovi.controller.EndPoints.USER_GET_AUTHORITIES; import static com.softserveinc.dokazovi.controller.EndPoints.USER_GET_CURRENT_USER; import static com.softserveinc.dokazovi.controller.EndPoints.USER_GET_USER_BY_ID; import static com.softserveinc.dokazovi.controller.EndPoints.USER_RANDOM_EXPERTS; @@ -207,4 +210,26 @@ public ResponseEntity changePassword( } return ResponseEntity.status(HttpStatus.OK).build(); } + + /** + * Gets current user's authorities. + * Checks if userPrincipal not null. + * If no - returns HttpStatus 'NOT FOUND'. + * + * @param userPrincipal authorities of user that we want to get + * @return found user's authorities and HttpStatus 'OK' + */ + + @GetMapping(USER_GET_AUTHORITIES) + @ApiOperation(value = "Get user's authorities", + authorizations = {@Authorization(value = "Authorization")}) + public ResponseEntity> getAuthorities( + @AuthenticationPrincipal UserPrincipal userPrincipal) { + Collection authorities = null; + if (userPrincipal != null) { + authorities = userPrincipal.getAuthorities(); + } + return ResponseEntity.status(authorities != null + ? HttpStatus.OK : HttpStatus.NOT_FOUND).body(authorities); + } } diff --git a/src/main/java/com/softserveinc/dokazovi/repositories/PasswordResetTokenRepository.java b/src/main/java/com/softserveinc/dokazovi/repositories/PasswordResetTokenRepository.java index a284c481..31065d01 100644 --- a/src/main/java/com/softserveinc/dokazovi/repositories/PasswordResetTokenRepository.java +++ b/src/main/java/com/softserveinc/dokazovi/repositories/PasswordResetTokenRepository.java @@ -2,9 +2,11 @@ import com.softserveinc.dokazovi.entity.PasswordResetTokenEntity; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; import java.util.Optional; +@Repository public interface PasswordResetTokenRepository extends JpaRepository { Optional findByToken (String token); diff --git a/src/test/java/com/softserveinc/dokazovi/controller/UserControllerTest.java b/src/test/java/com/softserveinc/dokazovi/controller/UserControllerTest.java index e3b4c188..cf69aec7 100644 --- a/src/test/java/com/softserveinc/dokazovi/controller/UserControllerTest.java +++ b/src/test/java/com/softserveinc/dokazovi/controller/UserControllerTest.java @@ -8,6 +8,7 @@ import com.softserveinc.dokazovi.service.MailSenderService; import com.softserveinc.dokazovi.service.PasswordResetTokenService; import com.softserveinc.dokazovi.service.UserService; +import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -16,26 +17,38 @@ import org.mockito.junit.jupiter.MockitoExtension; import org.mockito.junit.jupiter.MockitoSettings; import org.mockito.quality.Strictness; +import org.springframework.core.MethodParameter; import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Pageable; import org.springframework.data.web.PageableHandlerMethodArgumentResolver; import org.springframework.http.MediaType; +import org.springframework.security.core.GrantedAuthority; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.setup.MockMvcBuilders; +import org.springframework.web.bind.support.WebDataBinderFactory; +import org.springframework.web.context.request.NativeWebRequest; +import org.springframework.web.method.support.HandlerMethodArgumentResolver; +import org.springframework.web.method.support.ModelAndViewContainer; import java.time.LocalDateTime; +import java.util.Collection; import java.util.Set; import static com.softserveinc.dokazovi.controller.EndPoints.USER; import static com.softserveinc.dokazovi.controller.EndPoints.USER_ALL_EXPERTS; import static com.softserveinc.dokazovi.controller.EndPoints.USER_CHANGE_PASSWORD; import static com.softserveinc.dokazovi.controller.EndPoints.USER_CHECK_TOKEN; +import static com.softserveinc.dokazovi.controller.EndPoints.USER_GET_AUTHORITIES; import static com.softserveinc.dokazovi.controller.EndPoints.USER_GET_CURRENT_USER; import static com.softserveinc.dokazovi.controller.EndPoints.USER_RANDOM_EXPERTS; import static com.softserveinc.dokazovi.controller.EndPoints.USER_RESET_PASSWORD; import static com.softserveinc.dokazovi.controller.EndPoints.USER_UPDATE_PASSWORD; +import static com.softserveinc.dokazovi.entity.enumerations.RolePermission.DELETE_POST; +import static com.softserveinc.dokazovi.entity.enumerations.RolePermission.SAVE_OWN_PUBLICATION; +import static com.softserveinc.dokazovi.entity.enumerations.RolePermission.SAVE_TAG; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; @@ -49,7 +62,7 @@ class UserControllerTest { private MockMvc mockMvc; @Mock - UserPrincipal userPrincipal; + private UserPrincipal userPrincipal; @Mock private UserService userService; @Mock @@ -59,11 +72,24 @@ class UserControllerTest { @InjectMocks private UserController userController; + private HandlerMethodArgumentResolver methodArgumentResolver = new HandlerMethodArgumentResolver() { + @Override + public boolean supportsParameter(MethodParameter parameter) { + return parameter.getParameterType().isAssignableFrom(UserPrincipal.class); + } + + @Override + public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, + NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception { + return userPrincipal; + } + }; + @BeforeEach public void init() { this.mockMvc = MockMvcBuilders .standaloneSetup(userController) - .setCustomArgumentResolvers(new PageableHandlerMethodArgumentResolver()) + .setCustomArgumentResolvers(methodArgumentResolver, new PageableHandlerMethodArgumentResolver()) .build(); } @@ -205,7 +231,7 @@ void getCurrentUser_notFound() throws Exception { .id(existingUserId) .build(); - when(userService.findExpertById(any(Integer.class))).thenReturn(userDTO); + when(userService.findExpertById(any(Integer.class))).thenReturn(null); when(userPrincipal.getId()).thenReturn(9); mockMvc.perform(get(uri)).andExpect(status().isNotFound()); } @@ -294,4 +320,27 @@ void checkTokenTest() throws Exception { when(passwordResetTokenService.validatePasswordResetToken(token)).thenReturn(true); mockMvc.perform(get(uri)).andExpect(status().isOk()); } + + @Test + void getAuthoritiesTestNotFound() throws Exception { + String uri = USER + USER_GET_AUTHORITIES; + when(userPrincipal.getAuthorities()).thenReturn(null); + mockMvc.perform(get(uri)).andExpect(status().isNotFound()); + Collection actual = userPrincipal.getAuthorities(); + Assertions.assertNull(actual); + } + + @Test + void getAuthoritiesTestIsOk() throws Exception { + Collection expected = Set.of(SAVE_OWN_PUBLICATION, + SAVE_TAG, + DELETE_POST); + String uri = USER + USER_GET_AUTHORITIES; + doReturn(expected).when(userPrincipal).getAuthorities(); + Collection actual = userPrincipal.getAuthorities(); + mockMvc.perform(get(uri)).andExpect(status().isOk()); + Assertions.assertNotNull(actual); + Assertions.assertNotNull(userPrincipal); + Assertions.assertEquals(expected, actual); + } } From 9c83a20cfaa74a41f41432205355a6d9ee1f0b9c Mon Sep 17 00:00:00 2001 From: Stanislav Kucher Date: Thu, 12 Aug 2021 17:39:46 +0300 Subject: [PATCH 06/10] Six users' test data was appended --- .../db/testdata/V13.5__add_new_users.sql | 120 ++++++++++++++++++ 1 file changed, 120 insertions(+) create mode 100644 src/main/resources/db/testdata/V13.5__add_new_users.sql diff --git a/src/main/resources/db/testdata/V13.5__add_new_users.sql b/src/main/resources/db/testdata/V13.5__add_new_users.sql new file mode 100644 index 00000000..7ffcfe72 --- /dev/null +++ b/src/main/resources/db/testdata/V13.5__add_new_users.sql @@ -0,0 +1,120 @@ +-- Adding a new institution located in Chernihiv, Chernihiv oblast. Primary_key with value #11 will be assigned to it. +INSERT INTO public.institutions (name, address, city_id) +VALUES ('Чернігівська міська лікарня', 'вул. 1 Травня, 161', 437); +-- The institution in Chernihiv was added + +-- Adding 6 new users (authors) with the "Doctor" role from Chernihiv oblast +-- Adding the 1st user. User_id with value #61 and Doctor_id with value #57 will be assigned to him. +INSERT INTO public.users (email, password, status, first_name, last_name, phone, created_at, avatar, enabled, + role_id) +VALUES ('kimkwan@mail.com', '$2y$10$SvnpkOhhs4I6J7w3jLcvIe5mWakxJ3ADtquH5Tv0x70rmjPZifTSi', 'ACTIVE', + 'Kim', 'Kwan', '+380637144441', '2021-02-16 03:56:31.444571', 'https://i.pravatar.cc/300?img=1', true, + 3); +INSERT INTO public.providers (provider_name, email, user_id_by_provider, user_id) +VALUES ('LOCAL', 'kimkwan@mail.com', '61', 61); +INSERT INTO public.doctors (qualification, bio, user_id, institution_id, promotion_scale, promotion_level, + published_posts, rating, social_network) +VALUES ('Главрач', + 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.', + 61, 11, 1, 0, 0, 1, 'https://www.linkedin.com/'); +INSERT INTO public.doctors_institutions (doctor_id, institution_id) +VALUES (57, 11); +INSERT INTO public.doctors_directions (doctor_id, direction_id) +VALUES (57, 1); +-- The 1st author was added + +-- Adding the 2nd user. User_id with value #62 and Doctor_id with value #58 will be assigned to him. +INSERT INTO public.users (email, password, status, first_name, last_name, phone, created_at, avatar, enabled, + role_id) +VALUES ('cixitaihou@mail.com', '$2y$10$SvnpkOhhs4I6J7w3jLcvIe5mWakxJ3ADtquH5Tv0x70rmjPZifTSi', 'ACTIVE', + 'Cixi', 'Taihou', '+380637244441', '2021-02-16 03:56:32.444571', 'https://i.pravatar.cc/300?img=2', true, + 3); +INSERT INTO public.providers (provider_name, email, user_id_by_provider, user_id) +VALUES ('LOCAL', 'cixitaihou@mail.com', '62', 62); +INSERT INTO public.doctors (qualification, bio, user_id, institution_id, promotion_scale, promotion_level, + published_posts, rating, social_network) +VALUES ('Замглаврача', + 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.', + 62, 11, 1, 0, 0, 1, 'https://www.linkedin.com/'); +INSERT INTO public.doctors_institutions (doctor_id, institution_id) +VALUES (58, 11); +INSERT INTO public.doctors_directions (doctor_id, direction_id) +VALUES (58, 2); +-- The 2nd author was added + +-- Adding the 3rd user. User_id with value #63 and Doctor_id with value #59 will be assigned to him. +INSERT INTO public.users (email, password, status, first_name, last_name, phone, created_at, avatar, enabled, + role_id) +VALUES ('chiangkaishi@mail.com', '$2y$10$SvnpkOhhs4I6J7w3jLcvIe5mWakxJ3ADtquH5Tv0x70rmjPZifTSi', 'ACTIVE', + 'Chiang', 'Kaishi', '+380637344441', '2021-02-16 03:56:33.444571', 'https://i.pravatar.cc/300?img=3', true, + 3); +INSERT INTO public.providers (provider_name, email, user_id_by_provider, user_id) +VALUES ('LOCAL', 'chiangkaishi@mail.com', '63', 63); +INSERT INTO public.doctors (qualification, bio, user_id, institution_id, promotion_scale, promotion_level, + published_posts, rating, social_network) +VALUES ('Хірург', + 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.', + 63, 11, 1, 0, 0, 1, 'https://www.linkedin.com/'); +INSERT INTO public.doctors_institutions (doctor_id, institution_id) +VALUES (59, 11); +INSERT INTO public.doctors_directions (doctor_id, direction_id) +VALUES (59, 3); +-- The 3rd author was added + +-- Adding the 4th user. User_id with value #64 and Doctor_id with value #60 will be assigned to him. +INSERT INTO public.users (email, password, status, first_name, last_name, phone, created_at, avatar, enabled, + role_id) +VALUES ('yojiyamamoto@mail.com', '$2y$10$SvnpkOhhs4I6J7w3jLcvIe5mWakxJ3ADtquH5Tv0x70rmjPZifTSi', 'ACTIVE', + 'Yoji', 'Yamamoto', '+380637444441', '2021-02-16 03:56:34.444571', 'https://i.pravatar.cc/300?img=4', true, + 3); +INSERT INTO public.providers (provider_name, email, user_id_by_provider, user_id) +VALUES ('LOCAL', 'yojiyamamoto@mail.com', '64', 64); +INSERT INTO public.doctors (qualification, bio, user_id, institution_id, promotion_scale, promotion_level, + published_posts, rating, social_network) +VALUES ('Стоматолог', + 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.', + 64, 11, 1, 0, 0, 1, 'https://www.linkedin.com/'); +INSERT INTO public.doctors_institutions (doctor_id, institution_id) +VALUES (60, 11); +INSERT INTO public.doctors_directions (doctor_id, direction_id) +VALUES (60, 4); +-- The 4th author was added + +-- Adding the 5th user. User_id with value #65 and Doctor_id with value #61 will be assigned to him. +INSERT INTO public.users (email, password, status, first_name, last_name, phone, created_at, avatar, enabled, + role_id) +VALUES ('togoakira@mail.com', '$2y$10$SvnpkOhhs4I6J7w3jLcvIe5mWakxJ3ADtquH5Tv0x70rmjPZifTSi', 'ACTIVE', + 'Akira', 'Togo', '+380637544441', '2021-02-16 03:56:35.444571', 'https://i.pravatar.cc/300?img=5', true, + 3); +INSERT INTO public.providers (provider_name, email, user_id_by_provider, user_id) +VALUES ('LOCAL', 'togoakira@mail.com', '65', 65); +INSERT INTO public.doctors (qualification, bio, user_id, institution_id, promotion_scale, promotion_level, + published_posts, rating, social_network) +VALUES ('Педіатр', + 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.', + 65, 11, 1, 0, 0, 1, 'https://www.linkedin.com/'); +INSERT INTO public.doctors_institutions (doctor_id, institution_id) +VALUES (61, 11); +INSERT INTO public.doctors_directions (doctor_id, direction_id) +VALUES (61, 5); +-- The 5th author was added + +-- Adding the 6th user. User_id with value #66 and Doctor_id with value #62 will be assigned to him. +INSERT INTO public.users (email, password, status, first_name, last_name, phone, created_at, avatar, enabled, + role_id) +VALUES ('takedakano@mail.com', '$2y$10$SvnpkOhhs4I6J7w3jLcvIe5mWakxJ3ADtquH5Tv0x70rmjPZifTSi', 'ACTIVE', + 'Kano', 'Takeda', '+380637644441', '2021-02-16 03:56:35.444571', 'https://i.pravatar.cc/300?img=6', true, + 3); +INSERT INTO public.providers (provider_name, email, user_id_by_provider, user_id) +VALUES ('LOCAL', 'takedakano@mail.com', '66', 66); +INSERT INTO public.doctors (qualification, bio, user_id, institution_id, promotion_scale, promotion_level, + published_posts, rating, social_network) +VALUES ('Терапевт', + 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.', + 66, 11, 1, 0, 0, 1, 'https://www.linkedin.com/'); +INSERT INTO public.doctors_institutions (doctor_id, institution_id) +VALUES (62, 11); +INSERT INTO public.doctors_directions (doctor_id, direction_id) +VALUES (62, 6); +-- The 6th author was added +-- 6 new users (authors) with the "Doctor" role from Chernihiv oblast were added \ No newline at end of file From 6df1c32fd1d9e9192c647ed9260889a875921a52 Mon Sep 17 00:00:00 2001 From: KuzmaJava <44094852+KuzmaJava@users.noreply.github.com> Date: Mon, 16 Aug 2021 14:17:46 +0300 Subject: [PATCH 07/10] To fix bug regarding sorting of author's posts (#444) * Edited findAllByAuthorIdAndStatus method to findAllByAuthorIdAndStatusOrderByPublishedAtDesc in the PostRepository to sort by descending. * The tests were edited --- .../softserveinc/dokazovi/repositories/PostRepository.java | 3 ++- .../softserveinc/dokazovi/service/impl/PostServiceImpl.java | 5 +++-- .../dokazovi/service/impl/PostServiceImplTest.java | 4 ++-- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/src/main/java/com/softserveinc/dokazovi/repositories/PostRepository.java b/src/main/java/com/softserveinc/dokazovi/repositories/PostRepository.java index 3285ed20..ce11e725 100644 --- a/src/main/java/com/softserveinc/dokazovi/repositories/PostRepository.java +++ b/src/main/java/com/softserveinc/dokazovi/repositories/PostRepository.java @@ -32,7 +32,8 @@ Page findAllByDirectionsContainsAndTypeIdInAndTagsIdInAndStatus( DirectionEntity direction, Set typeId, Set tagId, PostStatus postStatus, Pageable pageable); - Page findAllByAuthorIdAndStatus(Integer authorId, PostStatus postStatus, Pageable pageable); + Page findAllByAuthorIdAndStatusOrderByPublishedAtDesc(Integer authorId, PostStatus postStatus, + Pageable pageable); Page findAllByAuthorIdAndTypeIdInAndStatus( Integer authorId, Set typeId, PostStatus postStatus, Pageable pageable); diff --git a/src/main/java/com/softserveinc/dokazovi/service/impl/PostServiceImpl.java b/src/main/java/com/softserveinc/dokazovi/service/impl/PostServiceImpl.java index 5e2a045d..c5cd02d4 100644 --- a/src/main/java/com/softserveinc/dokazovi/service/impl/PostServiceImpl.java +++ b/src/main/java/com/softserveinc/dokazovi/service/impl/PostServiceImpl.java @@ -255,7 +255,8 @@ public Page findLatestByPostTypesAndOrigins(Pageable pageable) public Page findAllByExpertAndTypeAndDirections(Integer expertId, Set typeId, Set directionId, Pageable pageable) { if (typeId == null && directionId == null) { - return postRepository.findAllByAuthorIdAndStatus(expertId, PostStatus.PUBLISHED, pageable) + return postRepository.findAllByAuthorIdAndStatusOrderByPublishedAtDesc(expertId, PostStatus.PUBLISHED, + pageable) .map(postMapper::toPostDTO); } if (typeId == null) { @@ -276,7 +277,7 @@ public Page findAllByExpertAndTypeAndStatus(Integer expertId, Set Date: Mon, 16 Aug 2021 14:22:16 +0300 Subject: [PATCH 08/10] Change set to list in the findLatestByPostTypesAndOrigins method's PostServiceImpl class (#443) Adjusted SQL queries per each section by adding to ORDER BY clause ordering by post ID DESC --- .../softserveinc/dokazovi/dto/post/PostMainPageDTO.java | 4 ++-- .../dokazovi/repositories/PostRepository.java | 8 ++++---- .../dokazovi/service/impl/PostServiceImpl.java | 8 ++++---- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/main/java/com/softserveinc/dokazovi/dto/post/PostMainPageDTO.java b/src/main/java/com/softserveinc/dokazovi/dto/post/PostMainPageDTO.java index 48c185a1..157d41c8 100644 --- a/src/main/java/com/softserveinc/dokazovi/dto/post/PostMainPageDTO.java +++ b/src/main/java/com/softserveinc/dokazovi/dto/post/PostMainPageDTO.java @@ -5,7 +5,7 @@ import lombok.Data; import lombok.NoArgsConstructor; -import java.util.Set; +import java.util.List; @Data @Builder @@ -14,5 +14,5 @@ public class PostMainPageDTO { private String fieldName; - private Set postDTOS; + private List postDTOS; } diff --git a/src/main/java/com/softserveinc/dokazovi/repositories/PostRepository.java b/src/main/java/com/softserveinc/dokazovi/repositories/PostRepository.java index ce11e725..30e8aaca 100644 --- a/src/main/java/com/softserveinc/dokazovi/repositories/PostRepository.java +++ b/src/main/java/com/softserveinc/dokazovi/repositories/PostRepository.java @@ -113,7 +113,7 @@ Page findAllByDirectionsAndByPostTypesAndByOrigins(Set type + " WHERE ORIGIN_ID = 1))" + " AND P1.STATUS IN ('PUBLISHED')" + " AND P1.TYPE_ID NOT IN (2)" - + " ORDER BY CREATED_AT DESC") + + " ORDER BY CREATED_AT DESC, P1.POST_ID DESC") Page findLatestByPostTypeExpertOpinion(Pageable pageable); @Query(nativeQuery = true, @@ -124,7 +124,7 @@ Page findAllByDirectionsAndByPostTypesAndByOrigins(Set type + " WHERE ORIGIN_ID = 2)) " + " AND P1.STATUS IN ('PUBLISHED') " + " AND P1.TYPE_ID NOT IN (2) " - + " ORDER BY CREATED_AT DESC ") + + " ORDER BY CREATED_AT DESC, P1.POST_ID DESC ") Page findLatestByPostTypeMedia(Pageable pageable); @@ -136,7 +136,7 @@ Page findAllByDirectionsAndByPostTypesAndByOrigins(Set type + " WHERE ORIGIN_ID = 3)) " + " AND P1.STATUS IN ('PUBLISHED') " + " AND P1.TYPE_ID NOT IN (2) " - + " ORDER BY CREATED_AT DESC ") + + " ORDER BY CREATED_AT DESC, P1.POST_ID DESC ") Page findLatestByPostTypeTranslation(Pageable pageable); @Query(nativeQuery = true, @@ -144,7 +144,7 @@ Page findAllByDirectionsAndByPostTypesAndByOrigins(Set type + " FROM POSTS P1 " + " WHERE P1.TYPE_ID IN (2) " + " AND P1.STATUS IN ('PUBLISHED') " - + " ORDER BY CREATED_AT DESC ") + + " ORDER BY CREATED_AT DESC, P1.POST_ID DESC ") Page findLatestByOriginVideo(Pageable pageable); @Query(nativeQuery = true, diff --git a/src/main/java/com/softserveinc/dokazovi/service/impl/PostServiceImpl.java b/src/main/java/com/softserveinc/dokazovi/service/impl/PostServiceImpl.java index c5cd02d4..af877d54 100644 --- a/src/main/java/com/softserveinc/dokazovi/service/impl/PostServiceImpl.java +++ b/src/main/java/com/softserveinc/dokazovi/service/impl/PostServiceImpl.java @@ -234,19 +234,19 @@ public Page findLatestByPostTypesAndOrigins(Pageable pageable) PostMainPageDTO expertOptions = PostMainPageDTO.builder() .fieldName("expertOpinion") .postDTOS(postRepository.findLatestByPostTypeExpertOpinion(PageRequest.of(pageable.getPageNumber(), 4)) - .map(postMapper::toPostDTO).toSet()).build(); + .map(postMapper::toPostDTO).toList()).build(); PostMainPageDTO media = PostMainPageDTO.builder() .fieldName("media") .postDTOS(postRepository.findLatestByPostTypeMedia(PageRequest.of(pageable.getPageNumber(), 4)) - .map(postMapper::toPostDTO).toSet()).build(); + .map(postMapper::toPostDTO).toList()).build(); PostMainPageDTO translation = PostMainPageDTO.builder() .fieldName("translation") .postDTOS(postRepository.findLatestByPostTypeTranslation(PageRequest.of(pageable.getPageNumber(), 4)) - .map(postMapper::toPostDTO).toSet()).build(); + .map(postMapper::toPostDTO).toList()).build(); PostMainPageDTO video = PostMainPageDTO.builder() .fieldName("video") .postDTOS(postRepository.findLatestByOriginVideo(PageRequest.of(pageable.getPageNumber(), 4)) - .map(postMapper::toPostDTO).toSet()).build(); + .map(postMapper::toPostDTO).toList()).build(); return new PageImpl<>(List.of(expertOptions, media, translation, video)); } From ca5714380a14341bce923db5f24060d328c4a278 Mon Sep 17 00:00:00 2001 From: Anton Sidliar <78530619+antoshaSid@users.noreply.github.com> Date: Mon, 16 Aug 2021 20:18:21 +0300 Subject: [PATCH 09/10] Filtering posts of users by directions properly (#445) --- .../dokazovi/controller/EndPoints.java | 1 + .../dokazovi/controller/UserController.java | 13 ++ .../repositories/DirectionRepository.java | 13 ++ .../dokazovi/service/DirectionService.java | 7 + .../service/impl/DirectionServiceImpl.java | 24 +++ .../V14__add_doctor_post_directions_table.sql | 170 ++++++++++++++++++ .../controller/UserControllerTest.java | 13 ++ .../impl/DirectionServiceImplTest.java | 21 +++ 8 files changed, 262 insertions(+) create mode 100644 src/main/resources/db/migration/V14__add_doctor_post_directions_table.sql diff --git a/src/main/java/com/softserveinc/dokazovi/controller/EndPoints.java b/src/main/java/com/softserveinc/dokazovi/controller/EndPoints.java index ec4b9e60..a426e583 100644 --- a/src/main/java/com/softserveinc/dokazovi/controller/EndPoints.java +++ b/src/main/java/com/softserveinc/dokazovi/controller/EndPoints.java @@ -48,6 +48,7 @@ public final class EndPoints { public static final String PLATFORM_INFORMATION = "/platform-information"; public static final String PLATFORM_INFORMATION_BY_ID = "/{infoId}"; public static final String POST_GET_BY_IMPORTANT_IMAGE = "/get-by-important-image"; + public static final String USER_EXPERT_ALL_POST_DIRECTIONS = "/experts/{expertId}/post-directions"; /** * Method that adds slash after each endpoint while calling diff --git a/src/main/java/com/softserveinc/dokazovi/controller/UserController.java b/src/main/java/com/softserveinc/dokazovi/controller/UserController.java index 677b4ceb..7b52b3d9 100644 --- a/src/main/java/com/softserveinc/dokazovi/controller/UserController.java +++ b/src/main/java/com/softserveinc/dokazovi/controller/UserController.java @@ -1,6 +1,7 @@ package com.softserveinc.dokazovi.controller; import com.softserveinc.dokazovi.annotations.ApiPageable; +import com.softserveinc.dokazovi.dto.direction.DirectionDTO; import com.softserveinc.dokazovi.dto.user.UserEmailDTO; import com.softserveinc.dokazovi.dto.user.UserDTO; import com.softserveinc.dokazovi.dto.user.UserEmailPasswordDTO; @@ -9,6 +10,7 @@ import com.softserveinc.dokazovi.entity.UserEntity; import com.softserveinc.dokazovi.pojo.UserSearchCriteria; import com.softserveinc.dokazovi.security.UserPrincipal; +import com.softserveinc.dokazovi.service.DirectionService; import com.softserveinc.dokazovi.service.PasswordResetTokenService; import com.softserveinc.dokazovi.service.UserService; import io.swagger.annotations.ApiOperation; @@ -37,12 +39,14 @@ import javax.validation.Valid; import java.util.Collection; +import java.util.List; import java.util.Set; import static com.softserveinc.dokazovi.controller.EndPoints.USER; import static com.softserveinc.dokazovi.controller.EndPoints.USER_ALL_EXPERTS; import static com.softserveinc.dokazovi.controller.EndPoints.USER_CHANGE_PASSWORD; import static com.softserveinc.dokazovi.controller.EndPoints.USER_CHECK_TOKEN; +import static com.softserveinc.dokazovi.controller.EndPoints.USER_EXPERT_ALL_POST_DIRECTIONS; import static com.softserveinc.dokazovi.controller.EndPoints.USER_GET_AUTHORITIES; import static com.softserveinc.dokazovi.controller.EndPoints.USER_GET_CURRENT_USER; import static com.softserveinc.dokazovi.controller.EndPoints.USER_GET_USER_BY_ID; @@ -60,6 +64,7 @@ public class UserController { private final UserService userService; + private final DirectionService directionService; private final PasswordResetTokenService passwordResetTokenService; /** @@ -103,6 +108,14 @@ public ResponseEntity> getAllExpertsByDirectionsAndByRegionsOrdere .body(userService.findAllExperts(userSearchCriteria, pageable)); } + @GetMapping(USER_EXPERT_ALL_POST_DIRECTIONS) + @ApiOperation(value = "Get list of all directions which is used in all posts of user") + public ResponseEntity> getAllDirectionsOfUserPosts(@PathVariable("expertId") Integer userId) { + return ResponseEntity + .status(HttpStatus.OK) + .body(directionService.findAllDirectionsOfPostsByUserId(userId)); + } + /** * Gets the user by its id. * Checks if the user exists. If no - returns HttpStatus 'NOT FOUND'. diff --git a/src/main/java/com/softserveinc/dokazovi/repositories/DirectionRepository.java b/src/main/java/com/softserveinc/dokazovi/repositories/DirectionRepository.java index 42185da6..5bc7632c 100644 --- a/src/main/java/com/softserveinc/dokazovi/repositories/DirectionRepository.java +++ b/src/main/java/com/softserveinc/dokazovi/repositories/DirectionRepository.java @@ -4,6 +4,7 @@ import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Modifying; import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; import org.springframework.stereotype.Repository; import java.util.List; @@ -53,5 +54,17 @@ public interface DirectionRepository extends JpaRepository findAllDirectionsByUserId(Integer userId); + + /** + * Gets all directions of posts which user has + * + * @param id id of user + * @return list of directions + */ + @Query(nativeQuery = true, value = "SELECT * FROM directions d " + + "WHERE d.direction_id IN " + + "(SELECT DISTINCT direction_id FROM public.doctor_post_directions " + + "WHERE doctor_id = (:doctorId))") + List findAllDirectionsOfPostsByUserId(@Param("doctorId") Integer id); } diff --git a/src/main/java/com/softserveinc/dokazovi/service/DirectionService.java b/src/main/java/com/softserveinc/dokazovi/service/DirectionService.java index 3bf430ef..addfe482 100644 --- a/src/main/java/com/softserveinc/dokazovi/service/DirectionService.java +++ b/src/main/java/com/softserveinc/dokazovi/service/DirectionService.java @@ -1,6 +1,7 @@ package com.softserveinc.dokazovi.service; import com.softserveinc.dokazovi.dto.direction.DirectionDTO; +import com.softserveinc.dokazovi.entity.DirectionEntity; import java.util.List; @@ -10,7 +11,13 @@ public interface DirectionService { List findAllDirectionsByUserId(Integer userId); + List findAllDirectionsOfPostsByUserId(Integer userId); + + DirectionEntity getById(Integer id); + void updateDirectionsHasDoctorsStatus(); void updateDirectionsHasPostsStatus(); + + } diff --git a/src/main/java/com/softserveinc/dokazovi/service/impl/DirectionServiceImpl.java b/src/main/java/com/softserveinc/dokazovi/service/impl/DirectionServiceImpl.java index e67da6f1..fe6b2320 100644 --- a/src/main/java/com/softserveinc/dokazovi/service/impl/DirectionServiceImpl.java +++ b/src/main/java/com/softserveinc/dokazovi/service/impl/DirectionServiceImpl.java @@ -1,6 +1,7 @@ package com.softserveinc.dokazovi.service.impl; import com.softserveinc.dokazovi.dto.direction.DirectionDTO; +import com.softserveinc.dokazovi.entity.DirectionEntity; import com.softserveinc.dokazovi.mapper.DirectionMapper; import com.softserveinc.dokazovi.repositories.DirectionRepository; import com.softserveinc.dokazovi.service.DirectionService; @@ -50,6 +51,29 @@ public List findAllDirectionsByUserId(Integer userId) { .collect(Collectors.toList()); } + /** + * + * @param userId id of user + * @return list of directions from all user posts + */ + @Override + public List findAllDirectionsOfPostsByUserId(Integer userId) { + return directionRepository.findAllDirectionsOfPostsByUserId(userId).stream() + .map(directionMapper::toDirectionDTO) + .collect(Collectors.toList()); + } + + /** + * Gets direction by id + * + * @param id direction id + * @return direction with appropriate id + */ + @Override + public DirectionEntity getById(Integer id) { + return directionRepository.findById(id).orElse(null); + } + /** * Updates the directions status depending on the availability of doctors in it. * Runs every four hours diff --git a/src/main/resources/db/migration/V14__add_doctor_post_directions_table.sql b/src/main/resources/db/migration/V14__add_doctor_post_directions_table.sql new file mode 100644 index 00000000..e7cc0d04 --- /dev/null +++ b/src/main/resources/db/migration/V14__add_doctor_post_directions_table.sql @@ -0,0 +1,170 @@ +-- +-- Create a new table for storing doctors' by-post directions. +-- + +-- might be removed in migration +DROP TABLE IF EXISTS doctor_post_directions; + +-- Here we create a new table, based on all directions from all posts written by all doctors. +CREATE TABLE doctor_post_directions AS + SELECT DISTINCT d.doctor_id, p.post_id, pd.direction_id + FROM public.posts p + JOIN public.posts_directions pd ON pd.post_id = p.post_id + JOIN public.doctors d ON d.user_id = p.author_id; + +-- Here we make sure that we will not have any duplicate values in the table +ALTER TABLE doctor_post_directions + ADD CONSTRAINT doctor_post_directions_distinct + UNIQUE (doctor_id, post_id, direction_id); + +-- Make sure values in the table cannot be null, and that entries get deleted properly (cascade) +ALTER TABLE doctor_post_directions + ALTER COLUMN doctor_id + SET NOT NULL; +ALTER TABLE doctor_post_directions + ADD CONSTRAINT doctor_fk + FOREIGN KEY (doctor_id) + REFERENCES public.doctors (doctor_id) + ON DELETE CASCADE; + +ALTER TABLE doctor_post_directions + ALTER COLUMN post_id + SET NOT NULL; +ALTER TABLE doctor_post_directions + ADD CONSTRAINT post_fk + FOREIGN KEY (post_id) + REFERENCES public.posts (post_id) + ON DELETE CASCADE; + +ALTER TABLE doctor_post_directions + ALTER COLUMN direction_id + SET NOT NULL; +ALTER TABLE doctor_post_directions + ADD CONSTRAINT direction_fk + FOREIGN KEY (direction_id) + REFERENCES public.directions (direction_id) + ON DELETE CASCADE; + +-- +-- Handle creating post direction entries +-- + +CREATE OR REPLACE FUNCTION dpdv_handle_post_direction_insert() +RETURNS TRIGGER +AS $$ +DECLARE + VAR_USER_ID INTEGER; + VAR_DOCTOR_ID +INTEGER; +BEGIN + -- Get author of the post referenced by the direction + SELECT author_id + FROM public.posts p + WHERE p.post_id = NEW.post_id INTO VAR_USER_ID; + + -- If there is a doctor with such a user_id + IF (SELECT EXISTS(SELECT 1 FROM public.doctors d WHERE d.user_id = VAR_USER_ID)) THEN + -- Get his doctor_id + SELECT doctor_id + FROM public.doctors d + WHERE d.user_id = VAR_USER_ID INTO VAR_DOCTOR_ID; + -- Add a corresponding entry to our table + INSERT INTO public.doctor_post_directions (doctor_id, post_id, direction_id) + VALUES (VAR_DOCTOR_ID, NEW.post_id, NEW.direction_id) ON CONFLICT DO NOTHING; + END IF; + RETURN NEW; +END; +$$ +LANGUAGE plpgsql; + +-- might be removed in the migration +DROP TRIGGER IF EXISTS dpdv_handle_post_direction_insert_trigger + ON public.posts_directions; +-- make sure the function above gets executed when we get a new post direction +CREATE TRIGGER dpdv_handle_post_direction_insert_trigger + AFTER INSERT + ON public.posts_directions + FOR EACH ROW EXECUTE PROCEDURE dpdv_handle_post_direction_insert(); + +-- +-- Handle deleting post direction entries +-- + +CREATE OR REPLACE FUNCTION dpdv_handle_post_direction_delete() +RETURNS TRIGGER +AS $$ +DECLARE + VAR_USER_ID INTEGER; + VAR_DOCTOR_ID +INTEGER; +BEGIN + -- Get author of the post referenced by the direction + SELECT author_id + FROM public.posts p + WHERE p.post_id = OLD.post_id INTO VAR_USER_ID; + + -- If there is a doctor with such a user_id + IF (SELECT EXISTS(SELECT 1 FROM public.doctors d WHERE d.user_id = VAR_USER_ID)) THEN + -- Get his doctor_id + SELECT doctor_id + FROM public.doctors d + WHERE d.user_id = VAR_USER_ID INTO VAR_DOCTOR_ID; + + -- And remove the corresponding entry from our table + DELETE + FROM public.doctor_post_directions dpd + WHERE dpd.doctor_id = VAR_DOCTOR_ID + AND dpd.post_id = OLD.post_id + AND dpd.direction_id = OLD.direction_id; + END IF; + RETURN NEW; +END; +$$ +LANGUAGE plpgsql; + +-- feel free to remove this when making a migration +DROP TRIGGER IF EXISTS dpdv_handle_post_direction_delete + ON public.posts_directions; + +-- make sure the function above gets executed when a post direction is deleted +CREATE TRIGGER dpdv_handle_post_direction_delete + BEFORE DELETE + ON public.posts_directions + FOR EACH ROW EXECUTE PROCEDURE dpdv_handle_post_direction_delete(); + +-- +-- Handle changing post author +-- + +CREATE OR REPLACE FUNCTION dpdv_handle_update_post_author() +RETURNS TRIGGER +AS $$ +DECLARE + VAR_NEW_DOCTOR_ID INTEGER; +BEGIN + -- Do not do anything if the author id is unchanged + IF NOT (OLD.author_id = NEW.author_id) THEN + -- Get the doctor_id + SELECT doctor_id + FROM public.doctors d + WHERE d.user_id = NEW.author_id INTO VAR_NEW_DOCTOR_ID; + + -- Update all entries in our table with our post_id with the new doctor_id + UPDATE public.doctor_post_directions dpd + SET doctor_id = VAR_NEW_DOCTOR_ID + WHERE dpd.post_id = NEW.post_id; + END IF; + RETURN NEW; +END; +$$ +LANGUAGE plpgsql; + +-- This isn't needed in the migration +DROP TRIGGER IF EXISTS dpdv_handle_update_post_author_trigger + ON public.posts; + +-- Make sure the function above gets executed when a post gets updated +CREATE TRIGGER dpdv_handle_update_post_author_trigger + AFTER UPDATE + ON public.posts + FOR EACH ROW EXECUTE PROCEDURE dpdv_handle_update_post_author(); \ No newline at end of file diff --git a/src/test/java/com/softserveinc/dokazovi/controller/UserControllerTest.java b/src/test/java/com/softserveinc/dokazovi/controller/UserControllerTest.java index cf69aec7..c926c683 100644 --- a/src/test/java/com/softserveinc/dokazovi/controller/UserControllerTest.java +++ b/src/test/java/com/softserveinc/dokazovi/controller/UserControllerTest.java @@ -5,6 +5,7 @@ import com.softserveinc.dokazovi.entity.UserEntity; import com.softserveinc.dokazovi.pojo.UserSearchCriteria; import com.softserveinc.dokazovi.security.UserPrincipal; +import com.softserveinc.dokazovi.service.DirectionService; import com.softserveinc.dokazovi.service.MailSenderService; import com.softserveinc.dokazovi.service.PasswordResetTokenService; import com.softserveinc.dokazovi.service.UserService; @@ -32,6 +33,7 @@ import java.time.LocalDateTime; import java.util.Collection; +import java.util.Collections; import java.util.Set; import static com.softserveinc.dokazovi.controller.EndPoints.USER; @@ -49,6 +51,7 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; @@ -66,6 +69,8 @@ class UserControllerTest { @Mock private UserService userService; @Mock + private DirectionService directionService; + @Mock private PasswordResetTokenService passwordResetTokenService; @Mock private MailSenderService mailSenderService; @@ -343,4 +348,12 @@ void getAuthoritiesTestIsOk() throws Exception { Assertions.assertNotNull(userPrincipal); Assertions.assertEquals(expected, actual); } + + @Test + void getAllDirectionsOfUserPostsTest() throws Exception { + when(directionService.findAllDirectionsOfPostsByUserId(1)).thenReturn(Collections.emptyList()); + mockMvc.perform(get(USER + "/experts/" + "1" + "/post-directions")) + .andExpect(status().isOk()); + verify(directionService, times(1)).findAllDirectionsOfPostsByUserId(1); + } } diff --git a/src/test/java/com/softserveinc/dokazovi/service/impl/DirectionServiceImplTest.java b/src/test/java/com/softserveinc/dokazovi/service/impl/DirectionServiceImplTest.java index adf2f2c2..b901fe12 100644 --- a/src/test/java/com/softserveinc/dokazovi/service/impl/DirectionServiceImplTest.java +++ b/src/test/java/com/softserveinc/dokazovi/service/impl/DirectionServiceImplTest.java @@ -1,15 +1,19 @@ package com.softserveinc.dokazovi.service.impl; +import com.softserveinc.dokazovi.dto.direction.DirectionDTO; import com.softserveinc.dokazovi.entity.DirectionEntity; import com.softserveinc.dokazovi.mapper.DirectionMapper; import com.softserveinc.dokazovi.repositories.DirectionRepository; +import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.InjectMocks; 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.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.times; @@ -59,4 +63,21 @@ void updateDirectionsHasPostsStatusTest() { verify(directionRepository, times(1)) .updateDirectionsHasPostsStatus(); } + + @Test + void getByIdTest() { + when(directionRepository.findById(3)).thenReturn(Optional.of(new DirectionEntity())); + DirectionEntity expected = directionService.getById(3); + DirectionEntity actual = new DirectionEntity(); + Assertions.assertEquals(expected, actual); + } + + @Test + void findAllDirectionsOfPostsByUserIdTest() { + when(directionRepository.findAllDirectionsOfPostsByUserId(1)).thenReturn( + Collections.singletonList(new DirectionEntity())); + List list = directionService.findAllDirectionsOfPostsByUserId(1); + Assertions.assertEquals(list.size(), 1); + verify(directionRepository, times(1)).findAllDirectionsOfPostsByUserId(1); + } } From 4b14b10ffaa2718045c5860542ce9ad543e84806 Mon Sep 17 00:00:00 2001 From: Ihor Zakharko Date: Mon, 16 Aug 2021 21:14:01 +0300 Subject: [PATCH 10/10] Issue divide master develop db data (#446) * add application-prod.properties * add V13.1__add_demo_data.sql * add posts to the V13.1__add_demo_data.sql * add has_posts all true to the V13.1__add_demo_data.sql * rename V13.1__add_demo_data.sql to 14.1 --- .../resources/application-prod.properties | 1 + .../db/masterdata/V14.1__add_demo_data.sql | 1204 +++++++++++++++++ 2 files changed, 1205 insertions(+) create mode 100644 src/main/resources/application-prod.properties create mode 100644 src/main/resources/db/masterdata/V14.1__add_demo_data.sql diff --git a/src/main/resources/application-prod.properties b/src/main/resources/application-prod.properties new file mode 100644 index 00000000..119cc359 --- /dev/null +++ b/src/main/resources/application-prod.properties @@ -0,0 +1 @@ +spring.flyway.locations=classpath:/db/migration,classpath:/db/masterdata \ No newline at end of file diff --git a/src/main/resources/db/masterdata/V14.1__add_demo_data.sql b/src/main/resources/db/masterdata/V14.1__add_demo_data.sql new file mode 100644 index 00000000..3c4e2741 --- /dev/null +++ b/src/main/resources/db/masterdata/V14.1__add_demo_data.sql @@ -0,0 +1,1204 @@ +INSERT INTO public.tags (tag) +VALUES ('Профілактика'); +INSERT INTO public.tags (tag) +VALUES ('Комплекс'); +INSERT INTO public.tags (tag) +VALUES ('Ковід'); +INSERT INTO public.tags (tag) +VALUES ('Допомога'); +INSERT INTO public.tags (tag) +VALUES ('Міокард'); +INSERT INTO public.tags (tag) +VALUES ('ЕКГ'); + +INSERT INTO public.directions (label, color, name) +VALUES ('Covid-19', '#ef5350', 'covid-19'); +INSERT INTO public.directions (label, color, name) +VALUES ('Офтальмологія', '#98ef50', 'ophthalmology'); +INSERT INTO public.directions (label, color, name) +VALUES ('Хірургія', '#7aebbf', 'surgery'); +INSERT INTO public.directions (label, color, name) +VALUES ('Терапія', '#ffee58', 'therapy'); +INSERT INTO public.directions (label, color, name) +VALUES ('Вірусологія', '#da80e8', 'virology'); +INSERT INTO public.directions (label, color, name) +VALUES ('Кардіологія', '#00ffff', 'cardiology'); +INSERT INTO public.directions (label, color, name) +VALUES ('Педіатрія', '#993333', 'pediatrics'); + +INSERT INTO public.post_types (name) +VALUES ('Стаття'); +INSERT INTO public.post_types (name) +VALUES ('Відео'); +INSERT INTO public.post_types (name) +VALUES ('Допис'); +INSERT INTO public.post_types (name) +VALUES ('Переклад'); + +INSERT INTO public.roles (role_name) +VALUES ('Administrator'); +INSERT INTO public.roles (role_name) +VALUES ('Moderator'); +INSERT INTO public.roles (role_name) +VALUES ('Doctor'); + +INSERT INTO public.regions (name) +VALUES ('Автономна Республіка Крим'); +INSERT INTO public.regions (name) +VALUES ('Вінницька область'); +INSERT INTO public.regions (name) +VALUES ('Волинська область'); +INSERT INTO public.regions (name) +VALUES ('Дніпропетровська область'); +INSERT INTO public.regions (name) +VALUES ('Донецька область'); +INSERT INTO public.regions (name) +VALUES ('Житомирська область'); +INSERT INTO public.regions (name) +VALUES ('Закарпатська область'); +INSERT INTO public.regions (name) +VALUES ('Запорізька область'); +INSERT INTO public.regions (name) +VALUES ('Івано-Франківська область'); +INSERT INTO public.regions (name) +VALUES ('Київська область'); +INSERT INTO public.regions (name) +VALUES ('Кіровоградська область'); +INSERT INTO public.regions (name) +VALUES ('Луганська область'); +INSERT INTO public.regions (name) +VALUES ('Львівська область'); +INSERT INTO public.regions (name) +VALUES ('Миколаївська область'); +INSERT INTO public.regions (name) +VALUES ('Одеська область'); +INSERT INTO public.regions (name) +VALUES ('Полтавська область'); +INSERT INTO public.regions (name) +VALUES ('Рівненська область'); +INSERT INTO public.regions (name) +VALUES ('Сумська область'); +INSERT INTO public.regions (name) +VALUES ('Тернопільська область'); +INSERT INTO public.regions (name) +VALUES ('Харківська область'); +INSERT INTO public.regions (name) +VALUES ('Херсонська область'); +INSERT INTO public.regions (name) +VALUES ('Хмельницька область'); +INSERT INTO public.regions (name) +VALUES ('Черкаська область'); +INSERT INTO public.regions (name) +VALUES ('Чернівецька область'); +INSERT INTO public.regions (name) +VALUES ('Чернігівська область'); + +INSERT INTO public.cities (name, region_id) +VALUES ('Авдіївка', 5); +INSERT INTO public.cities (name, region_id) +VALUES ('Алмазна', 12); +INSERT INTO public.cities (name, region_id) +VALUES ('Алупка', 1); +INSERT INTO public.cities (name, region_id) +VALUES ('Алушта', 1); +INSERT INTO public.cities (name, region_id) +VALUES ('Алчевськ', 12); +INSERT INTO public.cities (name, region_id) +VALUES ('Амвросіївка', 5); +INSERT INTO public.cities (name, region_id) +VALUES ('Ананьїв', 15); +INSERT INTO public.cities (name, region_id) +VALUES ('Андрушівка', 6); +INSERT INTO public.cities (name, region_id) +VALUES ('Антрацит', 12); +INSERT INTO public.cities (name, region_id) +VALUES ('Апостолове', 4); +INSERT INTO public.cities (name, region_id) +VALUES ('Армянськ', 1); +INSERT INTO public.cities (name, region_id) +VALUES ('Арциз', 15); +INSERT INTO public.cities (name, region_id) +VALUES ('Балаклія', 20); +INSERT INTO public.cities (name, region_id) +VALUES ('Балта', 15); +INSERT INTO public.cities (name, region_id) +VALUES ('Бар', 2); +INSERT INTO public.cities (name, region_id) +VALUES ('Баранівка', 6); +INSERT INTO public.cities (name, region_id) +VALUES ('Барвінкове', 20); +INSERT INTO public.cities (name, region_id) +VALUES ('Батурин', 25); +INSERT INTO public.cities (name, region_id) +VALUES ('Бахмач', 25); +INSERT INTO public.cities (name, region_id) +VALUES ('Бахмут', 5); +INSERT INTO public.cities (name, region_id) +VALUES ('Бахчисарай', 1); +INSERT INTO public.cities (name, region_id) +VALUES ('Баштанка', 14); +INSERT INTO public.cities (name, region_id) +VALUES ('Белз', 13); +INSERT INTO public.cities (name, region_id) +VALUES ('Бердичів', 6); +INSERT INTO public.cities (name, region_id) +VALUES ('Бердянськ', 8); +INSERT INTO public.cities (name, region_id) +VALUES ('Берегове', 7); +INSERT INTO public.cities (name, region_id) +VALUES ('Бережани', 19); +INSERT INTO public.cities (name, region_id) +VALUES ('Березань', 10); +INSERT INTO public.cities (name, region_id) +VALUES ('Березівка', 15); +INSERT INTO public.cities (name, region_id) +VALUES ('Березне', 17); +INSERT INTO public.cities (name, region_id) +VALUES ('Берестечко', 3); +INSERT INTO public.cities (name, region_id) +VALUES ('Берислав', 21); +INSERT INTO public.cities (name, region_id) +VALUES ('Бершадь', 2); +INSERT INTO public.cities (name, region_id) +VALUES ('Бібрка', 13); +INSERT INTO public.cities (name, region_id) +VALUES ('Біла Церква', 10); +INSERT INTO public.cities (name, region_id) +VALUES ('Білгород-Дністровський', 15); +INSERT INTO public.cities (name, region_id) +VALUES ('Білицьке', 5); +INSERT INTO public.cities (name, region_id) +VALUES ('Білогірськ', 1); +INSERT INTO public.cities (name, region_id) +VALUES ('Білозерське', 5); +INSERT INTO public.cities (name, region_id) +VALUES ('Білопілля', 18); +INSERT INTO public.cities (name, region_id) +VALUES ('Біляївка', 15); +INSERT INTO public.cities (name, region_id) +VALUES ('Благовіщенське', 11); +INSERT INTO public.cities (name, region_id) +VALUES ('Бобринець', 11); +INSERT INTO public.cities (name, region_id) +VALUES ('Бобровиця', 25); +INSERT INTO public.cities (name, region_id) +VALUES ('Богодухів', 20); +INSERT INTO public.cities (name, region_id) +VALUES ('Богуслав', 10); +INSERT INTO public.cities (name, region_id) +VALUES ('Боково-Хрустальне', 12); +INSERT INTO public.cities (name, region_id) +VALUES ('Болград', 15); +INSERT INTO public.cities (name, region_id) +VALUES ('Болехів', 9); +INSERT INTO public.cities (name, region_id) +VALUES ('Борзна', 25); +INSERT INTO public.cities (name, region_id) +VALUES ('Борислав', 13); +INSERT INTO public.cities (name, region_id) +VALUES ('Бориспіль', 10); +INSERT INTO public.cities (name, region_id) +VALUES ('Борщів', 19); +INSERT INTO public.cities (name, region_id) +VALUES ('Боярка', 10); +INSERT INTO public.cities (name, region_id) +VALUES ('Бровари', 10); +INSERT INTO public.cities (name, region_id) +VALUES ('Броди', 13); +INSERT INTO public.cities (name, region_id) +VALUES ('Брянка', 12); +INSERT INTO public.cities (name, region_id) +VALUES ('Бунге', 5); +INSERT INTO public.cities (name, region_id) +VALUES ('Буринь', 18); +INSERT INTO public.cities (name, region_id) +VALUES ('Бурштин', 9); +INSERT INTO public.cities (name, region_id) +VALUES ('Буськ', 13); +INSERT INTO public.cities (name, region_id) +VALUES ('Буча', 10); +INSERT INTO public.cities (name, region_id) +VALUES ('Бучач', 19); +INSERT INTO public.cities (name, region_id) +VALUES ('Валки', 20); +INSERT INTO public.cities (name, region_id) +VALUES ('Вараш', 17); +INSERT INTO public.cities (name, region_id) +VALUES ('Василівка', 8); +INSERT INTO public.cities (name, region_id) +VALUES ('Васильків', 10); +INSERT INTO public.cities (name, region_id) +VALUES ('Ватутіне', 23); +INSERT INTO public.cities (name, region_id) +VALUES ('Вашківці', 24); +INSERT INTO public.cities (name, region_id) +VALUES ('Великі Мости', 13); +INSERT INTO public.cities (name, region_id) +VALUES ('Верхівцеве', 4); +INSERT INTO public.cities (name, region_id) +VALUES ('Верхньодніпровськ', 4); +INSERT INTO public.cities (name, region_id) +VALUES ('Вижниця', 24); +INSERT INTO public.cities (name, region_id) +VALUES ('Вилкове', 15); +INSERT INTO public.cities (name, region_id) +VALUES ('Винники', 13); +INSERT INTO public.cities (name, region_id) +VALUES ('Виноградів', 7); +INSERT INTO public.cities (name, region_id) +VALUES ('Вишгород', 10); +INSERT INTO public.cities (name, region_id) +VALUES ('Вишневе', 10); +INSERT INTO public.cities (name, region_id) +VALUES ('Вільногірськ', 4); +INSERT INTO public.cities (name, region_id) +VALUES ('Вільнянськ', 8); +INSERT INTO public.cities (name, region_id) +VALUES ('Вінниця', 2); +INSERT INTO public.cities (name, region_id) +VALUES ('Вовчанськ', 20); +INSERT INTO public.cities (name, region_id) +VALUES ('Вознесенівка', 12); +INSERT INTO public.cities (name, region_id) +VALUES ('Вознесенськ', 14); +INSERT INTO public.cities (name, region_id) +VALUES ('Волноваха', 5); +INSERT INTO public.cities (name, region_id) +VALUES ('Володимир-Волинський', 3); +INSERT INTO public.cities (name, region_id) +VALUES ('Волочиськ', 22); +INSERT INTO public.cities (name, region_id) +VALUES ('Ворожба', 18); +INSERT INTO public.cities (name, region_id) +VALUES ('Вуглегірськ', 5); +INSERT INTO public.cities (name, region_id) +VALUES ('Вугледар', 5); +INSERT INTO public.cities (name, region_id) +VALUES ('Гадяч', 16); +INSERT INTO public.cities (name, region_id) +VALUES ('Гайворон', 11); +INSERT INTO public.cities (name, region_id) +VALUES ('Гайсин', 2); +INSERT INTO public.cities (name, region_id) +VALUES ('Галич', 9); +INSERT INTO public.cities (name, region_id) +VALUES ('Генічеськ', 21); +INSERT INTO public.cities (name, region_id) +VALUES ('Герца', 24); +INSERT INTO public.cities (name, region_id) +VALUES ('Гірник', 5); +INSERT INTO public.cities (name, region_id) +VALUES ('Гірське', 12); +INSERT INTO public.cities (name, region_id) +VALUES ('Глиняни', 13); +INSERT INTO public.cities (name, region_id) +VALUES ('Глобине', 16); +INSERT INTO public.cities (name, region_id) +VALUES ('Глухів', 18); +INSERT INTO public.cities (name, region_id) +VALUES ('Гнівань', 2); +INSERT INTO public.cities (name, region_id) +VALUES ('Гола Пристань', 21); +INSERT INTO public.cities (name, region_id) +VALUES ('Голубівка', 12); +INSERT INTO public.cities (name, region_id) +VALUES ('Горішні Плавні', 16); +INSERT INTO public.cities (name, region_id) +VALUES ('Горлівка', 5); +INSERT INTO public.cities (name, region_id) +VALUES ('Городенка', 9); +INSERT INTO public.cities (name, region_id) +VALUES ('Городище', 23); +INSERT INTO public.cities (name, region_id) +VALUES ('Городня', 25); +INSERT INTO public.cities (name, region_id) +VALUES ('Городок', 13); +INSERT INTO public.cities (name, region_id) +VALUES ('Городок', 22); +INSERT INTO public.cities (name, region_id) +VALUES ('Горохів', 3); +INSERT INTO public.cities (name, region_id) +VALUES ('Гребінка', 16); +INSERT INTO public.cities (name, region_id) +VALUES ('Гуляйполе', 8); +INSERT INTO public.cities (name, region_id) +VALUES ('Дебальцеве', 5); +INSERT INTO public.cities (name, region_id) +VALUES ('Деражня', 22); +INSERT INTO public.cities (name, region_id) +VALUES ('Дергачі', 20); +INSERT INTO public.cities (name, region_id) +VALUES ('Джанкой', 1); +INSERT INTO public.cities (name, region_id) +VALUES ('Дніпро', 4); +INSERT INTO public.cities (name, region_id) +VALUES ('Дніпрорудне', 8); +INSERT INTO public.cities (name, region_id) +VALUES ('Добромиль', 13); +INSERT INTO public.cities (name, region_id) +VALUES ('Добропілля', 5); +INSERT INTO public.cities (name, region_id) +VALUES ('Довжанськ', 12); +INSERT INTO public.cities (name, region_id) +VALUES ('Докучаєвськ', 5); +INSERT INTO public.cities (name, region_id) +VALUES ('Долина', 9); +INSERT INTO public.cities (name, region_id) +VALUES ('Долинська', 11); +INSERT INTO public.cities (name, region_id) +VALUES ('Донецьк', 5); +INSERT INTO public.cities (name, region_id) +VALUES ('Дрогобич', 13); +INSERT INTO public.cities (name, region_id) +VALUES ('Дружба', 18); +INSERT INTO public.cities (name, region_id) +VALUES ('Дружківка', 5); +INSERT INTO public.cities (name, region_id) +VALUES ('Дубляни', 13); +INSERT INTO public.cities (name, region_id) +VALUES ('Дубно', 17); +INSERT INTO public.cities (name, region_id) +VALUES ('Дубровиця', 17); +INSERT INTO public.cities (name, region_id) +VALUES ('Дунаївці', 22); +INSERT INTO public.cities (name, region_id) +VALUES ('Енергодар', 8); +INSERT INTO public.cities (name, region_id) +VALUES ('Євпаторія', 1); +INSERT INTO public.cities (name, region_id) +VALUES ('Єнакієве', 5); +INSERT INTO public.cities (name, region_id) +VALUES ('Жашків', 23); +INSERT INTO public.cities (name, region_id) +VALUES ('Жданівка', 5); +INSERT INTO public.cities (name, region_id) +VALUES ('Жидачів', 13); +INSERT INTO public.cities (name, region_id) +VALUES ('Житомир', 6); +INSERT INTO public.cities (name, region_id) +VALUES ('Жмеринка', 2); +INSERT INTO public.cities (name, region_id) +VALUES ('Жовква', 13); +INSERT INTO public.cities (name, region_id) +VALUES ('Жовті Води', 4); +INSERT INTO public.cities (name, region_id) +VALUES ('Заводське', 16); +INSERT INTO public.cities (name, region_id) +VALUES ('Залізне', 5); +INSERT INTO public.cities (name, region_id) +VALUES ('Заліщики', 19); +INSERT INTO public.cities (name, region_id) +VALUES ('Запоріжжя', 8); +INSERT INTO public.cities (name, region_id) +VALUES ('Заставна', 24); +INSERT INTO public.cities (name, region_id) +VALUES ('Збараж', 19); +INSERT INTO public.cities (name, region_id) +VALUES ('Зборів', 19); +INSERT INTO public.cities (name, region_id) +VALUES ('Звенигородка', 23); +INSERT INTO public.cities (name, region_id) +VALUES ('Здолбунів', 17); +INSERT INTO public.cities (name, region_id) +VALUES ('Зеленодольськ', 4); +INSERT INTO public.cities (name, region_id) +VALUES ('Зимогір''я', 12); +INSERT INTO public.cities (name, region_id) +VALUES ('Зіньків', 16); +INSERT INTO public.cities (name, region_id) +VALUES ('Зміїв', 20); +INSERT INTO public.cities (name, region_id) +VALUES ('Знам''янка', 11); +INSERT INTO public.cities (name, region_id) +VALUES ('Золоте', 12); +INSERT INTO public.cities (name, region_id) +VALUES ('Золотоноша', 23); +INSERT INTO public.cities (name, region_id) +VALUES ('Золочів', 13); +INSERT INTO public.cities (name, region_id) +VALUES ('Зоринськ', 12); +INSERT INTO public.cities (name, region_id) +VALUES ('Зугрес', 5); +INSERT INTO public.cities (name, region_id) +VALUES ('Івано-Франківськ', 9); +INSERT INTO public.cities (name, region_id) +VALUES ('Ізмаїл', 15); +INSERT INTO public.cities (name, region_id) +VALUES ('Ізюм', 20); +INSERT INTO public.cities (name, region_id) +VALUES ('Ізяслав', 22); +INSERT INTO public.cities (name, region_id) +VALUES ('Іллінці', 2); +INSERT INTO public.cities (name, region_id) +VALUES ('Іловайськ', 5); +INSERT INTO public.cities (name, region_id) +VALUES ('Інкерман', 1); +INSERT INTO public.cities (name, region_id) +VALUES ('Ірміно', 12); +INSERT INTO public.cities (name, region_id) +VALUES ('Ірпінь', 10); +INSERT INTO public.cities (name, region_id) +VALUES ('Іршава', 7); +INSERT INTO public.cities (name, region_id) +VALUES ('Ічня', 25); +INSERT INTO public.cities (name, region_id) +VALUES ('Кагарлик', 10); +INSERT INTO public.cities (name, region_id) +VALUES ('Кадіївка', 12); +INSERT INTO public.cities (name, region_id) +VALUES ('Калинівка', 2); +INSERT INTO public.cities (name, region_id) +VALUES ('Калуш', 9); +INSERT INTO public.cities (name, region_id) +VALUES ('Кальміуське', 5); +INSERT INTO public.cities (name, region_id) +VALUES ('Камінь-Каширський', 3); +INSERT INTO public.cities (name, region_id) +VALUES ('Кам''янець-Подільський', 22); +INSERT INTO public.cities (name, region_id) +VALUES ('Кам''янка', 23); +INSERT INTO public.cities (name, region_id) +VALUES ('Камянка-Бузька', 13); +INSERT INTO public.cities (name, region_id) +VALUES ('Кам''янка-Дніпровська', 8); +INSERT INTO public.cities (name, region_id) +VALUES ('Кам''янське', 4); +INSERT INTO public.cities (name, region_id) +VALUES ('Канів', 23); +INSERT INTO public.cities (name, region_id) +VALUES ('Карлівка', 16); +INSERT INTO public.cities (name, region_id) +VALUES ('Каховка', 21); +INSERT INTO public.cities (name, region_id) +VALUES ('Керч', 1); +INSERT INTO public.cities (name, region_id) +VALUES ('Київ', 10); +INSERT INTO public.cities (name, region_id) +VALUES ('Кипуче', 12); +INSERT INTO public.cities (name, region_id) +VALUES ('Ківерці', 3); +INSERT INTO public.cities (name, region_id) +VALUES ('Кілія', 15); +INSERT INTO public.cities (name, region_id) +VALUES ('Кіцмань', 24); +INSERT INTO public.cities (name, region_id) +VALUES ('Кобеляки', 16); +INSERT INTO public.cities (name, region_id) +VALUES ('Ковель', 3); +INSERT INTO public.cities (name, region_id) +VALUES ('Кодима', 15); +INSERT INTO public.cities (name, region_id) +VALUES ('Козятин', 2); +INSERT INTO public.cities (name, region_id) +VALUES ('Коломия', 9); +INSERT INTO public.cities (name, region_id) +VALUES ('Комарно', 13); +INSERT INTO public.cities (name, region_id) +VALUES ('Конотоп', 18); +INSERT INTO public.cities (name, region_id) +VALUES ('Копичинці', 19); +INSERT INTO public.cities (name, region_id) +VALUES ('Корець', 17); +INSERT INTO public.cities (name, region_id) +VALUES ('Коростень', 6); +INSERT INTO public.cities (name, region_id) +VALUES ('Коростишів', 6); +INSERT INTO public.cities (name, region_id) +VALUES ('Корсунь-Шевченківський', 23); +INSERT INTO public.cities (name, region_id) +VALUES ('Корюківка', 25); +INSERT INTO public.cities (name, region_id) +VALUES ('Косів', 9); +INSERT INTO public.cities (name, region_id) +VALUES ('Костопіль', 17); +INSERT INTO public.cities (name, region_id) +VALUES ('Костянтинівка', 5); +INSERT INTO public.cities (name, region_id) +VALUES ('Краматорськ', 5); +INSERT INTO public.cities (name, region_id) +VALUES ('Красилів', 22); +INSERT INTO public.cities (name, region_id) +VALUES ('Красногорівка', 5); +INSERT INTO public.cities (name, region_id) +VALUES ('Красноград', 20); +INSERT INTO public.cities (name, region_id) +VALUES ('Красноперекопськ (Яни Капу)', 1); +INSERT INTO public.cities (name, region_id) +VALUES ('Кременець', 19); +INSERT INTO public.cities (name, region_id) +VALUES ('Кременчук', 16); +INSERT INTO public.cities (name, region_id) +VALUES ('Кремінна', 12); +INSERT INTO public.cities (name, region_id) +VALUES ('Кривий Ріг', 4); +INSERT INTO public.cities (name, region_id) +VALUES ('Кролевець', 18); +INSERT INTO public.cities (name, region_id) +VALUES ('Кропивницький', 11); +INSERT INTO public.cities (name, region_id) +VALUES ('Куп''янськ', 20); +INSERT INTO public.cities (name, region_id) +VALUES ('Курахове', 5); +INSERT INTO public.cities (name, region_id) +VALUES ('Ладижин', 2); +INSERT INTO public.cities (name, region_id) +VALUES ('Ланівці', 19); +INSERT INTO public.cities (name, region_id) +VALUES ('Лебедин', 18); +INSERT INTO public.cities (name, region_id) +VALUES ('Лиман', 5); +INSERT INTO public.cities (name, region_id) +VALUES ('Липовець', 2); +INSERT INTO public.cities (name, region_id) +VALUES ('Лисичанськ', 12); +INSERT INTO public.cities (name, region_id) +VALUES ('Лозова', 20); +INSERT INTO public.cities (name, region_id) +VALUES ('Лохвиця', 16); +INSERT INTO public.cities (name, region_id) +VALUES ('Лубни', 16); +INSERT INTO public.cities (name, region_id) +VALUES ('Луганськ', 12); +INSERT INTO public.cities (name, region_id) +VALUES ('Лутугине', 12); +INSERT INTO public.cities (name, region_id) +VALUES ('Луцьк', 3); +INSERT INTO public.cities (name, region_id) +VALUES ('Львів', 13); +INSERT INTO public.cities (name, region_id) +VALUES ('Любомль', 3); +INSERT INTO public.cities (name, region_id) +VALUES ('Люботин', 20); +INSERT INTO public.cities (name, region_id) +VALUES ('Макіївка', 5); +INSERT INTO public.cities (name, region_id) +VALUES ('Мала Виска', 11); +INSERT INTO public.cities (name, region_id) +VALUES ('Малин', 6); +INSERT INTO public.cities (name, region_id) +VALUES ('Марганець', 4); +INSERT INTO public.cities (name, region_id) +VALUES ('Маріуполь', 5); +INSERT INTO public.cities (name, region_id) +VALUES ('Мар''їнка', 5); +INSERT INTO public.cities (name, region_id) +VALUES ('Мелітополь', 8); +INSERT INTO public.cities (name, region_id) +VALUES ('Мена', 25); +INSERT INTO public.cities (name, region_id) +VALUES ('Мерефа', 20); +INSERT INTO public.cities (name, region_id) +VALUES ('Миколаїв', 13); +INSERT INTO public.cities (name, region_id) +VALUES ('Миколаїв', 14); +INSERT INTO public.cities (name, region_id) +VALUES ('Миколаївка', 5); +INSERT INTO public.cities (name, region_id) +VALUES ('Миргород', 16); +INSERT INTO public.cities (name, region_id) +VALUES ('Мирноград', 5); +INSERT INTO public.cities (name, region_id) +VALUES ('Миронівка', 10); +INSERT INTO public.cities (name, region_id) +VALUES ('Міусинськ', 12); +INSERT INTO public.cities (name, region_id) +VALUES ('Могилів-Подільський', 2); +INSERT INTO public.cities (name, region_id) +VALUES ('Молодогвардійськ', 12); +INSERT INTO public.cities (name, region_id) +VALUES ('Молочанськ', 8); +INSERT INTO public.cities (name, region_id) +VALUES ('Монастириська', 19); +INSERT INTO public.cities (name, region_id) +VALUES ('Монастирище', 23); +INSERT INTO public.cities (name, region_id) +VALUES ('Моршин', 13); +INSERT INTO public.cities (name, region_id) +VALUES ('Моспине', 5); +INSERT INTO public.cities (name, region_id) +VALUES ('Мостиська', 13); +INSERT INTO public.cities (name, region_id) +VALUES ('Мукачево', 7); +INSERT INTO public.cities (name, region_id) +VALUES ('Надвірна', 9); +INSERT INTO public.cities (name, region_id) +VALUES ('Немирів', 2); +INSERT INTO public.cities (name, region_id) +VALUES ('Нетішин', 22); +INSERT INTO public.cities (name, region_id) +VALUES ('Ніжин', 25); +INSERT INTO public.cities (name, region_id) +VALUES ('Нікополь', 4); +INSERT INTO public.cities (name, region_id) +VALUES ('Нова Каховка', 21); +INSERT INTO public.cities (name, region_id) +VALUES ('Нова Одеса', 14); +INSERT INTO public.cities (name, region_id) +VALUES ('Новгород-Сіверський', 25); +INSERT INTO public.cities (name, region_id) +VALUES ('Новий Буг', 14); +INSERT INTO public.cities (name, region_id) +VALUES ('Новий Калинів', 13); +INSERT INTO public.cities (name, region_id) +VALUES ('Новий Розділ', 13); +INSERT INTO public.cities (name, region_id) +VALUES ('Новоазовськ', 5); +INSERT INTO public.cities (name, region_id) +VALUES ('Нововолинськ', 3); +INSERT INTO public.cities (name, region_id) +VALUES ('Новоград-Волинський', 6); +INSERT INTO public.cities (name, region_id) +VALUES ('Новогродівка', 5); +INSERT INTO public.cities (name, region_id) +VALUES ('Новодністровськ', 24); +INSERT INTO public.cities (name, region_id) +VALUES ('Новодружеськ', 12); +INSERT INTO public.cities (name, region_id) +VALUES ('Новомиргород', 11); +INSERT INTO public.cities (name, region_id) +VALUES ('Новомосковськ', 4); +INSERT INTO public.cities (name, region_id) +VALUES ('Новоселиця', 24); +INSERT INTO public.cities (name, region_id) +VALUES ('Новоукраїнка', 11); +INSERT INTO public.cities (name, region_id) +VALUES ('Новояворівськ', 13); +INSERT INTO public.cities (name, region_id) +VALUES ('Носівка', 25); +INSERT INTO public.cities (name, region_id) +VALUES ('Обухів', 10); +INSERT INTO public.cities (name, region_id) +VALUES ('Овруч', 6); +INSERT INTO public.cities (name, region_id) +VALUES ('Одеса', 15); +INSERT INTO public.cities (name, region_id) +VALUES ('Олевськ', 6); +INSERT INTO public.cities (name, region_id) +VALUES ('Олександрівськ', 12); +INSERT INTO public.cities (name, region_id) +VALUES ('Олександрія', 11); +INSERT INTO public.cities (name, region_id) +VALUES ('Олешки', 21); +INSERT INTO public.cities (name, region_id) +VALUES ('Оріхів', 8); +INSERT INTO public.cities (name, region_id) +VALUES ('Остер', 25); +INSERT INTO public.cities (name, region_id) +VALUES ('Острог', 17); +INSERT INTO public.cities (name, region_id) +VALUES ('Охтирка', 18); +INSERT INTO public.cities (name, region_id) +VALUES ('Очаків', 14); +INSERT INTO public.cities (name, region_id) +VALUES ('Павлоград', 4); +INSERT INTO public.cities (name, region_id) +VALUES ('Первомайськ', 12); +INSERT INTO public.cities (name, region_id) +VALUES ('Первомайськ', 14); +INSERT INTO public.cities (name, region_id) +VALUES ('Первомайський', 20); +INSERT INTO public.cities (name, region_id) +VALUES ('Перевальськ', 12); +INSERT INTO public.cities (name, region_id) +VALUES ('Перемишляни', 13); +INSERT INTO public.cities (name, region_id) +VALUES ('Перечин', 7); +INSERT INTO public.cities (name, region_id) +VALUES ('Перещепине', 4); +INSERT INTO public.cities (name, region_id) +VALUES ('Переяслав', 10); +INSERT INTO public.cities (name, region_id) +VALUES ('Першотравенськ', 4); +INSERT INTO public.cities (name, region_id) +VALUES ('Петрово-Красносілля', 12); +INSERT INTO public.cities (name, region_id) +VALUES ('Пирятин', 16); +INSERT INTO public.cities (name, region_id) +VALUES ('Південне', 20); +INSERT INTO public.cities (name, region_id) +VALUES ('Підгайці', 19); +INSERT INTO public.cities (name, region_id) +VALUES ('Підгородне', 4); +INSERT INTO public.cities (name, region_id) +VALUES ('Погребище', 2); +INSERT INTO public.cities (name, region_id) +VALUES ('Подільськ', 15); +INSERT INTO public.cities (name, region_id) +VALUES ('Покров', 4); +INSERT INTO public.cities (name, region_id) +VALUES ('Покровськ', 5); +INSERT INTO public.cities (name, region_id) +VALUES ('Пологи', 8); +INSERT INTO public.cities (name, region_id) +VALUES ('Полонне', 22); +INSERT INTO public.cities (name, region_id) +VALUES ('Полтава', 16); +INSERT INTO public.cities (name, region_id) +VALUES ('Помічна', 11); +INSERT INTO public.cities (name, region_id) +VALUES ('Попасна', 12); +INSERT INTO public.cities (name, region_id) +VALUES ('Почаїв', 19); +INSERT INTO public.cities (name, region_id) +VALUES ('Привілля', 12); +INSERT INTO public.cities (name, region_id) +VALUES ('Прилуки', 25); +INSERT INTO public.cities (name, region_id) +VALUES ('Приморськ', 8); +INSERT INTO public.cities (name, region_id) +VALUES ('Прип''ять', 10); +INSERT INTO public.cities (name, region_id) +VALUES ('Пустомити', 13); +INSERT INTO public.cities (name, region_id) +VALUES ('Путивль', 18); +INSERT INTO public.cities (name, region_id) +VALUES ('П''ятихатки', 4); +INSERT INTO public.cities (name, region_id) +VALUES ('Рава-Руська', 13); +INSERT INTO public.cities (name, region_id) +VALUES ('Радехів', 13); +INSERT INTO public.cities (name, region_id) +VALUES ('Радивилів', 17); +INSERT INTO public.cities (name, region_id) +VALUES ('Радомишль', 6); +INSERT INTO public.cities (name, region_id) +VALUES ('Рахів', 7); +INSERT INTO public.cities (name, region_id) +VALUES ('Рені', 15); +INSERT INTO public.cities (name, region_id) +VALUES ('Решетилівка', 16); +INSERT INTO public.cities (name, region_id) +VALUES ('Ржищів', 10); +INSERT INTO public.cities (name, region_id) +VALUES ('Рівне', 17); +INSERT INTO public.cities (name, region_id) +VALUES ('Ровеньки', 12); +INSERT INTO public.cities (name, region_id) +VALUES ('Рогатин', 9); +INSERT INTO public.cities (name, region_id) +VALUES ('Родинське', 5); +INSERT INTO public.cities (name, region_id) +VALUES ('Рожище', 3); +INSERT INTO public.cities (name, region_id) +VALUES ('Роздільна', 15); +INSERT INTO public.cities (name, region_id) +VALUES ('Ромни', 18); +INSERT INTO public.cities (name, region_id) +VALUES ('Рубіжне', 12); +INSERT INTO public.cities (name, region_id) +VALUES ('Рудки', 13); +INSERT INTO public.cities (name, region_id) +VALUES ('Саки', 1); +INSERT INTO public.cities (name, region_id) +VALUES ('Самбір', 13); +INSERT INTO public.cities (name, region_id) +VALUES ('Сарни', 17); +INSERT INTO public.cities (name, region_id) +VALUES ('Свалява', 7); +INSERT INTO public.cities (name, region_id) +VALUES ('Сватове', 12); +INSERT INTO public.cities (name, region_id) +VALUES ('Світловодськ', 11); +INSERT INTO public.cities (name, region_id) +VALUES ('Світлодарськ', 5); +INSERT INTO public.cities (name, region_id) +VALUES ('Святогірськ', 5); +INSERT INTO public.cities (name, region_id) +VALUES ('Севастополь', 1); +INSERT INTO public.cities (name, region_id) +VALUES ('Селидове', 5); +INSERT INTO public.cities (name, region_id) +VALUES ('Семенівка', 25); +INSERT INTO public.cities (name, region_id) +VALUES ('Середина-Буда', 18); +INSERT INTO public.cities (name, region_id) +VALUES ('Сєвєродонецьк', 12); +INSERT INTO public.cities (name, region_id) +VALUES ('Синельникове', 4); +INSERT INTO public.cities (name, region_id) +VALUES ('Сіверськ', 5); +INSERT INTO public.cities (name, region_id) +VALUES ('Сімферополь', 1); +INSERT INTO public.cities (name, region_id) +VALUES ('Скадовськ', 21); +INSERT INTO public.cities (name, region_id) +VALUES ('Скалат', 19); +INSERT INTO public.cities (name, region_id) +VALUES ('Сквира', 10); +INSERT INTO public.cities (name, region_id) +VALUES ('Сколе', 13); +INSERT INTO public.cities (name, region_id) +VALUES ('Славута', 22); +INSERT INTO public.cities (name, region_id) +VALUES ('Славутич', 10); +INSERT INTO public.cities (name, region_id) +VALUES ('Слов''янськ', 5); +INSERT INTO public.cities (name, region_id) +VALUES ('Сміла', 23); +INSERT INTO public.cities (name, region_id) +VALUES ('Снігурівка', 14); +INSERT INTO public.cities (name, region_id) +VALUES ('Сніжне', 5); +INSERT INTO public.cities (name, region_id) +VALUES ('Сновськ', 25); +INSERT INTO public.cities (name, region_id) +VALUES ('Снятин', 9); +INSERT INTO public.cities (name, region_id) +VALUES ('Сокаль', 13); +INSERT INTO public.cities (name, region_id) +VALUES ('Сокиряни', 24); +INSERT INTO public.cities (name, region_id) +VALUES ('Соледар', 5); +INSERT INTO public.cities (name, region_id) +VALUES ('Сорокине', 12); +INSERT INTO public.cities (name, region_id) +VALUES ('Соснівка', 13); +INSERT INTO public.cities (name, region_id) +VALUES ('Старий Крим', 1); +INSERT INTO public.cities (name, region_id) +VALUES ('Старий Самбір', 13); +INSERT INTO public.cities (name, region_id) +VALUES ('Старобільськ', 12); +INSERT INTO public.cities (name, region_id) +VALUES ('Старокостянтинів', 22); +INSERT INTO public.cities (name, region_id) +VALUES ('Стебник', 13); +INSERT INTO public.cities (name, region_id) +VALUES ('Сторожинець', 24); +INSERT INTO public.cities (name, region_id) +VALUES ('Стрий', 13); +INSERT INTO public.cities (name, region_id) +VALUES ('Судак', 1); +INSERT INTO public.cities (name, region_id) +VALUES ('Судова Вишня', 13); +INSERT INTO public.cities (name, region_id) +VALUES ('Суми', 18); +INSERT INTO public.cities (name, region_id) +VALUES ('Суходільськ', 12); +INSERT INTO public.cities (name, region_id) +VALUES ('Таврійськ', 21); +INSERT INTO public.cities (name, region_id) +VALUES ('Тальне', 23); +INSERT INTO public.cities (name, region_id) +VALUES ('Тараща', 10); +INSERT INTO public.cities (name, region_id) +VALUES ('Татарбунари', 15); +INSERT INTO public.cities (name, region_id) +VALUES ('Теплодар', 15); +INSERT INTO public.cities (name, region_id) +VALUES ('Теребовля', 19); +INSERT INTO public.cities (name, region_id) +VALUES ('Тернівка', 4); +INSERT INTO public.cities (name, region_id) +VALUES ('Тернопіль', 19); +INSERT INTO public.cities (name, region_id) +VALUES ('Тетіїв', 10); +INSERT INTO public.cities (name, region_id) +VALUES ('Тисмениця', 9); +INSERT INTO public.cities (name, region_id) +VALUES ('Тлумач', 9); +INSERT INTO public.cities (name, region_id) +VALUES ('Токмак', 8); +INSERT INTO public.cities (name, region_id) +VALUES ('Торецьк', 5); +INSERT INTO public.cities (name, region_id) +VALUES ('Тростянець', 18); +INSERT INTO public.cities (name, region_id) +VALUES ('Трускавець', 13); +INSERT INTO public.cities (name, region_id) +VALUES ('Тульчин', 2); +INSERT INTO public.cities (name, region_id) +VALUES ('Турка', 13); +INSERT INTO public.cities (name, region_id) +VALUES ('Тячів', 7); +INSERT INTO public.cities (name, region_id) +VALUES ('Угнів', 13); +INSERT INTO public.cities (name, region_id) +VALUES ('Ужгород', 7); +INSERT INTO public.cities (name, region_id) +VALUES ('Узин', 10); +INSERT INTO public.cities (name, region_id) +VALUES ('Українка', 10); +INSERT INTO public.cities (name, region_id) +VALUES ('Українськ', 5); +INSERT INTO public.cities (name, region_id) +VALUES ('Умань', 23); +INSERT INTO public.cities (name, region_id) +VALUES ('Устилуг', 3); +INSERT INTO public.cities (name, region_id) +VALUES ('Фастів', 10); +INSERT INTO public.cities (name, region_id) +VALUES ('Феодосія', 1); +INSERT INTO public.cities (name, region_id) +VALUES ('Харків', 20); +INSERT INTO public.cities (name, region_id) +VALUES ('Харцизьк', 5); +INSERT INTO public.cities (name, region_id) +VALUES ('Херсон', 21); +INSERT INTO public.cities (name, region_id) +VALUES ('Хирів', 13); +INSERT INTO public.cities (name, region_id) +VALUES ('Хмельницький', 22); +INSERT INTO public.cities (name, region_id) +VALUES ('Хмільник', 2); +INSERT INTO public.cities (name, region_id) +VALUES ('Ходорів', 13); +INSERT INTO public.cities (name, region_id) +VALUES ('Хорол', 16); +INSERT INTO public.cities (name, region_id) +VALUES ('Хоростків', 19); +INSERT INTO public.cities (name, region_id) +VALUES ('Хотин', 24); +INSERT INTO public.cities (name, region_id) +VALUES ('Хрестівка', 5); +INSERT INTO public.cities (name, region_id) +VALUES ('Христинівка', 23); +INSERT INTO public.cities (name, region_id) +VALUES ('Хрустальний', 12); +INSERT INTO public.cities (name, region_id) +VALUES ('Хуст', 7); +INSERT INTO public.cities (name, region_id) +VALUES ('Часів Яр', 5); +INSERT INTO public.cities (name, region_id) +VALUES ('Червоноград', 13); +INSERT INTO public.cities (name, region_id) +VALUES ('Черкаси', 23); +INSERT INTO public.cities (name, region_id) +VALUES ('Чернівці', 24); +INSERT INTO public.cities (name, region_id) +VALUES ('Чернігів', 25); +INSERT INTO public.cities (name, region_id) +VALUES ('Чигирин', 23); +INSERT INTO public.cities (name, region_id) +VALUES ('Чистякове', 5); +INSERT INTO public.cities (name, region_id) +VALUES ('Чоп', 7); +INSERT INTO public.cities (name, region_id) +VALUES ('Чорнобиль', 10); +INSERT INTO public.cities (name, region_id) +VALUES ('Чорноморськ', 15); +INSERT INTO public.cities (name, region_id) +VALUES ('Чортків', 19); +INSERT INTO public.cities (name, region_id) +VALUES ('Чугуїв', 20); +INSERT INTO public.cities (name, region_id) +VALUES ('Чуднів', 6); +INSERT INTO public.cities (name, region_id) +VALUES ('Шаргород', 2); +INSERT INTO public.cities (name, region_id) +VALUES ('Шахтарськ', 5); +INSERT INTO public.cities (name, region_id) +VALUES ('Шепетівка', 22); +INSERT INTO public.cities (name, region_id) +VALUES ('Шостка', 18); +INSERT INTO public.cities (name, region_id) +VALUES ('Шпола', 23); +INSERT INTO public.cities (name, region_id) +VALUES ('Шумськ', 19); +INSERT INTO public.cities (name, region_id) +VALUES ('Щастя', 12); +INSERT INTO public.cities (name, region_id) +VALUES ('Щолкіне', 1); +INSERT INTO public.cities (name, region_id) +VALUES ('Южне', 15); +INSERT INTO public.cities (name, region_id) +VALUES ('Южноукраїнськ', 14); +INSERT INTO public.cities (name, region_id) +VALUES ('Яворів', 13); +INSERT INTO public.cities (name, region_id) +VALUES ('Яготин', 10); +INSERT INTO public.cities (name, region_id) +VALUES ('Ялта', 1); +INSERT INTO public.cities (name, region_id) +VALUES ('Ямпіль', 2); +INSERT INTO public.cities (name, region_id) +VALUES ('Яремче', 9); +INSERT INTO public.cities (name, region_id) +VALUES ('Ясинувата', 5); + +-- Adding UNICEF Ukraine into the "institutions" table. Primary_key with value #1 will be assigned to it. +INSERT INTO public.institutions (name, address, city_id) +VALUES ('UNICEF Ukraine', 'вул. Інститутська, 28', 190); +-- UNICEF Ukraine was added + +-- The first administrator's appendage. Primary_key with value #1 will be assigned to him. +INSERT INTO public.users (email, password, status, first_name, last_name, phone, created_at, avatar, enabled, + role_id) +VALUES ('admin@mail.com', '$2y$10$GtQSp.P.EyAtCgUD2zWLW.01OBz409TGPl/Jo3U30Tig3YbbpIFv2', 'ACTIVE', 'Lisa', + 'Cuddy', '+380938383831', '2021-02-11 03:56:31.332925', null, true, 1); +INSERT INTO public.providers (provider_name, email, user_id_by_provider, user_id) +VALUES ('LOCAL', 'admin@mail.com', '1', 1); +-- The admin was added + +-- The second administrator's appendage. Primary_key with value #2 will be assigned to him. +INSERT INTO public.users (email, password, status, first_name, last_name, phone, created_at, avatar, enabled, + role_id) +VALUES ('admin1@mail.com', '$2y$10$GtQSp.P.EyAtCgUD2zWLW.01OBz409TGPl/Jo3U30Tig3YbbpIFv2', 'ACTIVE', 'Gregory', + 'House', '+380938383832', '2021-02-12 03:56:32.332925', null, true, 1); +INSERT INTO public.providers (provider_name, email, user_id_by_provider, user_id) +VALUES ('LOCAL', 'admin1@mail.com', '2', 2); +-- The second admin was added + +-- Adding the 1st author (expert) with the administrator role. User_id with value #3 and Doctor_id with value #1 will be assigned to him. +INSERT INTO public.users (email, password, status, first_name, last_name, phone, created_at, avatar, enabled, + role_id) +VALUES ('unicef.vaccination@gmail.com', '$2y$10$GtQSp.P.EyAtCgUD2zWLW.01OBz409TGPl/Jo3U30Tig3YbbpIFv2', 'ACTIVE', + 'UNICEF', 'Ukraine', '+380939393939', '2021-12-10 03:56:31.444571', 'https://i.imgur.com/OV1iQAD.png', true, + 1); +INSERT INTO public.providers (provider_name, email, user_id_by_provider, user_id) +VALUES ('LOCAL', 'unicef.vaccination@gmail.com', '3', 3); +INSERT INTO public.doctors (qualification, bio, user_id, institution_id, promotion_scale, promotion_level, + published_posts, rating, social_network) +VALUES ('Інфекціоніст', 'Представництво Дитячого фонду ООН в Україні',3, 1, 1, 0, + 0, 0, 'https://www.facebook.com/UNICEFUkraine/'); +INSERT INTO public.doctors_institutions (doctor_id, institution_id) +VALUES (1, 1); +INSERT INTO public.doctors_directions (doctor_id, direction_id) +VALUES (1, 1), (1, 4), (1, 6); +-- The 1st author was added + +-- Adding the 2nd author (expert) with the Doctor role. User_id with value #4 and Doctor_id with value #2 will be assigned to him. +INSERT INTO public.users (email, password, status, first_name, last_name, phone, created_at, avatar, enabled, + role_id) +VALUES ('kateryna.bulavinova@gmail.com', '$2y$10$GtQSp.P.EyAtCgUD2zWLW.01OBz409TGPl/Jo3U30Tig3YbbpIFv2', 'ACTIVE', + 'Катерина', 'Булавінова', '+380939393939', '2021-05-12 03:56:39.444571', 'https://i.imgur.com/k0j4vVH.png', true, + 3); +INSERT INTO public.providers (provider_name, email, user_id_by_provider, user_id) +VALUES ('LOCAL', 'kateryna.bulavinova@gmail.com', '4', 4); +INSERT INTO public.doctors (qualification, bio, user_id, institution_id, promotion_scale, promotion_level, + published_posts, rating, social_network) +VALUES ('Інфекціоніст', 'медичний експерт ЮНІСЕФ в Україні',4, 1, 1, 0, + 0, 0, 'facebook.com/kateryna.bulavinova'); +INSERT INTO public.doctors_institutions (doctor_id, institution_id) +VALUES (2, 1); +INSERT INTO public.doctors_directions (doctor_id, direction_id) +VALUES (2, 1), (2, 5), (2, 7); +-- The 2nd author was added + +INSERT INTO public.role_permission (role_id, permissions) +VALUES (1, 'SAVE_OWN_PUBLICATION'); +INSERT INTO public.role_permission (role_id, permissions) +VALUES (1, 'SAVE_TAG'); +INSERT INTO public.role_permission (role_id, permissions) +VALUES (2, 'SAVE_OWN_PUBLICATION'); +INSERT INTO public.role_permission (role_id, permissions) +VALUES (2, 'SAVE_TAG'); +INSERT INTO public.role_permission (role_id, permissions) +VALUES (3, 'SAVE_OWN_PUBLICATION'); +INSERT INTO public.role_permission (role_id, permissions) +VALUES (3, 'SAVE_TAG'); +INSERT INTO public.role_permission (role_id, permissions) +VALUES (1, 'DELETE_POST'); +INSERT INTO public.role_permission (role_id, permissions) +VALUES (2, 'DELETE_POST'); +INSERT INTO public.role_permission (role_id, permissions) +VALUES (3, 'DELETE_OWN_POST'); +INSERT INTO public.role_permission (role_id, permissions) +VALUES (1, 'UPDATE_POST'); +INSERT INTO public.role_permission (role_id, permissions) +VALUES (2, 'UPDATE_POST'); +INSERT INTO public.role_permission (role_id, permissions) +VALUES (3, 'UPDATE_OWN_POST'); +INSERT INTO public.role_permission (role_id, permissions) +VALUES (1, 'SAVE_PUBLICATION'); +INSERT INTO public.role_permission (role_id, permissions) +VALUES (1, 'SET_IMPORTANCE'); +INSERT INTO public.role_permission (role_id, permissions) +VALUES (1, 'SAVE_PLATFORM_INFORMATION'); +INSERT INTO public.role_permission (role_id, permissions) +VALUES (1, 'UPDATE_PLATFORM_INFORMATION'); +INSERT INTO public.role_permission (role_id, permissions) +VALUES (1, 'DELETE_OWN_POST'); +INSERT INTO public.role_permission (role_id, permissions) +VALUES (1, 'UPDATE_OWN_POST'); + +UPDATE DOCTORS D +SET PUBLISHED_POSTS=( + SELECT COUNT(P.POST_ID) + FROM POSTS P + WHERE P.AUTHOR_ID=D.USER_ID + AND STATUS='PUBLISHED' +); + +UPDATE DOCTORS SET RATING=PUBLISHED_POSTS * PROMOTION_SCALE; + +INSERT INTO public.origins (name, parameters) +VALUES ('Думка експерта', null); +INSERT INTO public.origins (name, parameters) +VALUES ('Медитека', null); +INSERT INTO public.origins (name, parameters) +VALUES ('Переклад', null); + +UPDATE public.platform_information +SET title = 'Про платформу', + content = '

Доказові — платформа з перевіреною і надійною інформацією про COVID-19, ' || + 'вакцинацію та інші важливі теми, пов’язані з медициною.

' || + '

На платформі публікуються авторські матеріали лікарів та експертів, інформація від ЮНІСЕФ, ' || + 'а також переклади міжнародних матеріалів та науково-популярних статей.

' || + '

Автори платформи Доказові — це люди, які постійно навчаються, використовують дані з наукових джерел ' || + 'і належать до авторитетних медичних спільнот. Вони постійно підвищують рівень знань, щоб ефективно лікувати,' || + ' розповідати і пояснювати.

Принципи, за якими працює платформа Доказові:' || + '

  • доказова медицина: на порталі оприлюднюються матеріали, ' || + 'які відповідають принципу доказової медицини
  • відбір інформації експертами: ' || + 'всю інформацію для порталу відбирають, готують і верифікують авторитетні й надійні лікарі, ' || + 'експерти, фактчекери
  • міжнародні рекомендації: відомості, подані на порталі, ' || + 'відповідають рекомендаціям ВООЗ, Центрів контролю за захворюваннями США і сайту ' || + 'системи охорони здоров’я Великобританії
  • інформація, якою варто дилітись: ' || + 'закликаємо поширювати інформацію, оприлюднену на платформі, а також використовувати її з освітніми цілями ' || + '(докладніше — у ' || + 'Правилах користування)

Матеріали сайту будуть корисними сімейним лікарям ' || + 'та медикам вузьких спеціальностей, науковцям, які працюють з темами COVID-19 і вакцинації, журналістам, ' || + 'які пишуть про них, батькам та всім, хто хоче знати більше на теми, про які пише платформа Доказові.

' || + '

Платформу розробили та запустили у 2021 році Дитячий фонд ООН (ЮНІСЕФ) в Україні та SoftServe ' || + 'за підтримки Агентства США з міжнародного розвитку (USAID).

' +WHERE id = 1; + +UPDATE public.platform_information +SET title = 'Контакти', + content = '

Хочете стати автором платформи?

Бажаєте співпрацювати?

Маєте запитання?

' || + '

Пишіть нам на contact@dokazovi.info

' +WHERE id = 3; + +-- Adding three post (articles) + +INSERT INTO PUBLIC.POSTS (TITLE, PREVIEW, CONTENT, VIDEO_URL, PREVIEW_IMAGE_URL, AUTHOR_ID, TYPE_ID, + CREATED_AT, MODIFIED_AT, PUBLISHED_AT, STATUS, IMPORTANT) +VALUES ('Грудне вигодовування і COVID-19', 'Як ви подбали про вакцинацію від грипу 2020?', + '

Як ви подбали про вакцинацію від грипу 2020? А чи знаєте, що в цьому році все трохи по-іншому? Пандемія COVID-19 призвела до різкого зростання попиту на вакцини від грипу на глобальному рівні. Попит настільки великий, що деякі виробники збільшили потужності на 20% і вище, практично вичерпавши свої ресурси. Однак навіть такі заходи все ще не відповідають силі запиту.



Багато країн збільшили квоти і замовили вакцину від грипу заздалегідь. Наприклад, Литва з 2008 року, тобто вже понад десять років, за рахунок держави покриває щеплення від грипу людям з хронічними захворюваннями і 65+, вагітним жінкам, медпрацівникам, а також людям в будинках для літніх та хоспісах. Звичайний обсяг закупівель вакцин від грипу Литви становив трохи більше 100 000, але в 2020 вони замовили вдвічі більше.Реальною цінністю під час пандемії є не просто лікарняні ліжка, а доступ до кисню. Як каже моя близька людина, треба нарешті обговорювати «паркомісця». Багато хто розуміє, що скоро вони будуть зайняті не лише ковідом, а й грипом чи дивовижним коктейлем Молотова з обох. Тому передусім вакцина від грипу потрібна медпрацівникам, щоб продовжувати рятувати від COVID-19, вагітним жінкам, людям з хронічними захворюваннями, дітям до двох років, а також дорослим і дуже дорослим. Ці категорії громадян є групою ризику за обома інфекціями. Однією із дуже важливих груп є також вагітні жінки, які можуть потрапити у відділення реанімації у 7−10 разів частіше, ніж інша людина, яка захворіла на грип. Вони мають вакцинуватися на будь-якому терміні вагітності.

Охоплення вакцинацією від грипу в Україні «героїчно» коливається від піввідсотка до 3%

А що ж ми? Охоплення вакцинацією від грипу в Україні «героїчно» коливається від піввідсотка до 3%.

Медичні працівники почасти обходять це некомфортне для себе питання, серйозно розмірковуючи про те, що «штами не збігаються», «вакцина не допомагає», а також про «чужорідний білок» і про «аутоімунні захворювання».

Вакцина від грипу буває проти трьох або чотирьох його штамів. У цьому сезоні в Україні очікуються вакцини трьох виробників, і серед них будуть і ті, й інші.

На початку жовтня в Україні з''явилася невелика партія однієї з трьох очікуваних вакцин. 5 жовтня прищепилася нею і я. Коли мене запитують, яка вакцина краща, я завжди відповідаю: «Перша, яку ви змогли схопити». Адже на вироблення імунітету піде, як мінімум, два тижні.

За роки роботи Дитячий фонд ООН (ЮНІСЕФ) в Україні об''єднав прогресивних лікарів і медсестер, які вакцинуються від грипу самі й формують попит у пацієнтів. Однак до цього часу є значна група «героїв», які намагаються із крижаним виразом обличчя в столиці нашої Батьківщини відволікти пацієнта від цього, на їхню думку, «непотрібного рішення» і, скажімо, досхочу покористуватися «імуномодуляторами». Особливо я люблю рекомендації для людей дуже дорослих у стилі «вам уже не треба», «куди вже», «вам пізно — цвинтар он там».

Окремою темою є «доступ до послуги». Цей холодний скреготливий термін асоціюється зазвичай із грошима. А я поки взагалі не про гроші. Я виключно про фізичну доступність. Якщо ви живете в Києві або в будь-якій з обласних «столиць», у вас є шанси на перемогу.

Особливо я люблю рекомендації для людей дуже дорослих у стилі «вам уже не треба», «куди вже», «вам пізно — цвинтар он там»

Якщо ви живете в місті невеликому, наприклад, у Косові, Коломиї, Бахмуті, Переяславі, то ваші шанси на успіх безпосередньо залежать від пристрасті лікарів і медсестер та їхньої відданості вакцинації. Якщо вони — фанати щеплень від грипу, домовилися з постачальниками / аптекою, оцінили потреби колег / пацієнтів, «заморочилися» логістикою, то скромні шанси захиститися від смертельно небезпечної інфекції у вас з''являються.

Як кажуть у нас в Одесі: «А так, щоб узагалі, так ні». Шанси на доступ до подібної послуги в селі, де, до речі, багато людей із груп ризику, а саме людей дуже дорослих, людей із хронічними захворюваннями, дітей до двох років і вагітних жінок, наближаються до нуля.

Щороку я перебуваю в якомусь дитячому передчутті, що ось нарешті почнеться суспільна дискусія на цю тему. Громада збереться, щоб обговорити, як же організувати фізичний доступ до вакцини і, можливо, фінансово підтримати тих, хто не може її купити сам, але цю дискусію завжди підмінює якась інша. Мабуть, нагальніша. Про місцеві вибори, наприклад.

Але повернімося до більшої картини. Інформація на початок жовтня: дистриб''ютори поставлять в Україну менше мільйона доз вакцин від грипу. Швидше за все, всі вони потраплять на приватний ринок.

Що таке приватний ринок? Це коли ви купуєте вакцину в аптеці або робите щеплення, оплачуючи його в приватній або державній клініці. Або ви — медпрацівник, для вас цю вакцину купив головний лікар або, можливо, військовий, і таку закупівлю зробило Міністерство оборони.

Вакцинація проти грипу не є в Україні складовою календаря щеплень, не покривається державою. У зв''язку з COVID-19 Міністерство охорони здоров''я кілька разів висловлювало намір закупити вакцину від грипу для медпрацівників. Особисто я всіма фібрами душі за і хочу, щоб так сталося в цьому сезоні хоча б в якійсь кількості.


Підпис під картинкою в один рядок

Поки ж, чого бажаю вам. Передусім визначитися у своєму намірі вакцинуватися.Якщо ви підписали декларацію із сімейним лікарем — зателефонуйте йому негайно і повідомте про своє палке бажання прищепитися від грипу. Обговоріть, як саме сімейний лікар допоможе вам у цьому. Якщо ви — «прихожанин» приватної клініки, зателефонуйте, будь ласка, на ресепцію або до свого лікаря і обговоріть, коли вакцинуватися від грипу. Якщо ви — головний лікар «ковідной лікарні», втім, будь-якої лікарні та поліклініки, терміново визначтеся, якою буде потреба у вакцинації для вашого персоналу і як саме ви зможете її задовольнити. Якщо ви — голова підприємства, банку або будь-якої іншої приватної структури, яка в мирний час сприяла організації щеплень, спонсорувала або співфінансувала їх для своїх працівників, почніть опікуватися цим питанням негайно, тому що вам може і не вистачити. Якщо ви — «громада», то сядьте і обговоріть оце все. Якщо ви — адепт вакцинації від грипу, робіть усе можливе, тільки не справляйте сидні. Бажаю успіхів у цьому захопливому квесті.

Матеріал вперше опубліковано на ресурсі Новое Время
Дата публікації оригіналу: 21.11.2020


1 Поки ж, чого бажаю вам
2 Передусім визначитися у своєму намірі вакцинуватися
', + NULL, 'https://i.imgur.com/1VU2fe5.png', 3, 1, '01.08.2021 03:51:31.700332', '02.08.2021 03:32:32.700332', + '03.09.2021 05:53:37.700332', 'PUBLISHED', TRUE), + ('Діти та батьки і COVID-19', 'Чи небезпечний COVID-19 для дітей?', + '

Чи небезпечний COVID-19 для дітей?

Вивчення впливу нового коронавірусу на дітей триває. Сьогодні відомо, що ним можуть заразитися люди будь-якого віку, але в дітей поки що зафіксовано здебільшого легкі форми або безсимптомний перебіг хвороби.

Вагітність і пологи

Хоча поки достеменно не відомо, чи передається вірус від матері дитині під час вагітності та пологів, ВООЗ і Дитячий фонд ООН (ЮНІСЕФ) рекомендують усім вагітним жінкам:

  • Вживайте тих самих запобіжних заходів, що й інші люди: дотримуйтеся дистанції, уникайте контактів та намагайтеся за можливості отримати медичні послуги телефоном або онлайн.
  • Будьте уважними до самопочуття та телефонуйте лікарю або лікарці, якщо вас щось непокоїть або у вас температура, кашель, нежить чи інші симптоми.
  • Звертайтеся по медичну допомогу якомога раніше, якщо належите до групи ризику, у вас температура, кашель або утруднене дихання.
  • Обговоріть із акушеркою чи лікарем / лікаркою найбезпечніше, на їхню думку, місце для пологів. Складіть план дій, який допоможе вам почуватися впевненіше і спокійніше в тривожні часи. 
  • Пам''ятайте: під час пандемії COVID-19 медичний заклад для пологів потрібно обирати не за критерієм близькості до місця проживання, а за його можливостями надати відповідну медичну допомогу. Якщо ви належите до груп високого та вкрай високого перинатального ризику, ваші пологи мають відбуватися у спеціалізованих закладах, які зазвичай називають перинатальними центрами.
  • Подбайте про найперші щеплення за календарем вакцинації для немовляти.

Грудне вигодовування і COVID-19

У контексті пандемії медичні працівники й батьки ставлять багато запитань про те, як чинити з грудним вигодовуванням. За інформацією ВООЗ, станом на сьогодні активний вірус SARS-CoV-2 (вірус, який може спричинити зараження) не було виявлено в грудному молоці жодної матері з підтвердженою / імовірною хворобою COVID-19. Отже, малоймовірно, що COVID-19 передається під час годування грудьми або через грудне молоко, яке було зціджене матір''ю з підтвердженим / імовірним захворюванням на COVID-19.

ЮНІСЕФ і ВООЗ рекомендують продовжувати годувати дитину грудьми, навіть якщо ви інфіковані або є підозра на інфікування

Численні переваги грудного вигодовування значно перевищують потенційні ризики передавання вірусу та захворювання на COVID-19. Саме тому Дитячий фонд ООН (ЮНІСЕФ) і ВООЗ рекомендують продовжувати годувати дитину грудьми, навіть якщо ви інфіковані або є підозра на інфікування, а також тримати немовля на руках та забезпечувати контакт «шкіра до шкіри».

Якщо температура, кашель, нежить у матері, яка годує грудьми

  • Зателефонуйте до свого лікаря / лікарки і повідомте, що ви годуєте грудьми.
  • Для зниження температури приймайте парацетамол.
  • Годуйте грудьми так часто, як цього потребує маля.
  • Використовуйте маску під час годування грудьми, щоб унеможливити потрапляння крапель інфікованого слизу на дитину, тим самим зменшуючи ризик її зараження. Стежте за тим, щоб маска щільно прилягала до обличчя. Для цього притисніть маску до перенісся спеціальним дротиком. 
  • Мийте руки перед тим, як: надіти маску; узяти маля; торкатися грудей або зціджуватися; користуватися молоковідсмоктувачем або посудом для грудного молока.
  • Знявши маску, відразу ж викиньте її і ретельно вимийте руки (протягом 20 секунд і довше).
  • За можливості уникайте кашлю або чхання на дитину, коли годуєте грудьми.
  • Якщо краплі інфікованого слизу могли потрапити на ваші груди, перед годуванням обережно вимийте їх теплою водою з милом (протягом щонайменше 20 секунд). Загалом же мити груди перед кожним годуванням або зціджуванням не потрібно.
  • Навіть коли не йдеться про COVID-19, молоковідсмоктувачі, ємності для зберігання молока та прибори для годування потрібно ретельно мити після кожного використання рідким милом або, наприклад, рідиною для миття посуду й теплою водою. Після цього прополощіть їх гарячою водою протягом 10–15 секунд.
  • Якщо ви зціджуєтеся, ваші близькі можуть годувати маля з чашки, ложки або шприца. Перед годуванням або користуванням посудом для грудного молока вони також повинні щоразу ретельно мити руки з милом не менш ніж 20 секунд. Зціджування грудного молока також важливе для підтримання лактації, щоб ви могли продовжувати годувати грудьми, коли одужаєте.
  • Якщо є така змога, обмежте свої домашні обов''язки лише годуванням грудьми або зціджуванням. Бажано, щоб догляд за малям (купання, перевдягання, прогулянки, вкладання спати) взяли на себе партнер / чоловік, інші члени родини або близькі люди чи друзі.

Якщо підтверджено COVID-19

Якщо у вас є підстави вважати, що ви інфіковані covid-19, або хворобу підтверджено лабораторно, будь ласка, дотримуйтеся всіх застережень, які допоможуть вам убезпечити інших від зараження. 

Стан матері погіршується

Якщо з''явилися такі симптоми, як задишка, частий сухий кашель, біль у грудній клітці, температура, яку складно знизити, виражена блідість чи синюшність шкіри, повторне блювання, терміново повідомте про це лікарю / лікарці, або зверніться до служби екстреної медичної допомоги за номером 103, або їдьте до медичного закладу самостійно.

Відновлюйте годування грудьми, коли стан поліпшиться

Якщо ваш стан не дозволяє поки що годувати грудьми, повернутися до годування можна, щойно ви почуватиметеся краще. Для цього потрібний частий безпосередній контакт із малюком: прикладайте його до грудей кожні дві години, не рідше. Що молодша дитина, то швидше зазвичай відбувається відновлення. Коли молоко почне повертатися, зменшуйте додаткове харчування.

Перебування у школі й дитячих садках під час пандемії COVID-19

Навчально-виховні заклади наразі ще напрацьовують практики, які б ураховували пандемію COVID-19. Але базові принципи перебування у школі й дитячих садках уже відомі:

  • Дітям, які належать до групи ризику (мають хронічні захворювання, наприклад, бронхіальну астму, діабет, онкологічні захворювання тощо), краще навчатися онлайн — за домовленістю з учителем або вихователем.
  • Діти не мають відвідувати школу або дитячий садок, якщо в них температура, кашель, нежить.
  • Якщо ви або ваші діти контактували з хворим на COVID-19, потрібно дотримуватися самоізоляції протягом 14 днів.
  • Якщо як контактна особа ви плануєте робити ПЛР-тест собі або дітям і для цього доведеться очікувати в черзі, то дуже важливо не лише бути в масці, а й дотримуватися дистанції з іншими людьми щонайменше 2 метри.
  • Людям, які вас оточують (друзям, однокласникам дітей, учителям, шкільній адміністрації), важливо повідомити про те, що ви або ваші діти захворіли на COVID-19, адже, не знаючи цього, вони можуть поширювати інфекцію. 

Як допомогти дітям впоратися з ситуацією довкола COVID-19

Діти можуть багато бачити інформації про пандемію в інтернеті або по телебаченню, але не до кінця розуміти формулювання, зміст та мову таких повідомлень, а тому відчувати тривогу, стрес і смуток. Батьки мають пояснювати те, що діти побачили, прочитали або почули від інших людей. Відкрите обговорення та ваша підтримка допоможуть їм не лише зрозуміти ситуацію, а й поводитися у спосіб, який убезпечить їх від хвороби.

Розповідайте дітям, як захистити себе та своїх друзів

Один із найкращих способів уберегти дітей від COVID-19 та інших захворювань — заохочувати їх регулярно мити руки. Покажіть їм і постійно нагадуйте, як прикривати рот і ніс згином ліктя під час кашлю чи чхання (тобто навчайте кашльового етикету). Пояснюйте, що краще не перебувати надто близько до людей, у яких є симптоми захворювання. Попросіть дитину негайно сказати вам, якщо відчує, що в неї лихоманка, кашель або ускладнене дихання.

Пояснюйте ситуацію словами, зрозумілими для дітей 

Діти мають право на правдиву інформацію про те, що відбувається у світі. Але водночас дорослі повинні захистити їх від стресу. Використовуйте мову, відповідну віку, спостерігайте за реакцією і будьте чутливі до рівня тривоги дітей.

Якщо ви не можете відповісти на запитання дитини, не намагайтеся вигадати відповідь. Використовуйте це як можливість разом ознайомитися з інформацією. Достовірними її джерелами є сайти міжнародних організацій, наприклад, Дитячого фонду ООН (ЮНІСЕФ), ВООЗ, Центрів з контролю та профілактики захворювань в США тощо. Поясніть, що деяка інформація в інтернеті не є точною, тож краще довіряти експертам.

Визнавайте почуття дітей та запевняйте їх, що боятися — це природно. Покажіть, що ви слухаєте їх, приділяючи цьому всю свою увагу.

Обговорюйте з дітьми те, що їх хвилює

Дізнайтеся, що саме діти вже знають, і дозвольте їм визначати напрямок розмови. Якщо діти ще маленькі й не чули про спалах, можливо, вам не потрібно порушувати це питання: просто нагадайте їм про хороші гігієнічні практики.

Переконайтеся, що ви в безпечному просторі. Нехай дитина скаже все, про що думає — не обмежуйте її у цьому. Допомогти почати розмову можуть розповіді й малюнки.

Не заперечуйте почуття дітей

Найголовніше — не нехтуйте побоюваннями дітлахів і не применшуйте їхню стурбованість. Визнавайте почуття дітей та запевняйте їх, що боятися — це природно. Покажіть, що ви слухаєте їх, приділяючи цьому всю свою увагу. Дайте зрозуміти, що діти можуть поговорити з вами коли захочуть.

Знижуйте стрес і тривожність 

Коли по телебаченню і в інтернеті ми бачимо безліч сюжетів, які непокоять усіх, починає здаватися, що криза охопила все наше життя. Діти можуть не розрізняти зображення на екрані та реальність навколо себе. Більше граючи з ними та відпочиваючи разом, ви допоможете дітям впоратися зі стресом. Дотримуйтеся регулярних процедур і розкладу, наскільки це можливо, особливо перед сном, або допоможіть скласти новий розклад у нових умовах.

Якщо у вашому районі зафіксовано спалах захворювання, спробуйте переконати своїх дітей у тому, що вони з великою ймовірністю не заразяться, що більшість людей, інфікованих коронавірусом, не хворіють важко і що багато дорослих наполегливо працюють над тим, щоб уберегти вашу родину.

Якщо ваша дитина почувається погано, поясніть, що їй доведеться залишитися вдома або в лікарні, оскільки це безпечніше для неї та її друзів. Скажіть їй, що ви знаєте, що часом це важко (можливо, страшно чи нудно), але дотримання правил допоможе залишатися всім у безпеці.

Переконайтеся, що діти не відчувають стигму та не поширюють її

Спалах коронавірусу приніс численні повідомлення з усього світу про расову дискримінацію, тому важливо переконатися, що ваші діти ані зазнають булінгу (цькування), ані сприяють йому.

Поясніть дітям, що коронавірус не має нічого спільного з тим, хто який має вигляд, звідки походить чи якою мовою говорить. Якщо дітей обзивають або знущаються над ними в школі, вони повинні знати, що можуть безпечно розповісти про це дорослим, яким довіряють.

Нагадуйте дітям, що кожен і кожна має право бути в безпеці в школі. Знущання — це завжди неправильно, тож ми маємо робити все, щоб добре ставитися та підтримувати одне одного. Заохочуйте дітей бути уважними до інших.

Розповідайте історії про людяність 

Дітям важливо знати, що люди допомагають одне одному, роблячи добрі справи. Поділіться, зокрема, історіями медичних працівників, вчених і молодих людей, які працюють над тим, щоб зупинити спалах. Якщо діти знатимуть, що співчутливі люди докладають зусиль для їхнього захисту, це може їх заcпокоїти.

Демонструйте спокій і впевненість 

Діти помічають, як ви реагуєте на новини, тому для них важливо бачити, що ви не панікуєте. Якщо ви занепокоєні або засмучені, знайдіть час для себе і зверніться до інших родин, друзів та людей, яким ви довіряєте. Приділіть трохи часу заняттям, які допоможуть вам розслабитися та відновити сили.

Обережно завершуйте розмови 

Дітям важливо знати, що вони не залишаться сам на сам з бідою. Коли завершуєте розмову на дражливі теми, оцініть рівень тривожності дитини: спостерігайте за мовою тіла, за тим, чи не змінився тон голосу, а також як вона дихає.

Нагадайте дітям, що вони можуть будь-коли поговорити з вами на будь-які інші теми, в тому числі складні. Переконайте, що ви піклуєтеся про них, слухаєте їх і що вони можуть звернутися до вас завжди, коли занепокоєні.

', + NULL, 'https://i.imgur.com/1VU2fe5.png', 4, 1, '02.08.2021 03:52:32.700332', '03.08.2021 03:33:33.700332', + '05.09.2021 06:52:37.700332', 'PUBLISHED', TRUE), + ('Діти та батьки і COVID-19', 'Чи небезпечний COVID-19 для дітей?', + '

Чи небезпечний COVID-19 для дітей? Що важливо врахувати про вагітність і пологи під час пандемії? Як чинити із грудним вигодовуванням? Що робити, якщо захворіла мати, яка годує грудьми? Що важливо знати про відвідування школи або дитячого садка? Як допомогти дітям впоратися з ситуацією довкола COVID-19? Дізнайтеся з нашої статті. 

Чи небезпечний COVID-19 для дітей?

Вивчення впливу нового коронавірусу на дітей триває. Сьогодні відомо, що ним можуть заразитися люди будь-якого віку, але в дітей поки що зафіксовано здебільшого легкі форми або безсимптомний перебіг хвороби.

Вагітність і пологи

Хоча поки достеменно не відомо, чи передається вірус від матері дитині під час вагітності та пологів, ВООЗ і Дитячий фонд ООН (ЮНІСЕФ) рекомендують усім вагітним жінкам:

  • Вживайте тих самих запобіжних заходів, що й інші люди: дотримуйтеся дистанції, уникайте контактів та намагайтеся за можливості отримати медичні послуги телефоном або онлайн.
  • Будьте уважними до самопочуття та телефонуйте лікарю або лікарці, якщо вас щось непокоїть або у вас температура, кашель, нежить чи інші симптоми.
  • Звертайтеся по медичну допомогу якомога раніше, якщо належите до групи ризику, у вас температура, кашель або утруднене дихання.
  • Обговоріть із акушеркою чи лікарем / лікаркою найбезпечніше, на їхню думку, місце для пологів. Складіть план дій, який допоможе вам почуватися впевненіше і спокійніше в тривожні часи. 
  • Пам''ятайте: під час пандемії COVID-19 медичний заклад для пологів потрібно обирати не за критерієм близькості до місця проживання, а за його можливостями надати відповідну медичну допомогу. Якщо ви належите до груп високого та вкрай високого перинатального ризику, ваші пологи мають відбуватися у спеціалізованих закладах, які зазвичай називають перинатальними центрами.
  • Подбайте про найперші щеплення за календарем вакцинації для немовляти.

Грудне вигодовування і COVID-19

У контексті пандемії медичні працівники й батьки ставлять багато запитань про те, як чинити з грудним вигодовуванням. За інформацією ВООЗ, станом на сьогодні активний вірус SARS-CoV-2 (вірус, який може спричинити зараження) не було виявлено в грудному молоці жодної матері з підтвердженою / імовірною хворобою COVID-19. Отже, малоймовірно, що COVID-19 передається під час годування грудьми або через грудне молоко, яке було зціджене матір''ю з підтвердженим / імовірним захворюванням на COVID-19.

ЮНІСЕФ і ВООЗ рекомендують продовжувати годувати дитину грудьми, навіть якщо ви інфіковані або є підозра на інфікування

Численні переваги грудного вигодовування значно перевищують потенційні ризики передавання вірусу та захворювання на COVID-19. Саме тому Дитячий фонд ООН (ЮНІСЕФ) і ВООЗ рекомендують продовжувати годувати дитину грудьми, навіть якщо ви інфіковані або є підозра на інфікування, а також тримати немовля на руках та забезпечувати контакт «шкіра до шкіри».

Якщо температура, кашель, нежить у матері, яка годує грудьми

  • Зателефонуйте до свого лікаря / лікарки і повідомте, що ви годуєте грудьми.
  • Для зниження температури приймайте парацетамол.
  • Годуйте грудьми так часто, як цього потребує маля.
  • Використовуйте маску під час годування грудьми, щоб унеможливити потрапляння крапель інфікованого слизу на дитину, тим самим зменшуючи ризик її зараження. Стежте за тим, щоб маска щільно прилягала до обличчя. Для цього притисніть маску до перенісся спеціальним дротиком. 
  • Мийте руки перед тим, як: надіти маску; узяти маля; торкатися грудей або зціджуватися; користуватися молоковідсмоктувачем або посудом для грудного молока.
  • Знявши маску, відразу ж викиньте її і ретельно вимийте руки (протягом 20 секунд і довше).
  • За можливості уникайте кашлю або чхання на дитину, коли годуєте грудьми.
  • Якщо краплі інфікованого слизу могли потрапити на ваші груди, перед годуванням обережно вимийте їх теплою водою з милом (протягом щонайменше 20 секунд). Загалом же мити груди перед кожним годуванням або зціджуванням не потрібно.
  • Навіть коли не йдеться про COVID-19, молоковідсмоктувачі, ємності для зберігання молока та прибори для годування потрібно ретельно мити після кожного використання рідким милом або, наприклад, рідиною для миття посуду й теплою водою. Після цього прополощіть їх гарячою водою протягом 10–15 секунд.
  • Якщо ви зціджуєтеся, ваші близькі можуть годувати маля з чашки, ложки або шприца. Перед годуванням або користуванням посудом для грудного молока вони також повинні щоразу ретельно мити руки з милом не менш ніж 20 секунд. Зціджування грудного молока також важливе для підтримання лактації, щоб ви могли продовжувати годувати грудьми, коли одужаєте.
  • Якщо є така змога, обмежте свої домашні обов''язки лише годуванням грудьми або зціджуванням. Бажано, щоб догляд за малям (купання, перевдягання, прогулянки, вкладання спати) взяли на себе партнер / чоловік, інші члени родини або близькі люди чи друзі.

Якщо підтверджено COVID-19

Якщо у вас є підстави вважати, що ви інфіковані covid-19, або хворобу підтверджено лабораторно, будь ласка, дотримуйтеся всіх застережень, які допоможуть вам убезпечити інших від зараження. 

Стан матері погіршується

Якщо з''явилися такі симптоми, як задишка, частий сухий кашель, біль у грудній клітці, температура, яку складно знизити, виражена блідість чи синюшність шкіри, повторне блювання, терміново повідомте про це лікарю / лікарці, або зверніться до служби екстреної медичної допомоги за номером 103, або їдьте до медичного закладу самостійно.

Відновлюйте годування грудьми, коли стан поліпшиться

Якщо ваш стан не дозволяє поки що годувати грудьми, повернутися до годування можна, щойно ви почуватиметеся краще. Для цього потрібний частий безпосередній контакт із малюком: прикладайте його до грудей кожні дві години, не рідше. Що молодша дитина, то швидше зазвичай відбувається відновлення. Коли молоко почне повертатися, зменшуйте додаткове харчування.

Перебування у школі й дитячих садках під час пандемії COVID-19

Навчально-виховні заклади наразі ще напрацьовують практики, які б ураховували пандемію COVID-19. Але базові принципи перебування у школі й дитячих садках уже відомі:

  • Дітям, які належать до групи ризику (мають хронічні захворювання, наприклад, бронхіальну астму, діабет, онкологічні захворювання тощо), краще навчатися онлайн — за домовленістю з учителем або вихователем.
  • Діти не мають відвідувати школу або дитячий садок, якщо в них температура, кашель, нежить.
  • Якщо ви або ваші діти контактували з хворим на COVID-19, потрібно дотримуватися самоізоляції протягом 14 днів.
  • Якщо як контактна особа ви плануєте робити ПЛР-тест собі або дітям і для цього доведеться очікувати в черзі, то дуже важливо не лише бути в масці, а й дотримуватися дистанції з іншими людьми щонайменше 2 метри.
  • Людям, які вас оточують (друзям, однокласникам дітей, учителям, шкільній адміністрації), важливо повідомити про те, що ви або ваші діти захворіли на COVID-19, адже, не знаючи цього, вони можуть поширювати інфекцію. 

Як допомогти дітям впоратися з ситуацією довкола COVID-19

Діти можуть багато бачити інформації про пандемію в інтернеті або по телебаченню, але не до кінця розуміти формулювання, зміст та мову таких повідомлень, а тому відчувати тривогу, стрес і смуток. Батьки мають пояснювати те, що діти побачили, прочитали або почули від інших людей. Відкрите обговорення та ваша підтримка допоможуть їм не лише зрозуміти ситуацію, а й поводитися у спосіб, який убезпечить їх від хвороби.

Розповідайте дітям, як захистити себе та своїх друзів

Один із найкращих способів уберегти дітей від COVID-19 та інших захворювань — заохочувати їх регулярно мити руки. Покажіть їм і постійно нагадуйте, як прикривати рот і ніс згином ліктя під час кашлю чи чхання (тобто навчайте кашльового етикету). Пояснюйте, що краще не перебувати надто близько до людей, у яких є симптоми захворювання. Попросіть дитину негайно сказати вам, якщо відчує, що в неї лихоманка, кашель або ускладнене дихання.

Пояснюйте ситуацію словами, зрозумілими для дітей 

Діти мають право на правдиву інформацію про те, що відбувається у світі. Але водночас дорослі повинні захистити їх від стресу. Використовуйте мову, відповідну віку, спостерігайте за реакцією і будьте чутливі до рівня тривоги дітей.

Якщо ви не можете відповісти на запитання дитини, не намагайтеся вигадати відповідь. Використовуйте це як можливість разом ознайомитися з інформацією. Достовірними її джерелами є сайти міжнародних організацій, наприклад, Дитячого фонду ООН (ЮНІСЕФ), ВООЗ, Центрів з контролю та профілактики захворювань в США тощо. Поясніть, що деяка інформація в інтернеті не є точною, тож краще довіряти експертам.

Визнавайте почуття дітей та запевняйте їх, що боятися — це природно. Покажіть, що ви слухаєте їх, приділяючи цьому всю свою увагу.

Обговорюйте з дітьми те, що їх хвилює

Дізнайтеся, що саме діти вже знають, і дозвольте їм визначати напрямок розмови. Якщо діти ще маленькі й не чули про спалах, можливо, вам не потрібно порушувати це питання: просто нагадайте їм про хороші гігієнічні практики.

Переконайтеся, що ви в безпечному просторі. Нехай дитина скаже все, про що думає — не обмежуйте її у цьому. Допомогти почати розмову можуть розповіді й малюнки.

Не заперечуйте почуття дітей

Найголовніше — не нехтуйте побоюваннями дітлахів і не применшуйте їхню стурбованість. Визнавайте почуття дітей та запевняйте їх, що боятися — це природно. Покажіть, що ви слухаєте їх, приділяючи цьому всю свою увагу. Дайте зрозуміти, що діти можуть поговорити з вами коли захочуть.

Знижуйте стрес і тривожність 

Коли по телебаченню і в інтернеті ми бачимо безліч сюжетів, які непокоять усіх, починає здаватися, що криза охопила все наше життя. Діти можуть не розрізняти зображення на екрані та реальність навколо себе. Більше граючи з ними та відпочиваючи разом, ви допоможете дітям впоратися зі стресом. Дотримуйтеся регулярних процедур і розкладу, наскільки це можливо, особливо перед сном, або допоможіть скласти новий розклад у нових умовах.

Якщо у вашому районі зафіксовано спалах захворювання, спробуйте переконати своїх дітей у тому, що вони з великою ймовірністю не заразяться, що більшість людей, інфікованих коронавірусом, не хворіють важко і що багато дорослих наполегливо працюють над тим, щоб уберегти вашу родину.

Якщо ваша дитина почувається погано, поясніть, що їй доведеться залишитися вдома або в лікарні, оскільки це безпечніше для неї та її друзів. Скажіть їй, що ви знаєте, що часом це важко (можливо, страшно чи нудно), але дотримання правил допоможе залишатися всім у безпеці.

Переконайтеся, що діти не відчувають стигму та не поширюють її

Спалах коронавірусу приніс численні повідомлення з усього світу про расову дискримінацію, тому важливо переконатися, що ваші діти ані зазнають булінгу (цькування), ані сприяють йому.

Поясніть дітям, що коронавірус не має нічого спільного з тим, хто який має вигляд, звідки походить чи якою мовою говорить. Якщо дітей обзивають або знущаються над ними в школі, вони повинні знати, що можуть безпечно розповісти про це дорослим, яким довіряють.

Нагадуйте дітям, що кожен і кожна має право бути в безпеці в школі. Знущання — це завжди неправильно, тож ми маємо робити все, щоб добре ставитися та підтримувати одне одного. Заохочуйте дітей бути уважними до інших.

Розповідайте історії про людяність 

Дітям важливо знати, що люди допомагають одне одному, роблячи добрі справи. Поділіться, зокрема, історіями медичних працівників, вчених і молодих людей, які працюють над тим, щоб зупинити спалах. Якщо діти знатимуть, що співчутливі люди докладають зусиль для їхнього захисту, це може їх заcпокоїти.

Демонструйте спокій і впевненість 

Діти помічають, як ви реагуєте на новини, тому для них важливо бачити, що ви не панікуєте. Якщо ви занепокоєні або засмучені, знайдіть час для себе і зверніться до інших родин, друзів та людей, яким ви довіряєте. Приділіть трохи часу заняттям, які допоможуть вам розслабитися та відновити сили.

Обережно завершуйте розмови 

Дітям важливо знати, що вони не залишаться сам на сам з бідою. Коли завершуєте розмову на дражливі теми, оцініть рівень тривожності дитини: спостерігайте за мовою тіла, за тим, чи не змінився тон голосу, а також як вона дихає.

Нагадайте дітям, що вони можуть будь-коли поговорити з вами на будь-які інші теми, в тому числі складні. Переконайте, що ви піклуєтеся про них, слухаєте їх і що вони можуть звернутися до вас завжди, коли занепокоєні.

', + NULL, 'https://i.imgur.com/O7TkvGg.png', 4, 1, '03.08.2021 03:53:33.700332', '05.08.2021 03:35:35.700332', + '06.09.2021 08:51:37.700332', 'PUBLISHED', FALSE ); + +INSERT INTO public.posts_origins (post_id, origin_id) +VALUES (1, 1), + (2, 1), + (3, 1); + +INSERT INTO public.posts_tags (post_id, tag_id) +VALUES (1, 1), + (1, 2), + (2, 3), + (2, 4), + (3, 5), + (3, 6); + +INSERT INTO public.posts_directions (post_id, direction_id) +VALUES (1, 1), + (1, 2), + (1, 3), + (2, 4), + (2, 5), + (3, 6), + (3, 7); + +UPDATE public.directions SET has_doctors = TRUE; + +UPDATE public.directions SET has_posts = TRUE; +-- Three post were added \ No newline at end of file