├── src ├── main │ ├── java │ │ └── movies │ │ │ └── spring │ │ │ └── data │ │ │ └── neo4j │ │ │ ├── api │ │ │ ├── service │ │ │ │ ├── movie │ │ │ │ │ ├── dto │ │ │ │ │ │ ├── LinkDTO.kt │ │ │ │ │ │ ├── NodeDTO.kt │ │ │ │ │ │ ├── PersonDTO.kt │ │ │ │ │ │ ├── RoleDTO.kt │ │ │ │ │ │ ├── MovieDTO.kt │ │ │ │ │ │ └── GraphDTO.kt │ │ │ │ │ └── MovieService.kt │ │ │ │ ├── EntityDTOMapper.kt │ │ │ │ ├── authorization │ │ │ │ │ ├── dto │ │ │ │ │ │ ├── CredentialsDTO.kt │ │ │ │ │ │ └── AuthorizationDTO.kt │ │ │ │ │ ├── AuthorizationService.kt │ │ │ │ │ └── AuthorizationServicePasswordImpl.kt │ │ │ │ └── user │ │ │ │ │ ├── UserService.kt │ │ │ │ │ └── dto │ │ │ │ │ └── UserDTO.kt │ │ │ └── endpoints │ │ │ │ ├── pub │ │ │ │ └── AuthorizationController.kt │ │ │ │ ├── secured │ │ │ │ ├── UserController.kt │ │ │ │ └── MovieController.kt │ │ │ │ └── aspects │ │ │ │ └── AuthenticatedProfileIdAspect.kt │ │ │ ├── infrastructure │ │ │ └── security │ │ │ │ ├── SessionCredentials.kt │ │ │ │ ├── TokenAuthenticationProvider.kt │ │ │ │ └── TokenAuthenticationFilter.kt │ │ │ ├── exception │ │ │ └── Exceptions.kt │ │ │ ├── utils │ │ │ └── Session+ApplicationUtils.kt │ │ │ ├── repositories │ │ │ ├── PersonRepository.kt │ │ │ ├── MovieRepository.kt │ │ │ └── UserRepository.kt │ │ │ ├── domain │ │ │ └── model │ │ │ │ └── persistent │ │ │ │ ├── entities │ │ │ │ ├── Person.kt │ │ │ │ ├── Role.kt │ │ │ │ ├── User.kt │ │ │ │ └── Movie.kt │ │ │ │ └── queries │ │ │ │ └── Principal.kt │ │ │ ├── ApplicationConfig.kt │ │ │ └── SecurityConfig.kt │ └── resources │ │ └── application.properties └── test │ ├── resources │ └── logback-test.xml │ └── java │ └── movies │ └── spring │ └── data │ └── neo4j │ ├── api │ ├── endpoints │ │ ├── secured │ │ │ ├── UserIntegrationTests.kt │ │ │ └── MovieIntegrationTests.kt │ │ ├── pub │ │ │ └── AuthorizationIntegrationTests.kt │ │ └── ControllerTest.kt │ └── service │ │ └── authorization │ │ └── AuthorizationServiceTest.kt │ ├── TestDataLoader.kt │ └── repositories │ └── MovieRepositoryTest.kt ├── .idea ├── compiler.xml ├── libraries │ ├── Gradle__junit_junit_4_12.xml │ ├── Gradle__org_ow2_asm_asm_5_0_4.xml │ ├── Gradle__org_neo4j_neo4j_3_1_2.xml │ ├── Gradle__net_sf_opencsv_opencsv_2_3.xml │ ├── Gradle__org_neo4j_neo4j_io_3_1_2.xml │ ├── Gradle__commons_io_commons_io_2_4.xml │ ├── Gradle__net_minidev_json_smart_2_3.xml │ ├── Gradle__org_neo4j_neo4j_csv_3_1_2.xml │ ├── Gradle__org_neo4j_neo4j_jmx_3_1_2.xml │ ├── Gradle__org_neo4j_neo4j_udc_3_1_2.xml │ ├── Gradle__org_neo4j_neo4j_dbms_3_1_2.xml │ ├── Gradle__org_objenesis_objenesis_2_6.xml │ ├── Gradle__org_slf4j_slf4j_api_1_7_25.xml │ ├── Gradle__org_neo4j_neo4j_common_3_1_2.xml │ ├── Gradle__org_neo4j_neo4j_cypher_3_1_2.xml │ ├── Gradle__org_neo4j_neo4j_kernel_3_1_2.xml │ ├── Gradle__org_neo4j_neo4j_unsafe_3_1_2.xml │ ├── Gradle__org_hamcrest_hamcrest_core_1_3.xml │ ├── Gradle__org_jetbrains_annotations_13_0.xml │ ├── Gradle__org_neo4j_neo4j_codegen_3_1_2.xml │ ├── Gradle__org_neo4j_neo4j_logging_3_1_2.xml │ ├── Gradle__org_slf4j_jul_to_slf4j_1_7_25.xml │ ├── Gradle__org_neo4j_neo4j_resource_3_1_2.xml │ ├── Gradle__org_skyscreamer_jsonassert_1_5_0.xml │ ├── Gradle__net_minidev_accessors_smart_1_2.xml │ ├── Gradle__org_slf4j_jcl_over_slf4j_1_7_25.xml │ ├── Gradle__ch_qos_logback_logback_core_1_2_3.xml │ ├── Gradle__com_jayway_jsonpath_json_path_2_4_0.xml │ ├── Gradle__org_ccil_cowan_tagsoup_tagsoup_1_2_1.xml │ ├── Gradle__org_hamcrest_hamcrest_library_1_3.xml │ ├── Gradle__com_jayway_restassured_xml_path_2_4_0.xml │ ├── Gradle__org_apache_lucene_lucene_core_5_5_0.xml │ ├── Gradle__org_neo4j_neo4j_graph_algo_3_1_2.xml │ ├── Gradle__org_parboiled_parboiled_core_1_1_7.xml │ ├── Gradle__org_neo4j_neo4j_collections_3_1_2.xml │ ├── Gradle__org_neo4j_neo4j_graphdb_api_3_1_2.xml │ ├── Gradle__org_neo4j_neo4j_import_tool_3_1_2.xml │ ├── Gradle__org_scala_lang_scala_library_2_11_8.xml │ ├── Gradle__org_scala_lang_scala_reflect_2_11_8.xml │ ├── Gradle__ch_qos_logback_logback_classic_1_2_3.xml │ ├── Gradle__com_jayway_restassured_json_path_2_4_0.xml │ ├── Gradle__org_apache_lucene_lucene_codecs_5_5_0.xml │ ├── Gradle__org_neo4j_neo4j_command_line_3_1_2.xml │ ├── Gradle__org_neo4j_neo4j_lucene_index_3_1_2.xml │ ├── Gradle__org_neo4j_neo4j_graph_matching_3_1_2.xml │ ├── Gradle__org_neo4j_neo4j_lucene_upgrade_3_1_2.xml │ ├── Gradle__com_jayway_restassured_rest_assured_2_4_0.xml │ ├── Gradle__org_apache_httpcomponents_httpclient_4_5_3.xml │ ├── Gradle__org_apache_commons_commons_compress_1_12.xml │ ├── Gradle__org_apache_lucene_lucene_queryparser_5_5_0.xml │ ├── Gradle__org_neo4j_neo4j_consistency_check_3_1_2.xml │ ├── Gradle__org_parboiled_parboiled_scala_2_11_1_1_7.xml │ ├── Gradle__javax_annotation_javax_annotation_api_1_3_2.xml │ ├── Gradle__javax_validation_validation_api_2_0_1_Final.xml │ ├── Gradle__org_jboss_logging_jboss_logging_3_3_2_Final.xml │ ├── Gradle__org_neo4j_neo4j_cypher_compiler_2_3_2_3_9.xml │ ├── Gradle__org_neo4j_neo4j_cypher_compiler_3_0_3_0_8.xml │ ├── Gradle__org_neo4j_neo4j_cypher_compiler_3_1_3_1_2.xml │ ├── Gradle__org_neo4j_neo4j_cypher_frontend_2_3_2_3_9.xml │ ├── Gradle__org_neo4j_neo4j_cypher_frontend_3_0_3_0_8.xml │ ├── Gradle__org_neo4j_neo4j_cypher_frontend_3_1_3_1_2.xml │ ├── Gradle__org_neo4j_neo4j_ogm_embedded_driver_3_0_1.xml │ ├── Gradle__org_apache_lucene_lucene_backward_codecs_5_5_0.xml │ ├── Gradle__org_neo4j_neo4j_primitive_collections_3_1_2.xml │ ├── Gradle__com_jayway_restassured_rest_assured_common_2_4_0.xml │ ├── Gradle__org_apache_lucene_lucene_analyzers_common_5_5_0.xml │ ├── Gradle__com_fasterxml_jackson_core_jackson_annotations_2_9_0.xml │ ├── Gradle__com_vaadin_external_google_android_json_0_0_20131108_vaadin1.xml │ └── Gradle__com_googlecode_concurrentlinkedhashmap_concurrentlinkedhashmap_lru_1_4_2.xml └── modules │ ├── movies-kotlin-spring-data-neo4j_main.iml │ └── movies-kotlin-spring-data-neo4j_test.iml ├── .gitignore ├── README.md └── LICENSE /src/main/java/movies/spring/data/neo4j/api/service/movie/dto/LinkDTO.kt: -------------------------------------------------------------------------------- 1 | package movies.spring.data.neo4j.api.service.movie.dto 2 | 3 | data class LinkDTO(val source: Int, 4 | val target: Int) -------------------------------------------------------------------------------- /src/main/java/movies/spring/data/neo4j/api/service/movie/dto/NodeDTO.kt: -------------------------------------------------------------------------------- 1 | package movies.spring.data.neo4j.api.service.movie.dto 2 | 3 | data class NodeDTO(val title: String, 4 | val label: String) -------------------------------------------------------------------------------- /src/main/java/movies/spring/data/neo4j/infrastructure/security/SessionCredentials.kt: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | package movies.spring.data.neo4j.infrastructure.security 5 | 6 | data class SessionCredentials(val apiKey: String, val accessToken: String) -------------------------------------------------------------------------------- /src/main/java/movies/spring/data/neo4j/exception/Exceptions.kt: -------------------------------------------------------------------------------- 1 | package movies.spring.data.neo4j.exception 2 | 3 | class UnauthorizedException(message: String) : RuntimeException(message) 4 | class NotFoundException(message: String) : RuntimeException(message) -------------------------------------------------------------------------------- /src/main/java/movies/spring/data/neo4j/utils/Session+ApplicationUtils.kt: -------------------------------------------------------------------------------- 1 | package movies.spring.data.neo4j.utils 2 | 3 | import org.neo4j.ogm.model.Result 4 | import org.neo4j.ogm.session.Session 5 | 6 | fun Session.query(query: String) : Result 7 | { 8 | return this.query(query, mapOf()) 9 | } -------------------------------------------------------------------------------- /src/test/resources/logback-test.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/main/java/movies/spring/data/neo4j/api/service/EntityDTOMapper.kt: -------------------------------------------------------------------------------- 1 | package movies.spring.data.neo4j.api.service 2 | 3 | interface EntityDTOMapper { 4 | 5 | fun mapFromEntities(entities: Collection): Collection { 6 | return entities.map { fromEntity(it) } 7 | } 8 | 9 | fun fromEntity(entity: E): D 10 | 11 | } -------------------------------------------------------------------------------- /.idea/compiler.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /src/main/java/movies/spring/data/neo4j/api/service/authorization/dto/CredentialsDTO.kt: -------------------------------------------------------------------------------- 1 | package movies.spring.data.neo4j.api.service.authorization.dto 2 | 3 | import com.fasterxml.jackson.annotation.JsonInclude 4 | 5 | @JsonInclude(JsonInclude.Include.NON_NULL) 6 | data class CredentialsDTO( 7 | val email: String, 8 | val password: String) 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled class file 2 | *.class 3 | 4 | # Log file 5 | *.log 6 | 7 | # BlueJ files 8 | *.ctxt 9 | 10 | # Mobile Tools for Java (J2ME) 11 | .mtj.tmp/ 12 | 13 | # Package Files # 14 | *.jar 15 | *.war 16 | *.ear 17 | *.zip 18 | *.tar.gz 19 | *.rar 20 | 21 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 22 | hs_err_pid* 23 | 24 | .idea 25 | -------------------------------------------------------------------------------- /src/main/java/movies/spring/data/neo4j/api/service/authorization/AuthorizationService.kt: -------------------------------------------------------------------------------- 1 | package movies.spring.data.neo4j.api.service.authorization 2 | 3 | import movies.spring.data.neo4j.api.service.authorization.dto.AuthorizationDTO 4 | import movies.spring.data.neo4j.api.service.authorization.dto.CredentialsDTO 5 | 6 | interface AuthorizationService 7 | { 8 | fun authorize(request: CredentialsDTO): AuthorizationDTO 9 | } -------------------------------------------------------------------------------- /src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | spring.data.neo4j.driver=org.neo4j.ogm.drivers.embedded.driver.EmbeddedDriver 2 | 3 | #org.springframework.data.rest.level=DEBUG 4 | #debug: true 5 | #spring.data.neo4j.uri=bolt://localhost 6 | #spring.data.neo4j.username=neo4j 7 | #spring.data.neo4j.password=h4ckM3 8 | indexes.auto=assert 9 | 10 | 11 | api.key=e8f3fdcc-8825-11e5-af63-feff819cdc9f 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /src/main/java/movies/spring/data/neo4j/repositories/PersonRepository.kt: -------------------------------------------------------------------------------- 1 | package movies.spring.data.neo4j.repositories 2 | 3 | import movies.spring.data.neo4j.domain.model.persistent.entities.Person 4 | import org.springframework.data.repository.PagingAndSortingRepository 5 | import org.springframework.stereotype.Repository 6 | 7 | 8 | @Repository 9 | interface PersonRepository : PagingAndSortingRepository 10 | -------------------------------------------------------------------------------- /src/main/java/movies/spring/data/neo4j/domain/model/persistent/entities/Person.kt: -------------------------------------------------------------------------------- 1 | package movies.spring.data.neo4j.domain.model.persistent.entities 2 | 3 | 4 | import org.neo4j.ogm.annotation.Id 5 | import org.neo4j.ogm.annotation.NodeEntity 6 | import org.neo4j.ogm.annotation.Relationship 7 | import java.util.* 8 | 9 | 10 | @NodeEntity 11 | class Person(@Id var name: String, 12 | var born: Long, 13 | @Relationship(type = "ACTED_IN") var movies: List = ArrayList()) 14 | -------------------------------------------------------------------------------- /.idea/libraries/Gradle__junit_junit_4_12.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /.idea/libraries/Gradle__org_ow2_asm_asm_5_0_4.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /.idea/libraries/Gradle__org_neo4j_neo4j_3_1_2.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /src/main/java/movies/spring/data/neo4j/api/service/movie/dto/PersonDTO.kt: -------------------------------------------------------------------------------- 1 | package movies.spring.data.neo4j.api.service.movie.dto 2 | 3 | import movies.spring.data.neo4j.api.service.EntityDTOMapper 4 | import movies.spring.data.neo4j.domain.model.persistent.entities.Person 5 | 6 | data class PersonDTO(val name: String, 7 | val born: Long) { 8 | 9 | companion object : EntityDTOMapper { 10 | 11 | override fun fromEntity(entity: Person) = 12 | PersonDTO(name = entity.name, born = entity.born) 13 | } 14 | } 15 | 16 | -------------------------------------------------------------------------------- /.idea/libraries/Gradle__net_sf_opencsv_opencsv_2_3.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /.idea/libraries/Gradle__org_neo4j_neo4j_io_3_1_2.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /.idea/libraries/Gradle__commons_io_commons_io_2_4.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /.idea/libraries/Gradle__net_minidev_json_smart_2_3.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /.idea/libraries/Gradle__org_neo4j_neo4j_csv_3_1_2.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /.idea/libraries/Gradle__org_neo4j_neo4j_jmx_3_1_2.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /.idea/libraries/Gradle__org_neo4j_neo4j_udc_3_1_2.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /.idea/libraries/Gradle__org_neo4j_neo4j_dbms_3_1_2.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /.idea/libraries/Gradle__org_objenesis_objenesis_2_6.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /.idea/libraries/Gradle__org_slf4j_slf4j_api_1_7_25.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /.idea/libraries/Gradle__org_neo4j_neo4j_common_3_1_2.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /.idea/libraries/Gradle__org_neo4j_neo4j_cypher_3_1_2.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /.idea/libraries/Gradle__org_neo4j_neo4j_kernel_3_1_2.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /.idea/libraries/Gradle__org_neo4j_neo4j_unsafe_3_1_2.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /.idea/libraries/Gradle__org_hamcrest_hamcrest_core_1_3.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /.idea/libraries/Gradle__org_jetbrains_annotations_13_0.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /.idea/libraries/Gradle__org_neo4j_neo4j_codegen_3_1_2.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /.idea/libraries/Gradle__org_neo4j_neo4j_logging_3_1_2.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /.idea/libraries/Gradle__org_slf4j_jul_to_slf4j_1_7_25.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /.idea/libraries/Gradle__org_neo4j_neo4j_resource_3_1_2.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /.idea/libraries/Gradle__org_skyscreamer_jsonassert_1_5_0.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /.idea/libraries/Gradle__net_minidev_accessors_smart_1_2.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /.idea/libraries/Gradle__org_slf4j_jcl_over_slf4j_1_7_25.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /src/main/java/movies/spring/data/neo4j/api/service/movie/dto/RoleDTO.kt: -------------------------------------------------------------------------------- 1 | package movies.spring.data.neo4j.api.service.movie.dto 2 | 3 | import movies.spring.data.neo4j.api.service.EntityDTOMapper 4 | import movies.spring.data.neo4j.domain.model.persistent.entities.Role 5 | 6 | data class RoleDTO(val person: PersonDTO, 7 | val roles: Collection) { 8 | 9 | companion object : EntityDTOMapper { 10 | 11 | override fun fromEntity(entity: Role): RoleDTO = 12 | RoleDTO(person = PersonDTO.fromEntity(entity.person), roles = entity.roles) 13 | 14 | 15 | } 16 | } 17 | 18 | -------------------------------------------------------------------------------- /.idea/libraries/Gradle__ch_qos_logback_logback_core_1_2_3.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /.idea/libraries/Gradle__com_jayway_jsonpath_json_path_2_4_0.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /.idea/libraries/Gradle__org_ccil_cowan_tagsoup_tagsoup_1_2_1.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /.idea/libraries/Gradle__org_hamcrest_hamcrest_library_1_3.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /.idea/libraries/Gradle__com_jayway_restassured_xml_path_2_4_0.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /.idea/libraries/Gradle__org_apache_lucene_lucene_core_5_5_0.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /.idea/libraries/Gradle__org_neo4j_neo4j_graph_algo_3_1_2.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /.idea/libraries/Gradle__org_parboiled_parboiled_core_1_1_7.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /.idea/libraries/Gradle__org_neo4j_neo4j_collections_3_1_2.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /.idea/libraries/Gradle__org_neo4j_neo4j_graphdb_api_3_1_2.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /.idea/libraries/Gradle__org_neo4j_neo4j_import_tool_3_1_2.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /.idea/libraries/Gradle__org_scala_lang_scala_library_2_11_8.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /.idea/libraries/Gradle__org_scala_lang_scala_reflect_2_11_8.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /.idea/libraries/Gradle__ch_qos_logback_logback_classic_1_2_3.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /.idea/libraries/Gradle__com_jayway_restassured_json_path_2_4_0.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /.idea/libraries/Gradle__org_apache_lucene_lucene_codecs_5_5_0.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /.idea/libraries/Gradle__org_neo4j_neo4j_command_line_3_1_2.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /.idea/libraries/Gradle__org_neo4j_neo4j_lucene_index_3_1_2.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /.idea/libraries/Gradle__org_neo4j_neo4j_graph_matching_3_1_2.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /.idea/libraries/Gradle__org_neo4j_neo4j_lucene_upgrade_3_1_2.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /src/main/java/movies/spring/data/neo4j/domain/model/persistent/queries/Principal.kt: -------------------------------------------------------------------------------- 1 | 2 | 3 | package movies.spring.data.neo4j.domain.model.persistent.queries 4 | 5 | import org.springframework.data.neo4j.annotation.QueryResult 6 | 7 | 8 | @QueryResult 9 | class Principal (var applicationToken: String, 10 | var profileId: String) 11 | { 12 | 13 | override fun toString(): String 14 | { 15 | if (applicationToken.isNullOrEmpty()) 16 | { 17 | return "Empty Principal" 18 | } else 19 | { 20 | return "Principal {token: $applicationToken, uuid: $profileId" 21 | } 22 | } 23 | 24 | } -------------------------------------------------------------------------------- /.idea/libraries/Gradle__com_jayway_restassured_rest_assured_2_4_0.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /.idea/libraries/Gradle__org_apache_httpcomponents_httpclient_4_5_3.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /.idea/libraries/Gradle__org_apache_commons_commons_compress_1_12.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /.idea/libraries/Gradle__org_apache_lucene_lucene_queryparser_5_5_0.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /.idea/libraries/Gradle__org_neo4j_neo4j_consistency_check_3_1_2.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /.idea/libraries/Gradle__org_parboiled_parboiled_scala_2_11_1_1_7.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /.idea/libraries/Gradle__javax_annotation_javax_annotation_api_1_3_2.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /.idea/libraries/Gradle__javax_validation_validation_api_2_0_1_Final.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /.idea/libraries/Gradle__org_jboss_logging_jboss_logging_3_3_2_Final.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /.idea/libraries/Gradle__org_neo4j_neo4j_cypher_compiler_2_3_2_3_9.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /.idea/libraries/Gradle__org_neo4j_neo4j_cypher_compiler_3_0_3_0_8.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /.idea/libraries/Gradle__org_neo4j_neo4j_cypher_compiler_3_1_3_1_2.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /.idea/libraries/Gradle__org_neo4j_neo4j_cypher_frontend_2_3_2_3_9.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /.idea/libraries/Gradle__org_neo4j_neo4j_cypher_frontend_3_0_3_0_8.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /.idea/libraries/Gradle__org_neo4j_neo4j_cypher_frontend_3_1_3_1_2.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /.idea/libraries/Gradle__org_neo4j_neo4j_ogm_embedded_driver_3_0_1.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /src/main/java/movies/spring/data/neo4j/api/service/authorization/dto/AuthorizationDTO.kt: -------------------------------------------------------------------------------- 1 | package movies.spring.data.neo4j.api.service.authorization.dto 2 | 3 | import movies.spring.data.neo4j.api.service.EntityDTOMapper 4 | import movies.spring.data.neo4j.domain.model.persistent.entities.User 5 | 6 | data class AuthorizationDTO( 7 | val accessToken: String, 8 | val profileId: String) { 9 | 10 | companion object : EntityDTOMapper { 11 | 12 | override fun fromEntity(entity: User) = AuthorizationDTO( 13 | accessToken = entity.applicationToken!!, 14 | profileId = entity.uuid) 15 | 16 | } 17 | } 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /.idea/libraries/Gradle__org_apache_lucene_lucene_backward_codecs_5_5_0.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /.idea/libraries/Gradle__org_neo4j_neo4j_primitive_collections_3_1_2.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /.idea/libraries/Gradle__com_jayway_restassured_rest_assured_common_2_4_0.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /.idea/libraries/Gradle__org_apache_lucene_lucene_analyzers_common_5_5_0.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /.idea/libraries/Gradle__com_fasterxml_jackson_core_jackson_annotations_2_9_0.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /.idea/libraries/Gradle__com_vaadin_external_google_android_json_0_0_20131108_vaadin1.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /src/main/java/movies/spring/data/neo4j/domain/model/persistent/entities/Role.kt: -------------------------------------------------------------------------------- 1 | package movies.spring.data.neo4j.domain.model.persistent.entities 2 | 3 | import java.util.ArrayList 4 | 5 | import com.fasterxml.jackson.annotation.JsonIdentityInfo 6 | import com.fasterxml.jackson.annotation.ObjectIdGenerators 7 | import org.neo4j.ogm.annotation.* 8 | 9 | @JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator::class, property = "id") 10 | @RelationshipEntity(type = "ACTED_IN") 11 | class Role(@Id @GeneratedValue var id: Long? = null, 12 | @StartNode var person: Person, 13 | @EndNode var movie: Movie, 14 | var roles: ArrayList = ArrayList()) { 15 | 16 | fun addRoleName(name: String) { 17 | this.roles.add(name) 18 | } 19 | 20 | } 21 | -------------------------------------------------------------------------------- /.idea/libraries/Gradle__com_googlecode_concurrentlinkedhashmap_concurrentlinkedhashmap_lru_1_4_2.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /src/main/java/movies/spring/data/neo4j/api/endpoints/pub/AuthorizationController.kt: -------------------------------------------------------------------------------- 1 | package movies.spring.data.neo4j.api.endpoints.pub 2 | 3 | 4 | import movies.spring.data.neo4j.api.service.authorization.AuthorizationService 5 | import movies.spring.data.neo4j.api.service.authorization.dto.AuthorizationDTO 6 | import movies.spring.data.neo4j.api.service.authorization.dto.CredentialsDTO 7 | import movies.spring.data.neo4j.repositories.UserRepository 8 | import org.springframework.beans.factory.annotation.Autowired 9 | import org.springframework.web.bind.annotation.* 10 | 11 | 12 | @RestController 13 | class AuthorizationController(val authService: AuthorizationService) { 14 | 15 | @PostMapping("/authorization") 16 | fun authorize(@RequestBody credentials: CredentialsDTO) = authService.authorize(credentials) 17 | 18 | } -------------------------------------------------------------------------------- /src/main/java/movies/spring/data/neo4j/repositories/MovieRepository.kt: -------------------------------------------------------------------------------- 1 | package movies.spring.data.neo4j.repositories 2 | 3 | import movies.spring.data.neo4j.domain.model.persistent.entities.Movie 4 | import org.springframework.data.neo4j.annotation.Query 5 | import org.springframework.data.repository.PagingAndSortingRepository 6 | import org.springframework.data.repository.query.Param 7 | import org.springframework.stereotype.Repository 8 | 9 | 10 | @Repository 11 | interface MovieRepository : PagingAndSortingRepository { 12 | 13 | fun findByTitle(@Param("title") title: String): Movie? 14 | 15 | fun findByTitleContaining(title: String): Collection 16 | 17 | @Query("MATCH (m:Movie)<-[r:ACTED_IN]-(a:Person) RETURN m,r,a LIMIT {limit}") 18 | fun graph(@Param("limit") limit: Int): Collection 19 | } 20 | 21 | -------------------------------------------------------------------------------- /src/main/java/movies/spring/data/neo4j/api/service/user/UserService.kt: -------------------------------------------------------------------------------- 1 | package movies.spring.data.neo4j.api.service.user 2 | 3 | import movies.spring.data.neo4j.api.service.user.dto.UserDTO 4 | import movies.spring.data.neo4j.repositories.UserRepository 5 | import org.springframework.beans.factory.annotation.Autowired 6 | import org.springframework.stereotype.Component 7 | import org.springframework.transaction.annotation.Transactional 8 | import java.util.* 9 | 10 | @Component 11 | class UserService constructor(private val userRepository: UserRepository) 12 | { 13 | 14 | @Transactional 15 | fun getMyUser(profileId: String): UserDTO 16 | { 17 | val user = userRepository.findByUuid(profileId)!! 18 | user.lastActive = Date() 19 | userRepository.save(user) 20 | 21 | return UserDTO.fromEntity(user) 22 | } 23 | } -------------------------------------------------------------------------------- /src/main/java/movies/spring/data/neo4j/api/service/movie/dto/MovieDTO.kt: -------------------------------------------------------------------------------- 1 | package movies.spring.data.neo4j.api.service.movie.dto 2 | 3 | import movies.spring.data.neo4j.api.service.EntityDTOMapper 4 | import movies.spring.data.neo4j.domain.model.persistent.entities.Movie 5 | 6 | class MovieDTO(val title: String, 7 | val uuid: String? = null, 8 | val releasedYear: Long, 9 | val tagLine: String?, 10 | val roles: Collection) { 11 | 12 | companion object : EntityDTOMapper { 13 | 14 | override fun fromEntity(entity: Movie) = MovieDTO( 15 | title = entity.title, 16 | releasedYear = entity.releasedYear, 17 | tagLine = entity.tagLine, 18 | roles = RoleDTO.mapFromEntities(entity.roles)) 19 | } 20 | 21 | } 22 | 23 | 24 | -------------------------------------------------------------------------------- /src/main/java/movies/spring/data/neo4j/api/endpoints/secured/UserController.kt: -------------------------------------------------------------------------------- 1 | package movies.spring.data.neo4j.api.endpoints.secured 2 | 3 | import movies.spring.data.neo4j.api.endpoints.aspects.Authenticated 4 | import movies.spring.data.neo4j.api.service.user.dto.UserDTO 5 | import movies.spring.data.neo4j.api.service.user.UserService 6 | import org.springframework.beans.factory.annotation.Autowired 7 | import org.springframework.web.bind.annotation.GetMapping 8 | import org.springframework.web.bind.annotation.RequestMapping 9 | import org.springframework.web.bind.annotation.RequestMethod 10 | import org.springframework.web.bind.annotation.RestController 11 | 12 | @RestController 13 | @RequestMapping("/user") 14 | class UserController(private val userService: UserService) { 15 | 16 | @GetMapping("/mine") 17 | fun getMyProfile(@Authenticated profileId: String) = userService.getMyUser(profileId) 18 | 19 | } -------------------------------------------------------------------------------- /src/main/java/movies/spring/data/neo4j/api/service/user/dto/UserDTO.kt: -------------------------------------------------------------------------------- 1 | package movies.spring.data.neo4j.api.service.user.dto 2 | 3 | import com.fasterxml.jackson.annotation.JsonInclude 4 | import com.fasterxml.jackson.annotation.JsonInclude.Include 5 | import movies.spring.data.neo4j.domain.model.persistent.entities.User 6 | import movies.spring.data.neo4j.api.service.EntityDTOMapper 7 | 8 | 9 | @JsonInclude(Include.NON_NULL) 10 | data class UserDTO( 11 | val uuid: String, 12 | val firstName: String, 13 | val lastName: String, 14 | val email: String) { 15 | 16 | companion object : EntityDTOMapper { 17 | 18 | override fun fromEntity(entity: User) = UserDTO( 19 | uuid = entity.uuid, 20 | firstName = entity.firstName, 21 | lastName = entity.lastName, 22 | email = entity.email) 23 | } 24 | } 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /src/main/java/movies/spring/data/neo4j/repositories/UserRepository.kt: -------------------------------------------------------------------------------- 1 | package movies.spring.data.neo4j.repositories 2 | 3 | 4 | import movies.spring.data.neo4j.domain.model.persistent.entities.User 5 | import movies.spring.data.neo4j.domain.model.persistent.queries.Principal 6 | import org.springframework.cache.annotation.Cacheable 7 | import org.springframework.data.neo4j.annotation.Query 8 | import org.springframework.data.repository.CrudRepository 9 | import org.springframework.stereotype.Repository 10 | 11 | @Repository 12 | interface UserRepository : CrudRepository 13 | { 14 | 15 | fun findByUuid(id: String): User? 16 | 17 | @Cacheable("AuthorizationCache") 18 | @Query("MATCH (n:User {applicationToken: {0} }) RETURN n.uuid as profileId, n.applicationToken as applicationToken, n.roles as roles;") 19 | fun findByApplicationToken(token: String): Principal? 20 | 21 | fun findByEmail(email: String): User? 22 | 23 | } 24 | 25 | -------------------------------------------------------------------------------- /src/test/java/movies/spring/data/neo4j/api/endpoints/secured/UserIntegrationTests.kt: -------------------------------------------------------------------------------- 1 | package movies.spring.data.neo4j.api.endpoints.secured 2 | 3 | import com.jayway.restassured.RestAssured 4 | import movies.spring.data.neo4j.api.endpoints.ControllerTest 5 | import org.hamcrest.core.IsNull 6 | import org.junit.Test 7 | import org.springframework.beans.factory.annotation.Autowired 8 | import org.springframework.beans.factory.annotation.Value 9 | 10 | class UserIntegrationTests : ControllerTest() { 11 | 12 | @Autowired @Value("@{api.key}") lateinit var apiKey: String 13 | 14 | @Test 15 | fun getMyUser() { 16 | val accessToken: String = login("jasper@appsquick.ly", "password").accessToken 17 | 18 | RestAssured.given().header("Access-Token", accessToken). 19 | header("API-Key", apiKey). 20 | get("/user/mine"). 21 | peek().then(). 22 | body("email", IsNull.notNullValue()) 23 | 24 | } 25 | 26 | 27 | 28 | 29 | } 30 | 31 | -------------------------------------------------------------------------------- /src/test/java/movies/spring/data/neo4j/TestDataLoader.kt: -------------------------------------------------------------------------------- 1 | package movies.spring.data.neo4j 2 | 3 | import movies.spring.data.neo4j.domain.model.persistent.entities.User 4 | import movies.spring.data.neo4j.repositories.UserRepository 5 | import org.springframework.stereotype.Service 6 | import org.springframework.transaction.annotation.Transactional 7 | 8 | /** 9 | * Create a test user for integration tests, until sign up feature implemented. 10 | */ 11 | @Service 12 | class TestDataLoader(private val userRepo: UserRepository) { 13 | 14 | init { 15 | this.addTestUser() 16 | } 17 | 18 | 19 | @Transactional 20 | fun addTestUser() { 21 | val email = "jasper@appsquick.ly" 22 | val jasper = userRepo.findByEmail(email) 23 | if (jasper == null) { 24 | userRepo.save(User( 25 | firstName = "Jasper", 26 | lastName = "Blues", 27 | email = email, 28 | password = "password")) 29 | } 30 | } 31 | 32 | } -------------------------------------------------------------------------------- /src/test/java/movies/spring/data/neo4j/api/endpoints/pub/AuthorizationIntegrationTests.kt: -------------------------------------------------------------------------------- 1 | package movies.spring.data.neo4j.api.endpoints.pub 2 | 3 | import com.jayway.restassured.RestAssured 4 | import movies.spring.data.neo4j.api.endpoints.ControllerTest 5 | import movies.spring.data.neo4j.api.service.authorization.dto.CredentialsDTO 6 | import org.hamcrest.core.IsNull 7 | import org.junit.Test 8 | 9 | 10 | class AuthorizationIntegrationTests : ControllerTest() 11 | { 12 | 13 | @Test 14 | fun authorize_withUserNameAndPassword() 15 | { 16 | 17 | val testCredentials = mapper.writeValueAsString( 18 | CredentialsDTO(email = "jasper@appsquick.ly", password = "password")) 19 | 20 | println(testCredentials) 21 | 22 | RestAssured.given() 23 | .header("content-type", "application/json") 24 | .body(testCredentials) 25 | .post("/authorization").peek().then() 26 | .body("accessToken", IsNull.notNullValue()) 27 | } 28 | 29 | 30 | } -------------------------------------------------------------------------------- /src/main/java/movies/spring/data/neo4j/domain/model/persistent/entities/User.kt: -------------------------------------------------------------------------------- 1 | package movies.spring.data.neo4j.domain.model.persistent.entities 2 | 3 | import org.neo4j.ogm.annotation.* 4 | import java.util.* 5 | import kotlin.collections.HashSet 6 | 7 | @NodeEntity 8 | open class User(@Id var uuid: String = UUID.randomUUID().toString(), 9 | @Index(unique = true) var applicationToken: String? = UUID.randomUUID().toString(), 10 | var firstName: String, 11 | var lastName: String, 12 | @Index(unique = true) var email: String, 13 | var password: String?, 14 | var joined: Date = Date(), 15 | var lastActive: Date = Date(), 16 | @Relationship(type = "LIKES", direction = Relationship.OUTGOING) 17 | var likes: MutableSet = HashSet()) { 18 | 19 | fun addLikeInteraction(movie: Movie) { 20 | //TODO: Bug - OGM should set the property as an empty set 21 | if (likes == null) { 22 | likes = HashSet() 23 | } 24 | likes.add(movie) 25 | } 26 | 27 | 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/movies/spring/data/neo4j/api/service/authorization/AuthorizationServicePasswordImpl.kt: -------------------------------------------------------------------------------- 1 | package movies.spring.data.neo4j.api.service.authorization; 2 | 3 | import movies.spring.data.neo4j.api.service.authorization.dto.AuthorizationDTO 4 | import movies.spring.data.neo4j.api.service.authorization.dto.CredentialsDTO 5 | import movies.spring.data.neo4j.exception.UnauthorizedException 6 | import movies.spring.data.neo4j.repositories.UserRepository 7 | import org.springframework.beans.factory.annotation.Autowired 8 | import org.springframework.stereotype.Component 9 | import org.springframework.transaction.annotation.Transactional 10 | 11 | @Component 12 | class AuthorizationServicePasswordImpl ( 13 | private val userRepository: UserRepository) : AuthorizationService 14 | { 15 | 16 | @Transactional 17 | override fun authorize(request: CredentialsDTO): AuthorizationDTO 18 | { 19 | val user = userRepository.findByEmail(request.email) 20 | if (user == null || user.password != request.password) 21 | { 22 | throw UnauthorizedException("The provided request were not valid.") 23 | } 24 | return AuthorizationDTO.fromEntity(user) 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/movies/spring/data/neo4j/domain/model/persistent/entities/Movie.kt: -------------------------------------------------------------------------------- 1 | package movies.spring.data.neo4j.domain.model.persistent.entities 2 | 3 | import org.neo4j.ogm.annotation.Id 4 | import org.neo4j.ogm.annotation.NodeEntity 5 | import org.neo4j.ogm.annotation.Property 6 | import org.neo4j.ogm.annotation.Relationship 7 | 8 | @NodeEntity 9 | class Movie(@Id var title: String, 10 | @Property(name = "releasedYear") var releasedYear: Long, 11 | @Property(name = "tagline") var tagLine: String? = null, 12 | @Relationship(type = "ACTED_IN", direction = Relationship.INCOMING) 13 | var roles: ArrayList = ArrayList()) { 14 | 15 | fun addRole(role: Role) { 16 | this.roles.add(role) 17 | } 18 | 19 | /** 20 | * Title is our primary index and merge key, therefore equals contract is based on it. 21 | */ 22 | override fun equals(other: Any?): Boolean { 23 | if (this === other) return true 24 | if (other !is Movie) return false 25 | 26 | if (title != other.title) return false 27 | 28 | return true 29 | } 30 | 31 | /** 32 | * Title is our primary index and merge key, therefore hashCode contract is based on it. 33 | */ 34 | override fun hashCode(): Int { 35 | return title.hashCode() 36 | } 37 | 38 | 39 | } 40 | -------------------------------------------------------------------------------- /src/main/java/movies/spring/data/neo4j/api/service/movie/dto/GraphDTO.kt: -------------------------------------------------------------------------------- 1 | package movies.spring.data.neo4j.api.service.movie.dto 2 | 3 | import movies.spring.data.neo4j.domain.model.persistent.entities.Movie 4 | 5 | data class GraphDTO(val nodes: List, 6 | val links: List) { 7 | 8 | companion object { 9 | fun mapFromEntity(movies : Collection) : GraphDTO 10 | { 11 | val nodes = java.util.ArrayList() 12 | val links = java.util.ArrayList() 13 | var i = 0 14 | val result = movies.iterator() 15 | while (result.hasNext()) { 16 | val movie = result.next() 17 | nodes.add(NodeDTO(title = movie.title, label = "movie")) 18 | val target = i 19 | i++ 20 | for (role in movie.roles) { 21 | val actor = NodeDTO(title = role.person.name, label = "actor") 22 | var source = nodes.indexOf(actor) 23 | if (source == -1) { 24 | nodes.add(actor) 25 | source = i++ 26 | } 27 | links.add(LinkDTO(source, target)) 28 | } 29 | } 30 | return GraphDTO(nodes, links) 31 | } 32 | } 33 | 34 | } -------------------------------------------------------------------------------- /src/main/java/movies/spring/data/neo4j/api/endpoints/secured/MovieController.kt: -------------------------------------------------------------------------------- 1 | package movies.spring.data.neo4j.api.endpoints.secured 2 | 3 | import movies.spring.data.neo4j.api.endpoints.aspects.Authenticated 4 | import movies.spring.data.neo4j.api.service.movie.MovieService 5 | import movies.spring.data.neo4j.api.service.movie.dto.MovieDTO 6 | import org.springframework.web.bind.annotation.* 7 | 8 | @RestController 9 | @RequestMapping("/movies") 10 | class MovieController constructor(private val movieService: MovieService) { 11 | 12 | @GetMapping("/graph") 13 | fun graph(@RequestParam(required = false, defaultValue = "100") limit: Int?) = 14 | movieService.graph(limit!!) 15 | 16 | @GetMapping("/by/title") 17 | fun byTitle(@RequestParam(required = true) title: String) = movieService.findByTitle(title) 18 | 19 | @GetMapping("/by/title/containing") 20 | fun byTitleContaining(@RequestParam(required = true) title: String) = 21 | movieService.findByTitleContaining(title) 22 | 23 | @PutMapping("/") 24 | fun save(@RequestBody dto: MovieDTO) = movieService.save(dto) 25 | 26 | @PutMapping("/{title}/like") 27 | fun addLikeInteraction(@PathVariable("title") title: String, @Authenticated userUuid: String) = 28 | movieService.addLikeInteractionTo(title, forUserUserUuid = userUuid) 29 | 30 | 31 | } 32 | 33 | -------------------------------------------------------------------------------- /src/test/java/movies/spring/data/neo4j/api/service/authorization/AuthorizationServiceTest.kt: -------------------------------------------------------------------------------- 1 | package movies.spring.data.neo4j.api.service.authorization 2 | 3 | import movies.spring.data.neo4j.ApplicationConfig 4 | import movies.spring.data.neo4j.api.service.authorization.dto.CredentialsDTO 5 | import movies.spring.data.neo4j.exception.UnauthorizedException 6 | import org.junit.Assert 7 | import org.junit.Test 8 | import org.junit.runner.RunWith 9 | import org.springframework.beans.factory.annotation.Autowired 10 | import org.springframework.boot.test.context.SpringBootTest 11 | import org.springframework.test.context.junit4.SpringRunner 12 | 13 | 14 | @RunWith(SpringRunner::class) 15 | @SpringBootTest(classes = arrayOf(ApplicationConfig::class), 16 | webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) 17 | class AuthorizationServiceTest 18 | { 19 | 20 | @Autowired private lateinit var authService: AuthorizationService 21 | 22 | 23 | @Test 24 | fun shouldAuthorize_with_valid_credentials() 25 | { 26 | val request = CredentialsDTO("jasper@appsquick.ly", "password") 27 | val authorization = authService.authorize(request) 28 | 29 | Assert.assertNotNull(authorization.accessToken) 30 | 31 | 32 | } 33 | 34 | @Test(expected = UnauthorizedException::class) 35 | fun testAuthorize_with_invalid_credentials_raises_exception() 36 | { 37 | val request = CredentialsDTO("jasper@appsquick.ly", "invalid") 38 | val authorization = authService.authorize(request) 39 | Assert.fail("$authorization should have thrown exception") 40 | } 41 | } -------------------------------------------------------------------------------- /src/main/java/movies/spring/data/neo4j/ApplicationConfig.kt: -------------------------------------------------------------------------------- 1 | package movies.spring.data.neo4j 2 | 3 | import com.fasterxml.jackson.annotation.JsonInclude 4 | import com.fasterxml.jackson.databind.PropertyNamingStrategy 5 | import com.fasterxml.jackson.databind.SerializationFeature 6 | import com.fasterxml.jackson.module.kotlin.KotlinModule 7 | import org.springframework.boot.SpringApplication 8 | import org.springframework.boot.autoconfigure.SpringBootApplication 9 | import org.springframework.boot.autoconfigure.domain.EntityScan 10 | import org.springframework.context.annotation.Bean 11 | import org.springframework.context.support.PropertySourcesPlaceholderConfigurer 12 | import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder 13 | 14 | @SpringBootApplication 15 | @EntityScan("movies.spring.data.neo4j.domain.model.persistent") 16 | class ApplicationConfig { 17 | 18 | companion object { 19 | @JvmStatic 20 | fun main(args: Array) { 21 | SpringApplication.run(ApplicationConfig::class.java, *args) 22 | } 23 | } 24 | 25 | 26 | @Bean 27 | fun kotlinPropertyConfigurer(): PropertySourcesPlaceholderConfigurer { 28 | val propertyConfigurer = PropertySourcesPlaceholderConfigurer() 29 | propertyConfigurer.setPlaceholderPrefix("@{") 30 | propertyConfigurer.setPlaceholderSuffix("}") 31 | propertyConfigurer.setIgnoreUnresolvablePlaceholders(true) 32 | return propertyConfigurer 33 | } 34 | 35 | @Bean 36 | fun defaultPropertyConfigurer() = PropertySourcesPlaceholderConfigurer() 37 | 38 | 39 | @Bean 40 | fun mapperConfigurer() = Jackson2ObjectMapperBuilder().apply { 41 | serializationInclusion(JsonInclude.Include.NON_NULL) 42 | failOnUnknownProperties(true) 43 | featuresToDisable(*arrayOf(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS)) 44 | indentOutput(true) 45 | modules(listOf(KotlinModule())) 46 | } 47 | 48 | } 49 | 50 | -------------------------------------------------------------------------------- /src/main/java/movies/spring/data/neo4j/infrastructure/security/TokenAuthenticationProvider.kt: -------------------------------------------------------------------------------- 1 | 2 | 3 | package movies.spring.data.neo4j.infrastructure.security 4 | 5 | import movies.spring.data.neo4j.repositories.UserRepository 6 | import org.springframework.security.authentication.AuthenticationProvider 7 | import org.springframework.security.authentication.BadCredentialsException 8 | import org.springframework.security.core.Authentication 9 | import org.springframework.security.core.AuthenticationException 10 | import org.springframework.security.core.authority.SimpleGrantedAuthority 11 | import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken 12 | 13 | class TokenAuthenticationProvider : AuthenticationProvider 14 | { 15 | val profileRepo: UserRepository 16 | val apiKey: String 17 | 18 | 19 | constructor(profileRepo: UserRepository, apiKey: String) 20 | { 21 | this.profileRepo = profileRepo 22 | this.apiKey = apiKey 23 | } 24 | 25 | 26 | @Throws(AuthenticationException::class) 27 | override fun authenticate(authentication: Authentication): Authentication 28 | { 29 | val credentials: SessionCredentials? = authentication.credentials as SessionCredentials? 30 | if (credentials != null && credentials.apiKey == this.apiKey) 31 | { 32 | val auth = profileRepo.findByApplicationToken(credentials.accessToken) 33 | if (auth != null) 34 | { 35 | val newAuthentication = PreAuthenticatedAuthenticationToken(auth, credentials, emptyList()) 36 | newAuthentication.isAuthenticated = true 37 | return newAuthentication 38 | } 39 | } 40 | throw BadCredentialsException("Bad credentials given.") 41 | } 42 | 43 | override fun supports(authentication: Class<*>): Boolean 44 | { 45 | return authentication == PreAuthenticatedAuthenticationToken::class.java 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/test/java/movies/spring/data/neo4j/api/endpoints/ControllerTest.kt: -------------------------------------------------------------------------------- 1 | package movies.spring.data.neo4j.api.endpoints 2 | 3 | import com.fasterxml.jackson.databind.ObjectMapper 4 | import com.jayway.restassured.RestAssured 5 | import movies.spring.data.neo4j.ApplicationConfig 6 | import movies.spring.data.neo4j.api.service.authorization.dto.AuthorizationDTO 7 | import movies.spring.data.neo4j.api.service.authorization.dto.CredentialsDTO 8 | import org.junit.Assert.assertNotNull 9 | import org.junit.Assert.fail 10 | import org.junit.Before 11 | import org.junit.runner.RunWith 12 | import org.springframework.beans.factory.annotation.Autowired 13 | import org.springframework.boot.test.context.SpringBootTest 14 | import org.springframework.boot.web.server.LocalServerPort 15 | import org.springframework.test.context.junit4.SpringRunner 16 | import org.springframework.transaction.annotation.Transactional 17 | 18 | @Transactional 19 | @RunWith(SpringRunner::class) 20 | @SpringBootTest(classes = arrayOf(ApplicationConfig::class), webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) 21 | abstract class ControllerTest { 22 | 23 | @Autowired lateinit var mapper: ObjectMapper 24 | 25 | @LocalServerPort 26 | var port: Int? = null 27 | 28 | @Before 29 | fun setup() { 30 | RestAssured.baseURI = "http://127.0.0.1" 31 | RestAssured.port = port!! 32 | } 33 | 34 | fun login(userName: String, password: String): AuthorizationDTO { 35 | val testCredentials = mapper.writeValueAsString(CredentialsDTO(userName, password)) 36 | 37 | println(testCredentials) 38 | 39 | val auth = RestAssured.given() 40 | .header("content-type", "application/json") 41 | .body(testCredentials) 42 | .post("/authorization").peek().asString() 43 | 44 | if (auth.contains("\"error\" : {")) { 45 | fail("Login failed.") 46 | } 47 | 48 | val authorization = mapper.readValue(auth, AuthorizationDTO::class.java) 49 | assertNotNull(authorization.accessToken) 50 | return authorization 51 | } 52 | 53 | 54 | } -------------------------------------------------------------------------------- /src/main/java/movies/spring/data/neo4j/SecurityConfig.kt: -------------------------------------------------------------------------------- 1 | package movies.spring.data.neo4j 2 | 3 | import movies.spring.data.neo4j.infrastructure.security.TokenAuthenticationFilter 4 | import movies.spring.data.neo4j.infrastructure.security.TokenAuthenticationProvider 5 | import movies.spring.data.neo4j.repositories.UserRepository 6 | import org.springframework.beans.factory.annotation.Autowired 7 | import org.springframework.beans.factory.annotation.Value 8 | import org.springframework.boot.autoconfigure.security.SecurityProperties 9 | import org.springframework.context.annotation.Bean 10 | import org.springframework.context.annotation.Configuration 11 | import org.springframework.core.annotation.Order 12 | import org.springframework.security.authentication.AuthenticationProvider 13 | import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder 14 | import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity 15 | import org.springframework.security.config.annotation.web.builders.HttpSecurity 16 | import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity 17 | import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter 18 | import org.springframework.security.config.http.SessionCreationPolicy 19 | import org.springframework.security.web.authentication.www.BasicAuthenticationFilter 20 | 21 | 22 | @Configuration 23 | //@Order(SecurityProperties.ACCESS_OVERRIDE_ORDER) 24 | @EnableWebSecurity 25 | @EnableGlobalMethodSecurity(prePostEnabled = true) 26 | class SecurityConfig : WebSecurityConfigurerAdapter() { 27 | 28 | @Autowired @Value("@{api.key}") lateinit var apiKey: String 29 | 30 | @Autowired lateinit var profileRepo: UserRepository 31 | 32 | 33 | @Throws(Exception::class) 34 | override fun configure(http: HttpSecurity) { 35 | http.addFilterBefore(TokenAuthenticationFilter(authenticationManager()), 36 | BasicAuthenticationFilter::class.java) 37 | 38 | http.csrf().disable() 39 | .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS) 40 | .and().authorizeRequests() 41 | .antMatchers(*arrayOf("/authorization/**", "/public/**")).permitAll() 42 | .antMatchers("/**").authenticated() 43 | } 44 | 45 | @Throws(Exception::class) 46 | override fun configure(auth: AuthenticationManagerBuilder?) { 47 | auth!!.authenticationProvider(tokenAuthenticationProvider()) 48 | } 49 | 50 | @Bean 51 | fun tokenAuthenticationProvider() = TokenAuthenticationProvider(profileRepo, apiKey) 52 | 53 | 54 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Kotlin SDN Movies Example Application 2 | 3 | This is an example backend app powered by Spring Data Neo4j, Spring Boot and the JVM language Kotlin. The domain is the [SDN movies dataset](https://neo4j.com/developer/example-project/) along with movie buffs as end users. 4 | 5 | The application includes features as would be typical of a social mobile application or web-app: 6 | 7 | * End users can interact (like, dislike) movies, roles or directors, make friends with other movie buffs, and based on these interactions, be recommended other movies that they're likely to enjoy. 8 | * Token-based authentication provided by Spring Security. 9 | 10 | Spring Data Neo4j was the first Spring Data project, started by the CEOs Rod Johnson and Emil Eifrem. 11 | It enables convenient integration of Neo4j in your Spring-based application. 12 | It provides object-graph mapping functionality and other features common to the Spring Data projects. 13 | 14 | ---------- 15 | ### Note 16 | 17 | This project uses Spring Data Neo4j 4 which is a complete rewrite from earlier versions. It is optimized for working with Neo4j Server and based on Neo4j's query language, Cypher. 18 | 19 | ---------- 20 | 21 | ## Quickstart 22 | 23 | * [Download](http://neo4j.com/download), install and start Neo4j Server]. 24 | * open the web-interface at http://localhost:7474 25 | * configure a username and password if you haven't already. 26 | * run `:play movies` command, and click and run the Cypher statement to insert the dataset 27 | * clone this project from GitHub 28 | . update `src/main/resources/application.properties` with the username and password you set above. 29 | * Open localhost:7474 and create a test user: 30 | 31 | ```CREATE (u:User {applicationToken:"ba2ab43b-01e6-4425-8089-f0ade3db28c3", email:"you@you.com", firstName:"YourFirstName", joined:"2018-10-13T01:28:22.771Z", lastActive:"2018-10-13T01:28:22.771Z", lastName:"YourLastName", password:"password", uuid:"b4342c76-f693-4205-8ffd-22d56b92cbbb" })``` 32 | 33 | * run the project with `gradle bootRun`. 34 | 35 | 36 | 37 | ## Code Walkthrough 38 | 39 | TODO 40 | 41 | ## Endpoints: 42 | 43 | * [AuthorizationIntegrationTests](https://github.com/appsquickly/movies-kotlin-spring-data-neo4j-4/blob/master/src/test/java/movies/spring/data/neo4j/api/endpoints/pub/AuthorizationIntegrationTests.kt) 44 | * [UserIntegrationTests](https://github.com/appsquickly/movies-kotlin-spring-data-neo4j-4/blob/master/src/test/java/movies/spring/data/neo4j/api/endpoints/secured/UserIntegrationTests.kt) 45 | * [MovieIntegrationTests](https://github.com/appsquickly/movies-kotlin-spring-data-neo4j-4/blob/master/src/test/java/movies/spring/data/neo4j/api/endpoints/secured/MovieIntegrationTests.kt) 46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /src/main/java/movies/spring/data/neo4j/api/endpoints/aspects/AuthenticatedProfileIdAspect.kt: -------------------------------------------------------------------------------- 1 | package movies.spring.data.neo4j.api.endpoints.aspects 2 | 3 | import org.apache.commons.logging.LogFactory 4 | import org.aspectj.lang.ProceedingJoinPoint 5 | import org.aspectj.lang.annotation.Around 6 | import org.aspectj.lang.annotation.Aspect 7 | import org.aspectj.lang.reflect.MethodSignature 8 | import org.springframework.core.annotation.Order 9 | import org.springframework.security.core.context.SecurityContextHolder 10 | import org.springframework.stereotype.Component 11 | import movies.spring.data.neo4j.domain.model.persistent.queries.Principal 12 | import movies.spring.data.neo4j.exception.UnauthorizedException 13 | 14 | annotation class Authenticated 15 | 16 | /** 17 | * Injects current user's profileId into controller method args annotated with @Authenticated. 18 | * This allows controllers to be unit tested without a dependency on SecurityContextHolder. 19 | */ 20 | 21 | @Component 22 | @Aspect @Order(Integer.MAX_VALUE) 23 | class AuthenticatedProfileIdAspect { 24 | 25 | val log = LogFactory.getLog(AuthenticatedProfileIdAspect::class.java) 26 | 27 | @Around("execution(* movies.spring.data.neo4j.api.endpoints.secured..*.*(..))") 28 | fun injectCurrentPrincipal(joinPoint: ProceedingJoinPoint): Any? { 29 | val profileId = principalOrThrow(joinPoint).profileId 30 | 31 | val newArgs = joinPoint.args.toMutableList() 32 | val methods = methods(joinPoint) 33 | for (i in methods.indices) { 34 | val method = methods[i] 35 | method 36 | .filter { it.annotationClass.java == Authenticated::class.java } 37 | .forEach { newArgs[i] = profileId } 38 | } 39 | 40 | val invocationResult = joinPoint.proceed(newArgs.toTypedArray()) 41 | return invocationResult 42 | } 43 | 44 | 45 | private fun methods(joinPoint: ProceedingJoinPoint): Array> { 46 | val signature = joinPoint.signature as MethodSignature 47 | val methodName = signature.method.name 48 | val parameterTypes = signature.method.parameterTypes 49 | val annotations = joinPoint.target.javaClass.getMethod(methodName, *parameterTypes).parameterAnnotations 50 | return annotations 51 | } 52 | 53 | private fun principalOrThrow(joinPoint: ProceedingJoinPoint): Principal { 54 | val authentication = SecurityContextHolder.getContext().authentication 55 | val currentUser = when (authentication) { 56 | null -> throw UnauthorizedException("There is no current principal for injection into ${joinPoint.target}") 57 | else -> authentication.principal as Principal 58 | } 59 | return currentUser 60 | } 61 | 62 | } 63 | 64 | 65 | 66 | -------------------------------------------------------------------------------- /src/test/java/movies/spring/data/neo4j/repositories/MovieRepositoryTest.kt: -------------------------------------------------------------------------------- 1 | package movies.spring.data.neo4j.repositories 2 | 3 | import org.junit.Assert.* 4 | 5 | import movies.spring.data.neo4j.domain.model.persistent.entities.Movie 6 | import movies.spring.data.neo4j.domain.model.persistent.entities.Person 7 | import movies.spring.data.neo4j.domain.model.persistent.entities.Role 8 | import org.junit.After 9 | import org.junit.Before 10 | import org.junit.Test 11 | import org.junit.runner.RunWith 12 | import org.neo4j.ogm.session.Session 13 | import org.springframework.beans.factory.annotation.Autowired 14 | import org.springframework.boot.test.context.SpringBootTest 15 | import org.springframework.test.context.junit4.SpringRunner 16 | import org.springframework.transaction.annotation.Transactional 17 | 18 | 19 | @RunWith(SpringRunner::class) 20 | @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) 21 | class MovieRepositoryTest { 22 | 23 | @Autowired lateinit var session: Session 24 | 25 | @Autowired lateinit var movieRepository: MovieRepository 26 | 27 | @Autowired lateinit var personRepository: PersonRepository 28 | 29 | @Before 30 | fun setUp() { 31 | movieRepository.deleteAll() 32 | 33 | val matrix = Movie(title = "The Matrix", releasedYear = 1999) 34 | 35 | movieRepository.save(matrix) 36 | 37 | val keanu = Person(name = "Keanu Reeves", born = 1964) 38 | 39 | personRepository.save(keanu) 40 | 41 | val neo = Role(movie = matrix, person = keanu) 42 | neo.addRoleName("Neo") 43 | 44 | matrix.addRole(neo) 45 | 46 | movieRepository.save(matrix) 47 | } 48 | 49 | 50 | /** 51 | * Test of findByTitle method, of class MovieRepository. 52 | */ 53 | @Test 54 | fun testFindByTitle() { 55 | 56 | val title = "The Matrix" 57 | val result = movieRepository.findByTitle(title) 58 | assertNotNull(result) 59 | assertEquals(1999L, result!!.releasedYear) 60 | } 61 | 62 | /** 63 | * Test of findByTitleContaining method, of class MovieRepository. 64 | */ 65 | @Test 66 | fun testFindByTitleContaining() { 67 | val title = "Matrix" 68 | val result = movieRepository.findByTitleContaining(title) 69 | assertNotNull(result) 70 | assertEquals(1, result.size.toLong()) 71 | } 72 | 73 | /** 74 | * Test of graph method, of class MovieRepository. 75 | */ 76 | @Test 77 | fun testGraph() { 78 | val graph = movieRepository.graph(5) 79 | 80 | assertEquals(1, graph.size.toLong()) 81 | 82 | val movie = graph.iterator().next() 83 | 84 | assertEquals(1, movie.roles.size.toLong()) 85 | 86 | assertEquals("The Matrix", movie.title) 87 | assertEquals("Keanu Reeves", movie.roles.iterator().next().person.name) 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /src/main/java/movies/spring/data/neo4j/api/service/movie/MovieService.kt: -------------------------------------------------------------------------------- 1 | package movies.spring.data.neo4j.api.service.movie 2 | 3 | import movies.spring.data.neo4j.api.service.movie.dto.GraphDTO 4 | import movies.spring.data.neo4j.api.service.movie.dto.MovieDTO 5 | import movies.spring.data.neo4j.domain.model.persistent.entities.Movie 6 | import movies.spring.data.neo4j.domain.model.persistent.entities.Person 7 | import movies.spring.data.neo4j.domain.model.persistent.entities.Role 8 | import movies.spring.data.neo4j.exception.NotFoundException 9 | import movies.spring.data.neo4j.repositories.MovieRepository 10 | import movies.spring.data.neo4j.repositories.UserRepository 11 | import org.springframework.stereotype.Service 12 | import org.springframework.transaction.annotation.Transactional 13 | import java.util.* 14 | 15 | /** 16 | * TODO: Add user recommendations based on weak ties, triadic closures and so forth. 17 | */ 18 | @Service 19 | class MovieService constructor(private val movieRepository: MovieRepository, 20 | private val userRepository: UserRepository) { 21 | 22 | @Transactional(readOnly = true) 23 | fun graph(limit: Int) = GraphDTO.mapFromEntity(movieRepository.graph(limit)) 24 | 25 | @Transactional(readOnly = true) 26 | fun findByTitle(title: String): MovieDTO { 27 | val movie = movieRepository.findByTitle(title) ?: 28 | throw NotFoundException("Movie with title $title does not exist.") 29 | return MovieDTO.fromEntity(movie) 30 | } 31 | 32 | @Transactional(readOnly = true) 33 | fun findByTitleContaining(term: String) = 34 | MovieDTO.mapFromEntities(movieRepository.findByTitleContaining(term)) 35 | 36 | @Transactional 37 | fun addLikeInteractionTo(title: String, forUserUserUuid: String) { 38 | 39 | val movie = movieRepository.findByTitle(title) ?: 40 | throw NotFoundException("Movie with uuid $title does not exist.") 41 | val user = userRepository.findByUuid(forUserUserUuid) ?: 42 | throw NotFoundException("User with uuid $title does not exist.") 43 | 44 | user.addLikeInteraction(movie) 45 | user.lastActive = Date() 46 | userRepository.save(user) 47 | } 48 | 49 | @Transactional 50 | fun save(dto: MovieDTO): MovieDTO { 51 | val movie = Movie(title = dto.title, releasedYear = dto.releasedYear, tagLine = dto.tagLine) 52 | dto.roles.forEach { 53 | val person = Person(name = it.person.name, born = it.person.born) 54 | val role = Role(movie = movie, person = person) 55 | it.roles.forEach { roleName -> 56 | role.addRoleName(roleName) 57 | } 58 | movie.addRole(role) 59 | } 60 | //TODO: Check what happens to UUID on merge by title here 61 | movieRepository.save(movie) 62 | return MovieDTO.fromEntity(movie) 63 | } 64 | 65 | 66 | } 67 | -------------------------------------------------------------------------------- /src/main/java/movies/spring/data/neo4j/infrastructure/security/TokenAuthenticationFilter.kt: -------------------------------------------------------------------------------- 1 | package movies.spring.data.neo4j.infrastructure.security 2 | 3 | import org.springframework.security.authentication.AuthenticationManager 4 | import org.springframework.security.authentication.InternalAuthenticationServiceException 5 | import org.springframework.security.core.Authentication 6 | import org.springframework.security.core.AuthenticationException 7 | import org.springframework.security.core.context.SecurityContextHolder 8 | import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken 9 | import org.springframework.web.filter.GenericFilterBean 10 | import java.io.IOException 11 | import javax.servlet.FilterChain 12 | import javax.servlet.ServletException 13 | import javax.servlet.ServletRequest 14 | import javax.servlet.ServletResponse 15 | import javax.servlet.http.HttpServletRequest 16 | import javax.servlet.http.HttpServletResponse 17 | 18 | class TokenAuthenticationFilter(private val authenticationManager: AuthenticationManager) 19 | : GenericFilterBean() { 20 | 21 | @Throws(IOException::class, ServletException::class) 22 | override fun doFilter(request: ServletRequest, response: ServletResponse, chain: FilterChain) { 23 | val httpRequest = request as HttpServletRequest 24 | val httpResponse = response as HttpServletResponse 25 | 26 | val token: String? = httpRequest.getHeader("Access-Token") 27 | val apiKey: String? = httpRequest.getHeader("API-Key") 28 | 29 | try { 30 | if (!token.isNullOrEmpty() && !apiKey.isNullOrEmpty()) { 31 | processTokenAuthentication(token!!, apiKey!!) 32 | } 33 | chain.doFilter(request, response) 34 | } catch (internalAuthenticationServiceException: InternalAuthenticationServiceException) { 35 | SecurityContextHolder.clearContext() 36 | logger.error("Internal authentication service exception", internalAuthenticationServiceException) 37 | httpResponse.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR) 38 | } catch (authenticationException: AuthenticationException) { 39 | SecurityContextHolder.clearContext() 40 | httpResponse.sendError(HttpServletResponse.SC_UNAUTHORIZED, authenticationException.message) 41 | } 42 | } 43 | 44 | private fun processTokenAuthentication(token: String, apiKey: String) { 45 | val authCredentials = SessionCredentials(apiKey, token) 46 | val requestAuthentication = PreAuthenticatedAuthenticationToken(authCredentials, authCredentials) 47 | val resultOfAuthentication = tryToAuthenticate(requestAuthentication) 48 | SecurityContextHolder.getContext().authentication = resultOfAuthentication 49 | } 50 | 51 | private fun tryToAuthenticate(requestAuthentication: Authentication): Authentication { 52 | val responseAuthentication = authenticationManager.authenticate(requestAuthentication) 53 | if (responseAuthentication == null || !responseAuthentication.isAuthenticated) { 54 | throw InternalAuthenticationServiceException("Unable to authenticate Domain User for provided credentials") 55 | } 56 | return responseAuthentication 57 | } 58 | 59 | } 60 | -------------------------------------------------------------------------------- /src/test/java/movies/spring/data/neo4j/api/endpoints/secured/MovieIntegrationTests.kt: -------------------------------------------------------------------------------- 1 | package movies.spring.data.neo4j.api.endpoints.secured 2 | 3 | import com.jayway.restassured.RestAssured 4 | import movies.spring.data.neo4j.api.endpoints.ControllerTest 5 | import movies.spring.data.neo4j.api.service.movie.dto.GraphDTO 6 | import movies.spring.data.neo4j.api.service.movie.dto.MovieDTO 7 | import movies.spring.data.neo4j.api.service.movie.dto.PersonDTO 8 | import movies.spring.data.neo4j.api.service.movie.dto.RoleDTO 9 | import org.junit.Assert.assertEquals 10 | import org.junit.Assert.assertNotNull 11 | import org.junit.Test 12 | import org.springframework.beans.factory.annotation.Autowired 13 | import org.springframework.beans.factory.annotation.Value 14 | 15 | 16 | class MovieIntegrationTests : ControllerTest() { 17 | 18 | @Autowired @Value("@{api.key}") lateinit var apiKey: String 19 | 20 | @Test 21 | fun shouldSaveMovies() { 22 | val accessToken: String = login("jasper@appsquick.ly", "password").accessToken 23 | 24 | val dto = MovieDTO(title = "The Devil's Advocate", 25 | releasedYear = 1997, 26 | tagLine = "Evil has its winning ways", 27 | roles = listOf( 28 | RoleDTO(person = PersonDTO("Keanu Reeves", 1964), 29 | roles = listOf("Kevin Lomax")), 30 | RoleDTO(person = PersonDTO("Charlize Theron", 1975), 31 | roles = listOf("Mary Ann Lomax")), 32 | RoleDTO(person = PersonDTO("Al Pacino", 1940), 33 | roles = listOf("John Milton")) 34 | )) 35 | 36 | RestAssured.given() 37 | .header("Access-Token", accessToken) 38 | .header("API-Key", apiKey) 39 | .header("Content-Type", "application/json") 40 | .body(mapper.writeValueAsString(dto)) 41 | .put("/movies/") 42 | .peek().then() 43 | .assertThat().statusCode(200) 44 | .extract().response().asString() 45 | } 46 | 47 | @Test 48 | fun shouldReturnGraph() { 49 | val accessToken: String = login("jasper@appsquick.ly", "password").accessToken 50 | 51 | val json = RestAssured.given().header("Access-Token", accessToken) 52 | .header("API-Key", apiKey) 53 | .get("/movies/graph") 54 | .peek().then() 55 | .assertThat().statusCode(200) 56 | .extract().response().asString() 57 | 58 | val dto = mapper.readValue(json, GraphDTO::class.java) 59 | assertNotNull(dto.nodes) 60 | assertNotNull(dto.links) 61 | } 62 | 63 | @Test 64 | fun shouldFindByTitle() { 65 | 66 | shouldSaveMovies() 67 | 68 | val accessToken: String = login("jasper@appsquick.ly", "password").accessToken 69 | 70 | val json = RestAssured.given().header("Access-Token", accessToken) 71 | .header("API-Key", apiKey) 72 | .param("title", "The Devil's Advocate") 73 | .get("/movies/by/title") 74 | .peek().then() 75 | .assertThat().statusCode(200) 76 | .extract().response().asString() 77 | 78 | val dto = mapper.readValue(json, MovieDTO::class.java) 79 | assertNotNull(dto) 80 | assertEquals("The Devil's Advocate", dto.title) 81 | 82 | } 83 | 84 | @Test 85 | fun shouldListByTitleContaining() { 86 | shouldSaveMovies() 87 | 88 | val accessToken: String = login("jasper@appsquick.ly", "password").accessToken 89 | 90 | RestAssured.given().header("Access-Token", accessToken) 91 | .header("API-Key", apiKey) 92 | .param("title", "The Devil's Advocate") 93 | .get("movies/by/title/containing") 94 | .peek().then() 95 | .assertThat().statusCode(200) 96 | } 97 | 98 | @Test 99 | fun shouldAllowUserLiking() { 100 | 101 | val accessToken: String = login("jasper@appsquick.ly", "password").accessToken 102 | 103 | val dto = MovieDTO(title = "The Devil's Advocate", 104 | releasedYear = 1997, 105 | tagLine = "Evil has its winning ways", 106 | roles = listOf( 107 | RoleDTO(person = PersonDTO("Keanu Reeves", 1964), 108 | roles = listOf("Kevin Lomax")), 109 | RoleDTO(person = PersonDTO("Charlize Theron", 1975), 110 | roles = listOf("Mary Ann Lomax")), 111 | RoleDTO(person = PersonDTO("Al Pacino", 1940), 112 | roles = listOf("John Milton")) 113 | )) 114 | 115 | val json = RestAssured.given() 116 | .header("Access-Token", accessToken) 117 | .header("API-Key", apiKey) 118 | .header("Content-Type", "application/json") 119 | .body(mapper.writeValueAsString(dto)) 120 | .put("/movies/") 121 | .peek().then() 122 | .assertThat().statusCode(200) 123 | .extract().response().asString() 124 | 125 | val title = mapper.readValue(json, MovieDTO::class.java).title 126 | 127 | RestAssured.given() 128 | .header("Access-Token", accessToken) 129 | .header("API-Key", apiKey) 130 | .header("Content-Type", "application/json") 131 | .body(mapper.writeValueAsString(dto)) 132 | .put("/movies/$title/like") 133 | .peek().then() 134 | .assertThat().statusCode(200) 135 | .extract().response().asString() 136 | 137 | 138 | } 139 | 140 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /.idea/modules/movies-kotlin-spring-data-neo4j_main.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 17 | 18 | 42 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | -------------------------------------------------------------------------------- /.idea/modules/movies-kotlin-spring-data-neo4j_test.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 18 | 19 | 33 | 49 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | --------------------------------------------------------------------------------