diff --git a/.env b/01-dgs-framework-basic-features/.env similarity index 100% rename from .env rename to 01-dgs-framework-basic-features/.env diff --git a/01-dgs-framework-basic-features/.gitignore b/01-dgs-framework-basic-features/.gitignore new file mode 100644 index 0000000..9c74deb --- /dev/null +++ b/01-dgs-framework-basic-features/.gitignore @@ -0,0 +1,39 @@ +HELP.md +.gradle +build/ +!gradle/wrapper/gradle-wrapper.jar +!**/src/main/**/build/ +!**/src/test/**/build/ + +### STS ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache +bin/ +!**/src/main/**/bin/ +!**/src/test/**/bin/ + +### IntelliJ IDEA ### +.idea +*.iws +*.iml +*.ipr +out/ +!**/src/main/**/out/ +!**/src/test/**/out/ + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ + +### VS Code ### +.vscode/ +uploaded-images +.DS_Store \ No newline at end of file diff --git a/.sdkmanrc b/01-dgs-framework-basic-features/.sdkmanrc similarity index 100% rename from .sdkmanrc rename to 01-dgs-framework-basic-features/.sdkmanrc diff --git a/LICENSE b/01-dgs-framework-basic-features/LICENSE similarity index 100% rename from LICENSE rename to 01-dgs-framework-basic-features/LICENSE diff --git a/Makefile b/01-dgs-framework-basic-features/Makefile similarity index 100% rename from Makefile rename to 01-dgs-framework-basic-features/Makefile diff --git a/NOTICE b/01-dgs-framework-basic-features/NOTICE similarity index 100% rename from NOTICE rename to 01-dgs-framework-basic-features/NOTICE diff --git a/01-dgs-framework-basic-features/README.md b/01-dgs-framework-basic-features/README.md new file mode 100644 index 0000000..e7042c9 --- /dev/null +++ b/01-dgs-framework-basic-features/README.md @@ -0,0 +1,45 @@ +Java DGS Framework example (basic features) +===== + +This repository is an example application for the [DGS Framework](https://netflix.github.io/dgs). +The example is a standalone GraphQL server in Java. + +It shows the following features: +* Datafetchers +* Mutations +* DataLoader to prevent the N+1 problem +* Query testing +* Using a generated Query API +* File Upload +* Using the Gradle codegen plugin +* A custom instrumentation implementation +* Subscriptions +* Testing a subscription +* Registering an optional scalar from graphql-java + + +Shows and Reviews +---- + +This example is built around two main types: Show and Review. +A `Show` represents a series or movie you would find on Netflix. +For ease of running the demo, the list of shows is hardcoded in ShowsServiceImpl. +A show can have `Reviews`. +Again, for ease of running the demo, a list of reviews is generated during startup for each show in DefaultReviewsService. + +Reviews can also be added by users of the API using a mutation, and a GraphQL Subscription is available to watch for added reviews. + +There's also a mutation available to add Artwork for a show, demonstrating file uploads. +Uploaded files are stored in a folder `uploaded-images` in the work directory where the application is started. + +Starting the example +---- + +The example requires Java 11. +Run the application in an IDE using its main class or using Gradle: + +``` +./gradlew bootRun +``` + +Interact with the application using GraphiQL on http://localhost:8080/graphiql. diff --git a/build.gradle.kts b/01-dgs-framework-basic-features/build.gradle.kts similarity index 100% rename from build.gradle.kts rename to 01-dgs-framework-basic-features/build.gradle.kts diff --git a/gradle/wrapper/gradle-wrapper.jar b/01-dgs-framework-basic-features/gradle/wrapper/gradle-wrapper.jar similarity index 100% rename from gradle/wrapper/gradle-wrapper.jar rename to 01-dgs-framework-basic-features/gradle/wrapper/gradle-wrapper.jar diff --git a/gradle/wrapper/gradle-wrapper.properties b/01-dgs-framework-basic-features/gradle/wrapper/gradle-wrapper.properties similarity index 100% rename from gradle/wrapper/gradle-wrapper.properties rename to 01-dgs-framework-basic-features/gradle/wrapper/gradle-wrapper.properties diff --git a/gradlew b/01-dgs-framework-basic-features/gradlew old mode 100755 new mode 100644 similarity index 100% rename from gradlew rename to 01-dgs-framework-basic-features/gradlew diff --git a/gradlew.bat b/01-dgs-framework-basic-features/gradlew.bat similarity index 96% rename from gradlew.bat rename to 01-dgs-framework-basic-features/gradlew.bat index ac1b06f..107acd3 100644 --- a/gradlew.bat +++ b/01-dgs-framework-basic-features/gradlew.bat @@ -1,89 +1,89 @@ -@rem -@rem Copyright 2015 the original author or authors. -@rem -@rem Licensed under the Apache License, Version 2.0 (the "License"); -@rem you may not use this file except in compliance with the License. -@rem You may obtain a copy of the License at -@rem -@rem https://www.apache.org/licenses/LICENSE-2.0 -@rem -@rem Unless required by applicable law or agreed to in writing, software -@rem distributed under the License is distributed on an "AS IS" BASIS, -@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -@rem See the License for the specific language governing permissions and -@rem limitations under the License. -@rem - -@if "%DEBUG%" == "" @echo off -@rem ########################################################################## -@rem -@rem Gradle startup script for Windows -@rem -@rem ########################################################################## - -@rem Set local scope for the variables with windows NT shell -if "%OS%"=="Windows_NT" setlocal - -set DIRNAME=%~dp0 -if "%DIRNAME%" == "" set DIRNAME=. -set APP_BASE_NAME=%~n0 -set APP_HOME=%DIRNAME% - -@rem Resolve any "." and ".." in APP_HOME to make it shorter. -for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi - -@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" - -@rem Find java.exe -if defined JAVA_HOME goto findJavaFromJavaHome - -set JAVA_EXE=java.exe -%JAVA_EXE% -version >NUL 2>&1 -if "%ERRORLEVEL%" == "0" goto execute - -echo. -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. - -goto fail - -:findJavaFromJavaHome -set JAVA_HOME=%JAVA_HOME:"=% -set JAVA_EXE=%JAVA_HOME%/bin/java.exe - -if exist "%JAVA_EXE%" goto execute - -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. - -goto fail - -:execute -@rem Setup the command line - -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar - - -@rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* - -:end -@rem End local scope for the variables with windows NT shell -if "%ERRORLEVEL%"=="0" goto mainEnd - -:fail -rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of -rem the _cmd.exe /c_ return code! -if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 -exit /b 1 - -:mainEnd -if "%OS%"=="Windows_NT" endlocal - -:omega +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto execute + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/settings.gradle.kts b/01-dgs-framework-basic-features/settings.gradle.kts similarity index 100% rename from settings.gradle.kts rename to 01-dgs-framework-basic-features/settings.gradle.kts diff --git a/src/main/java/com/example/demo/DemoApplication.java b/01-dgs-framework-basic-features/src/main/java/com/example/demo/DemoApplication.java similarity index 100% rename from src/main/java/com/example/demo/DemoApplication.java rename to 01-dgs-framework-basic-features/src/main/java/com/example/demo/DemoApplication.java diff --git a/src/main/java/com/example/demo/config/SecurityConfig.java b/01-dgs-framework-basic-features/src/main/java/com/example/demo/config/SecurityConfig.java similarity index 100% rename from src/main/java/com/example/demo/config/SecurityConfig.java rename to 01-dgs-framework-basic-features/src/main/java/com/example/demo/config/SecurityConfig.java diff --git a/src/main/java/com/example/demo/datafetchers/ArtworkUploadDataFetcher.java b/01-dgs-framework-basic-features/src/main/java/com/example/demo/datafetchers/ArtworkUploadDataFetcher.java similarity index 100% rename from src/main/java/com/example/demo/datafetchers/ArtworkUploadDataFetcher.java rename to 01-dgs-framework-basic-features/src/main/java/com/example/demo/datafetchers/ArtworkUploadDataFetcher.java diff --git a/src/main/java/com/example/demo/datafetchers/ReviewsDataFetcher.java b/01-dgs-framework-basic-features/src/main/java/com/example/demo/datafetchers/ReviewsDataFetcher.java similarity index 100% rename from src/main/java/com/example/demo/datafetchers/ReviewsDataFetcher.java rename to 01-dgs-framework-basic-features/src/main/java/com/example/demo/datafetchers/ReviewsDataFetcher.java diff --git a/src/main/java/com/example/demo/datafetchers/SecurityExampleFetchers.java b/01-dgs-framework-basic-features/src/main/java/com/example/demo/datafetchers/SecurityExampleFetchers.java similarity index 100% rename from src/main/java/com/example/demo/datafetchers/SecurityExampleFetchers.java rename to 01-dgs-framework-basic-features/src/main/java/com/example/demo/datafetchers/SecurityExampleFetchers.java diff --git a/src/main/java/com/example/demo/datafetchers/ShowsDatafetcher.java b/01-dgs-framework-basic-features/src/main/java/com/example/demo/datafetchers/ShowsDatafetcher.java similarity index 100% rename from src/main/java/com/example/demo/datafetchers/ShowsDatafetcher.java rename to 01-dgs-framework-basic-features/src/main/java/com/example/demo/datafetchers/ShowsDatafetcher.java diff --git a/src/main/java/com/example/demo/dataloaders/ReviewsDataLoader.java b/01-dgs-framework-basic-features/src/main/java/com/example/demo/dataloaders/ReviewsDataLoader.java similarity index 100% rename from src/main/java/com/example/demo/dataloaders/ReviewsDataLoader.java rename to 01-dgs-framework-basic-features/src/main/java/com/example/demo/dataloaders/ReviewsDataLoader.java diff --git a/src/main/java/com/example/demo/dataloaders/ReviewsDataLoaderWithContext.java b/01-dgs-framework-basic-features/src/main/java/com/example/demo/dataloaders/ReviewsDataLoaderWithContext.java similarity index 100% rename from src/main/java/com/example/demo/dataloaders/ReviewsDataLoaderWithContext.java rename to 01-dgs-framework-basic-features/src/main/java/com/example/demo/dataloaders/ReviewsDataLoaderWithContext.java diff --git a/src/main/java/com/example/demo/directives/UppercaseDirective.java b/01-dgs-framework-basic-features/src/main/java/com/example/demo/directives/UppercaseDirective.java similarity index 100% rename from src/main/java/com/example/demo/directives/UppercaseDirective.java rename to 01-dgs-framework-basic-features/src/main/java/com/example/demo/directives/UppercaseDirective.java diff --git a/src/main/java/com/example/demo/instrumentation/ExampleTracingInstrumentation.java b/01-dgs-framework-basic-features/src/main/java/com/example/demo/instrumentation/ExampleTracingInstrumentation.java similarity index 100% rename from src/main/java/com/example/demo/instrumentation/ExampleTracingInstrumentation.java rename to 01-dgs-framework-basic-features/src/main/java/com/example/demo/instrumentation/ExampleTracingInstrumentation.java diff --git a/src/main/java/com/example/demo/scalars/DateTimeScalar.java b/01-dgs-framework-basic-features/src/main/java/com/example/demo/scalars/DateTimeScalar.java similarity index 100% rename from src/main/java/com/example/demo/scalars/DateTimeScalar.java rename to 01-dgs-framework-basic-features/src/main/java/com/example/demo/scalars/DateTimeScalar.java diff --git a/src/main/java/com/example/demo/services/DefaultReviewsService.java b/01-dgs-framework-basic-features/src/main/java/com/example/demo/services/DefaultReviewsService.java similarity index 100% rename from src/main/java/com/example/demo/services/DefaultReviewsService.java rename to 01-dgs-framework-basic-features/src/main/java/com/example/demo/services/DefaultReviewsService.java diff --git a/src/main/java/com/example/demo/services/ReviewsService.java b/01-dgs-framework-basic-features/src/main/java/com/example/demo/services/ReviewsService.java similarity index 100% rename from src/main/java/com/example/demo/services/ReviewsService.java rename to 01-dgs-framework-basic-features/src/main/java/com/example/demo/services/ReviewsService.java diff --git a/src/main/java/com/example/demo/services/ShowsService.java b/01-dgs-framework-basic-features/src/main/java/com/example/demo/services/ShowsService.java similarity index 100% rename from src/main/java/com/example/demo/services/ShowsService.java rename to 01-dgs-framework-basic-features/src/main/java/com/example/demo/services/ShowsService.java diff --git a/src/main/java/com/example/demo/services/ShowsServiceImpl.java b/01-dgs-framework-basic-features/src/main/java/com/example/demo/services/ShowsServiceImpl.java similarity index 100% rename from src/main/java/com/example/demo/services/ShowsServiceImpl.java rename to 01-dgs-framework-basic-features/src/main/java/com/example/demo/services/ShowsServiceImpl.java diff --git a/src/main/resources/application.yml b/01-dgs-framework-basic-features/src/main/resources/application.yml similarity index 100% rename from src/main/resources/application.yml rename to 01-dgs-framework-basic-features/src/main/resources/application.yml diff --git a/src/main/resources/schema/schema.graphqls b/01-dgs-framework-basic-features/src/main/resources/schema/schema.graphqls similarity index 100% rename from src/main/resources/schema/schema.graphqls rename to 01-dgs-framework-basic-features/src/main/resources/schema/schema.graphqls diff --git a/src/test/java/com/example/demo/ArtworkUploadDataFetcherTest.java b/01-dgs-framework-basic-features/src/test/java/com/example/demo/ArtworkUploadDataFetcherTest.java similarity index 100% rename from src/test/java/com/example/demo/ArtworkUploadDataFetcherTest.java rename to 01-dgs-framework-basic-features/src/test/java/com/example/demo/ArtworkUploadDataFetcherTest.java diff --git a/src/test/java/com/example/demo/ReviewSubscriptionIntegrationTest.java b/01-dgs-framework-basic-features/src/test/java/com/example/demo/ReviewSubscriptionIntegrationTest.java similarity index 100% rename from src/test/java/com/example/demo/ReviewSubscriptionIntegrationTest.java rename to 01-dgs-framework-basic-features/src/test/java/com/example/demo/ReviewSubscriptionIntegrationTest.java diff --git a/src/test/java/com/example/demo/ReviewSubscriptionTest.java b/01-dgs-framework-basic-features/src/test/java/com/example/demo/ReviewSubscriptionTest.java similarity index 100% rename from src/test/java/com/example/demo/ReviewSubscriptionTest.java rename to 01-dgs-framework-basic-features/src/test/java/com/example/demo/ReviewSubscriptionTest.java diff --git a/src/test/java/com/example/demo/SecurityExampleFetchersTest.java b/01-dgs-framework-basic-features/src/test/java/com/example/demo/SecurityExampleFetchersTest.java similarity index 100% rename from src/test/java/com/example/demo/SecurityExampleFetchersTest.java rename to 01-dgs-framework-basic-features/src/test/java/com/example/demo/SecurityExampleFetchersTest.java diff --git a/src/test/java/com/example/demo/ShowsDatafetcherTest.java b/01-dgs-framework-basic-features/src/test/java/com/example/demo/ShowsDatafetcherTest.java similarity index 100% rename from src/test/java/com/example/demo/ShowsDatafetcherTest.java rename to 01-dgs-framework-basic-features/src/test/java/com/example/demo/ShowsDatafetcherTest.java diff --git a/02-dgs-framework-with-spring-data-jpa/.gitignore b/02-dgs-framework-with-spring-data-jpa/.gitignore new file mode 100644 index 0000000..9c74deb --- /dev/null +++ b/02-dgs-framework-with-spring-data-jpa/.gitignore @@ -0,0 +1,39 @@ +HELP.md +.gradle +build/ +!gradle/wrapper/gradle-wrapper.jar +!**/src/main/**/build/ +!**/src/test/**/build/ + +### STS ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache +bin/ +!**/src/main/**/bin/ +!**/src/test/**/bin/ + +### IntelliJ IDEA ### +.idea +*.iws +*.iml +*.ipr +out/ +!**/src/main/**/out/ +!**/src/test/**/out/ + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ + +### VS Code ### +.vscode/ +uploaded-images +.DS_Store \ No newline at end of file diff --git a/02-dgs-framework-with-spring-data-jpa/LICENSE b/02-dgs-framework-with-spring-data-jpa/LICENSE new file mode 100644 index 0000000..7a4a3ea --- /dev/null +++ b/02-dgs-framework-with-spring-data-jpa/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. \ No newline at end of file diff --git a/02-dgs-framework-with-spring-data-jpa/README.md b/02-dgs-framework-with-spring-data-jpa/README.md new file mode 100644 index 0000000..c82ae66 --- /dev/null +++ b/02-dgs-framework-with-spring-data-jpa/README.md @@ -0,0 +1,47 @@ +Java DGS Framework example (with Spring Data JPA) +===== + +This repository is an example application for the [DGS Framework](https://netflix.github.io/dgs). +The example is a standalone GraphQL server in Java that interacts with a PostgreSQL database via Spring Data JPA. + +It shows the following features: +* Datafetchers +* Mutations +* DataLoader to prevent the N+1 problem +* Lombok for model classes +* Spring Data JPA + + +Agencies, Agents, and Properties +---- + +This example is built around three main types: Agencies, Agents, and Properties. + +Each real estate agency has an ID, a name, a tax code, and is associated with N agents and N properties. + +Each real estate agent has a UUID, a full name and is associated with only one agency. + +Each property has an ID, a name, a type (enumeration), coordinates, a purchase date and is associated with only one real estate agency. +Each property can be created, updated or deleted using a mutation. + + +The schema file is located in the following path. +```sh +/src/main/resources/schema/schema.graphql +``` + +Starting the example +---- + +The example requires Java 17 and PostgreSQL 14. + +Create a PostgreSQL database, using the database name, username, and password contained in `application.yml`. + +Run the application in an IDE using its main class or using Gradle: + +``` +./gradlew bootRun +``` + +Interact with the application using GraphiQL on http://localhost:8081/graphiql. + diff --git a/02-dgs-framework-with-spring-data-jpa/build.gradle b/02-dgs-framework-with-spring-data-jpa/build.gradle new file mode 100644 index 0000000..d7e8a5c --- /dev/null +++ b/02-dgs-framework-with-spring-data-jpa/build.gradle @@ -0,0 +1,33 @@ +plugins { + id 'org.springframework.boot' version '2.7.3' + id 'io.spring.dependency-management' version '1.0.13.RELEASE' + id 'java' +} + +group = 'dev.vittorioexp' +version = '0.0.1-SNAPSHOT' +sourceCompatibility = '17' + +repositories { + mavenCentral() +} + +dependencies { + implementation 'org.springframework.boot:spring-boot-starter-data-jpa' + implementation 'org.springframework.boot:spring-boot-starter-graphql' + implementation 'org.springframework.boot:spring-boot-starter-web' + implementation(platform('com.netflix.graphql.dgs:graphql-dgs-platform-dependencies:5.2.4')) + implementation "com.netflix.graphql.dgs:graphql-dgs-spring-boot-starter" + compileOnly 'org.projectlombok:lombok' + developmentOnly 'org.springframework.boot:spring-boot-devtools' + runtimeOnly 'mysql:mysql-connector-java' + runtimeOnly 'org.postgresql:postgresql' + annotationProcessor 'org.projectlombok:lombok' + testImplementation 'org.springframework.boot:spring-boot-starter-test' + testImplementation 'org.springframework:spring-webflux' + testImplementation 'org.springframework.graphql:spring-graphql-test' +} + +tasks.named('test') { + useJUnitPlatform() +} diff --git a/02-dgs-framework-with-spring-data-jpa/gradle/wrapper/gradle-wrapper.jar b/02-dgs-framework-with-spring-data-jpa/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000..249e583 Binary files /dev/null and b/02-dgs-framework-with-spring-data-jpa/gradle/wrapper/gradle-wrapper.jar differ diff --git a/02-dgs-framework-with-spring-data-jpa/gradle/wrapper/gradle-wrapper.properties b/02-dgs-framework-with-spring-data-jpa/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..8049c68 --- /dev/null +++ b/02-dgs-framework-with-spring-data-jpa/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-bin.zip +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/02-dgs-framework-with-spring-data-jpa/gradlew b/02-dgs-framework-with-spring-data-jpa/gradlew new file mode 100644 index 0000000..a69d9cb --- /dev/null +++ b/02-dgs-framework-with-spring-data-jpa/gradlew @@ -0,0 +1,240 @@ +#!/bin/sh + +# +# Copyright © 2015-2021 the original authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +############################################################################## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# +############################################################################## + +# Attempt to set APP_HOME + +# Resolve links: $0 may be a link +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit + +APP_NAME="Gradle" +APP_BASE_NAME=${0##*/} + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD=$JAVA_HOME/jre/sh/java + else + JAVACMD=$JAVA_HOME/bin/java + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD=java + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + +# Collect all arguments for the java command; +# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of +# shell script including quotes and variable substitutions, so put them in +# double quotes to make sure that they get re-expanded; and +# * put everything else in single quotes, so that it's not re-expanded. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/02-dgs-framework-with-spring-data-jpa/gradlew.bat b/02-dgs-framework-with-spring-data-jpa/gradlew.bat new file mode 100644 index 0000000..f127cfd --- /dev/null +++ b/02-dgs-framework-with-spring-data-jpa/gradlew.bat @@ -0,0 +1,91 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%"=="" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%"=="" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if %ERRORLEVEL% equ 0 goto execute + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if %ERRORLEVEL% equ 0 goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/02-dgs-framework-with-spring-data-jpa/settings.gradle b/02-dgs-framework-with-spring-data-jpa/settings.gradle new file mode 100644 index 0000000..e2e6f03 --- /dev/null +++ b/02-dgs-framework-with-spring-data-jpa/settings.gradle @@ -0,0 +1 @@ +rootProject.name = 'dgs' diff --git a/02-dgs-framework-with-spring-data-jpa/src/main/java/dev/vittorioexp/dgs/DgsApplication.java b/02-dgs-framework-with-spring-data-jpa/src/main/java/dev/vittorioexp/dgs/DgsApplication.java new file mode 100644 index 0000000..03b8e0f --- /dev/null +++ b/02-dgs-framework-with-spring-data-jpa/src/main/java/dev/vittorioexp/dgs/DgsApplication.java @@ -0,0 +1,360 @@ +package dev.vittorioexp.dgs; + +import dev.vittorioexp.dgs.model.Agency; +import dev.vittorioexp.dgs.model.Agent; +import dev.vittorioexp.dgs.model.Property; +import dev.vittorioexp.dgs.model.PropertyType; +import dev.vittorioexp.dgs.repository.AgencyRepository; +import dev.vittorioexp.dgs.repository.AgentRepository; +import dev.vittorioexp.dgs.repository.PropertyRepository; +import org.springframework.boot.CommandLineRunner; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.annotation.Bean; + +import java.util.List; + +@SpringBootApplication +public class DgsApplication { + + public static void main(String[] args) { + SpringApplication.run(DgsApplication.class, args); + } + + @Bean + CommandLineRunner commandLineRunner( + AgencyRepository agencyRepository, + AgentRepository agentRepository, + PropertyRepository propertyRepository + ) { + return args -> { + + agencyRepository.saveAll(List.of( + Agency + .builder() + .name("Dream Estates") + .taxCode("3LURH283R7F3F8IFUHJ394") + .build(), + Agency + .builder() + .name("Private properties DALLAS") + .taxCode("LKFHWIEURH8347R3R") + .build(), + Agency + .builder() + .name("Abra buildings") + .taxCode("R8327FH287WFHD298F") + .build(), + Agency + .builder() + .name("Acme") + .taxCode("3RIUE8UH3587G385GU") + .build(), + Agency + .builder() + .name("MS land") + .taxCode("F34UFH387HF8374") + .build() + )); + + + agentRepository.saveAll(List.of( + Agent + .builder() + .fullName("Ada Smith") + .agency(agencyRepository.findById(1).orElse(null)) + .build(), + Agent + .builder() + .fullName("Bob Muller") + .agency(agencyRepository.findById(1).orElse(null)) + .build(), + Agent + .builder() + .fullName("Adam James") + .agency(agencyRepository.findById(1).orElse(null)) + .build(), + Agent + .builder() + .fullName("William james") + .agency(agencyRepository.findById(1).orElse(null)) + .build(), + Agent + .builder() + .fullName("Gregorio Roberts") + .agency(agencyRepository.findById(2).orElse(null)) + .build(), + Agent + .builder() + .fullName("AGENT20389") + .agency(agencyRepository.findById(3).orElse(null)) + .build(), + Agent + .builder() + .fullName("AGENT93485") + .agency(agencyRepository.findById(3).orElse(null)) + .build(), + Agent + .builder() + .fullName("AGENT102938") + .agency(agencyRepository.findById(4).orElse(null)) + .build(), + Agent + .builder() + .fullName("AGENT43687") + .agency(agencyRepository.findById(5).orElse(null)) + .build(), + Agent + .builder() + .fullName("AGENT00113") + .agency(agencyRepository.findById(5).orElse(null)) + .build() + + )); + + propertyRepository.saveAll(List.of( + Property + .builder() + .name("ERTY45Y") + .type(PropertyType.AGRICULTURAL_LAND) + .agency(agencyRepository.findById(1).orElse(null)) + .build(), + Property + .builder() + .name("Y454E5Y4") + .type(PropertyType.AGRICULTURAL_LAND) + .agency(agencyRepository.findById(1).orElse(null)) + .build(), + Property + .builder() + .name("Y454Y") + .type(PropertyType.AGRICULTURAL_LAND) + .agency(agencyRepository.findById(1).orElse(null)) + .build(), + Property + .builder() + .name("RY5R6Y5") + .type(PropertyType.AGRICULTURAL_LAND) + .agency(agencyRepository.findById(1).orElse(null)) + .build(), + Property + .builder() + .name("RTYR5Y54") + .type(PropertyType.BUILDING_LAND) + .agency(agencyRepository.findById(1).orElse(null)) + .build(), + Property + .builder() + .name("5IU6I") + .type(PropertyType.BUILDING_LAND) + .agency(agencyRepository.findById(1).orElse(null)) + .build(), + Property + .builder() + .name("6IT7T6") + .type(PropertyType.WOODED_LAND) + .agency(agencyRepository.findById(1).orElse(null)) + .build(), + Property + .builder() + .name("R3R2R2") + .type(PropertyType.WOODED_LAND) + .agency(agencyRepository.findById(1).orElse(null)) + .build(), + Property + .builder() + .name("R23Q23") + .type(PropertyType.AGRICULTURAL_LAND) + .agency(agencyRepository.findById(2).orElse(null)) + .build(), + Property + .builder() + .name("1EE21E2") + .type(PropertyType.AGRICULTURAL_LAND) + .agency(agencyRepository.findById(2).orElse(null)) + .build(), + Property + .builder() + .name("E121E21") + .type(PropertyType.AGRICULTURAL_LAND) + .agency(agencyRepository.findById(3).orElse(null)) + .build(), + Property + .builder() + .name("12E12E12") + .type(PropertyType.AGRICULTURAL_LAND) + .agency(agencyRepository.findById(3).orElse(null)) + .build(), + Property + .builder() + .name("R2323RQ3W") + .type(PropertyType.AGRICULTURAL_LAND) + .agency(agencyRepository.findById(3).orElse(null)) + .build(), + Property + .builder() + .name("T3WF4TGF3E") + .type(PropertyType.AGRICULTURAL_LAND) + .agency(agencyRepository.findById(3).orElse(null)) + .build(), + Property + .builder() + .name("T3W4EGTWG4REW") + .type(PropertyType.BUILDING_LAND) + .agency(agencyRepository.findById(4).orElse(null)) + .build(), + Property + .builder() + .name("ET43T4E") + .type(PropertyType.BUILDING_LAND) + .agency(agencyRepository.findById(4).orElse(null)) + .build(), + Property + .builder() + .name("5YYY5") + .type(PropertyType.WOODED_LAND) + .agency(agencyRepository.findById(4).orElse(null)) + .build(), + Property + .builder() + .name("U6UT5R6U") + .type(PropertyType.WOODED_LAND) + .agency(agencyRepository.findById(4).orElse(null)) + .build(), + Property + .builder() + .name("R5T6U5R6") + .type(PropertyType.AGRICULTURAL_LAND) + .agency(agencyRepository.findById(5).orElse(null)) + .build(), + Property + .builder() + .name("5T6U6T5U") + .type(PropertyType.AGRICULTURAL_LAND) + .agency(agencyRepository.findById(5).orElse(null)) + .build(), + Property + .builder() + .name("A32001") + .type(PropertyType.AGRICULTURAL_LAND) + .agency(agencyRepository.findById(1).orElse(null)) + .build(), + Property + .builder() + .name("A32002") + .type(PropertyType.AGRICULTURAL_LAND) + .agency(agencyRepository.findById(1).orElse(null)) + .build(), + Property + .builder() + .name("A32003") + .type(PropertyType.AGRICULTURAL_LAND) + .agency(agencyRepository.findById(1).orElse(null)) + .build(), + Property + .builder() + .name("A32004") + .type(PropertyType.AGRICULTURAL_LAND) + .agency(agencyRepository.findById(1).orElse(null)) + .build(), + Property + .builder() + .name("B5001") + .type(PropertyType.BUILDING_LAND) + .agency(agencyRepository.findById(1).orElse(null)) + .build(), + Property + .builder() + .name("B5002") + .type(PropertyType.BUILDING_LAND) + .agency(agencyRepository.findById(1).orElse(null)) + .build(), + Property + .builder() + .name("W001") + .type(PropertyType.WOODED_LAND) + .agency(agencyRepository.findById(1).orElse(null)) + .build(), + Property + .builder() + .name("W002") + .type(PropertyType.WOODED_LAND) + .agency(agencyRepository.findById(1).orElse(null)) + .build(), + Property + .builder() + .name("Agric-01") + .type(PropertyType.AGRICULTURAL_LAND) + .agency(agencyRepository.findById(2).orElse(null)) + .build(), + Property + .builder() + .name("Agric-02") + .type(PropertyType.AGRICULTURAL_LAND) + .agency(agencyRepository.findById(2).orElse(null)) + .build(), + Property + .builder() + .name("5667u4567") + .type(PropertyType.AGRICULTURAL_LAND) + .agency(agencyRepository.findById(3).orElse(null)) + .build(), + Property + .builder() + .name("35t3e5tg") + .type(PropertyType.AGRICULTURAL_LAND) + .agency(agencyRepository.findById(3).orElse(null)) + .build(), + Property + .builder() + .name("35tgey") + .type(PropertyType.AGRICULTURAL_LAND) + .agency(agencyRepository.findById(3).orElse(null)) + .build(), + Property + .builder() + .name("4hy4yh4") + .type(PropertyType.AGRICULTURAL_LAND) + .agency(agencyRepository.findById(3).orElse(null)) + .build(), + Property + .builder() + .name("24r3w4t") + .type(PropertyType.BUILDING_LAND) + .agency(agencyRepository.findById(4).orElse(null)) + .build(), + Property + .builder() + .name("9876t3t") + .type(PropertyType.BUILDING_LAND) + .agency(agencyRepository.findById(4).orElse(null)) + .build(), + Property + .builder() + .name("wfqw442") + .type(PropertyType.WOODED_LAND) + .agency(agencyRepository.findById(4).orElse(null)) + .build(), + Property + .builder() + .name("aerge5t") + .type(PropertyType.WOODED_LAND) + .agency(agencyRepository.findById(4).orElse(null)) + .build(), + Property + .builder() + .name("34tg3w54yg") + .type(PropertyType.AGRICULTURAL_LAND) + .agency(agencyRepository.findById(5).orElse(null)) + .build(), + Property + .builder() + .name("q3rr2q3r2w") + .type(PropertyType.AGRICULTURAL_LAND) + .agency(agencyRepository.findById(5).orElse(null)) + .build() + + )); + }; + } +} diff --git a/02-dgs-framework-with-spring-data-jpa/src/main/java/dev/vittorioexp/dgs/datafetcher/AgencyDataFetcher.java b/02-dgs-framework-with-spring-data-jpa/src/main/java/dev/vittorioexp/dgs/datafetcher/AgencyDataFetcher.java new file mode 100644 index 0000000..d5e4587 --- /dev/null +++ b/02-dgs-framework-with-spring-data-jpa/src/main/java/dev/vittorioexp/dgs/datafetcher/AgencyDataFetcher.java @@ -0,0 +1,23 @@ +package dev.vittorioexp.dgs.datafetcher; + +import com.netflix.graphql.dgs.DgsComponent; +import com.netflix.graphql.dgs.DgsQuery; +import com.netflix.graphql.dgs.InputArgument; +import dev.vittorioexp.dgs.model.Agency; +import dev.vittorioexp.dgs.service.AgencyService; +import org.springframework.beans.factory.annotation.Autowired; + +import java.util.List; + +@DgsComponent +public class AgencyDataFetcher { + + @Autowired + private AgencyService agencyService; + + @DgsQuery + public List agencies(@InputArgument List ids) { + return agencyService.getAgencies(ids); + } + +} diff --git a/02-dgs-framework-with-spring-data-jpa/src/main/java/dev/vittorioexp/dgs/datafetcher/AgentDataFetcher.java b/02-dgs-framework-with-spring-data-jpa/src/main/java/dev/vittorioexp/dgs/datafetcher/AgentDataFetcher.java new file mode 100644 index 0000000..b091d8c --- /dev/null +++ b/02-dgs-framework-with-spring-data-jpa/src/main/java/dev/vittorioexp/dgs/datafetcher/AgentDataFetcher.java @@ -0,0 +1,32 @@ +package dev.vittorioexp.dgs.datafetcher; + +import com.netflix.graphql.dgs.*; +import dev.vittorioexp.dgs.dataloader.AgentFromAgencyDataLoader; +import dev.vittorioexp.dgs.model.Agency; +import dev.vittorioexp.dgs.model.Agent; +import dev.vittorioexp.dgs.service.AgentService; +import org.dataloader.DataLoader; +import org.springframework.beans.factory.annotation.Autowired; + +import java.util.List; +import java.util.concurrent.CompletableFuture; + +@DgsComponent +public class AgentDataFetcher { + + @Autowired + private AgentService agentService; + + @DgsQuery + public List agents(@InputArgument List ids) { + return agentService.getAgents(ids); + } + + @DgsData(parentType = "Agency", field = "agents") + public CompletableFuture agentsFromAgency(DgsDataFetchingEnvironment dfe) { + Agency agency = dfe.getSource(); + DataLoader dataLoader = dfe.getDataLoader(AgentFromAgencyDataLoader.class); + return dataLoader.load(agency.getId()); + } + +} diff --git a/02-dgs-framework-with-spring-data-jpa/src/main/java/dev/vittorioexp/dgs/datafetcher/PropertyDataFetcher.java b/02-dgs-framework-with-spring-data-jpa/src/main/java/dev/vittorioexp/dgs/datafetcher/PropertyDataFetcher.java new file mode 100644 index 0000000..b08408e --- /dev/null +++ b/02-dgs-framework-with-spring-data-jpa/src/main/java/dev/vittorioexp/dgs/datafetcher/PropertyDataFetcher.java @@ -0,0 +1,32 @@ +package dev.vittorioexp.dgs.datafetcher; + +import com.netflix.graphql.dgs.*; +import dev.vittorioexp.dgs.dataloader.PropertyFromAgencyDataLoader; +import dev.vittorioexp.dgs.model.Agency; +import dev.vittorioexp.dgs.model.Property; +import dev.vittorioexp.dgs.service.PropertyService; +import org.dataloader.DataLoader; +import org.springframework.beans.factory.annotation.Autowired; + +import java.util.List; +import java.util.concurrent.CompletableFuture; + +@DgsComponent +public class PropertyDataFetcher { + + @Autowired + private PropertyService propertyService; + + @DgsQuery + public List properties(@InputArgument List ids) { + return propertyService.getProperties(ids); + } + + @DgsData(parentType = "Agency", field = "properties") + public CompletableFuture propertiesFromAgency(DgsDataFetchingEnvironment dfe) { + Agency agency = dfe.getSource(); + DataLoader dataLoader = dfe.getDataLoader(PropertyFromAgencyDataLoader.class); + return dataLoader.load(agency.getId()); + } + +} diff --git a/02-dgs-framework-with-spring-data-jpa/src/main/java/dev/vittorioexp/dgs/dataloader/AgentFromAgencyDataLoader.java b/02-dgs-framework-with-spring-data-jpa/src/main/java/dev/vittorioexp/dgs/dataloader/AgentFromAgencyDataLoader.java new file mode 100644 index 0000000..f732ae1 --- /dev/null +++ b/02-dgs-framework-with-spring-data-jpa/src/main/java/dev/vittorioexp/dgs/dataloader/AgentFromAgencyDataLoader.java @@ -0,0 +1,28 @@ +package dev.vittorioexp.dgs.dataloader; + +import com.netflix.graphql.dgs.DgsDataLoader; +import dev.vittorioexp.dgs.model.Agent; +import dev.vittorioexp.dgs.service.AgentService; +import org.dataloader.MappedBatchLoader; +import org.springframework.beans.factory.annotation.Autowired; + +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionStage; + + +@DgsDataLoader +public class AgentFromAgencyDataLoader implements MappedBatchLoader> { + + @Autowired + AgentService agentService; + + @Override + public CompletionStage>> load(Set keys) { + return CompletableFuture.supplyAsync( + () -> agentService.getAgentsByAgencyIds(keys) + ); + } +} diff --git a/02-dgs-framework-with-spring-data-jpa/src/main/java/dev/vittorioexp/dgs/dataloader/PropertyFromAgencyDataLoader.java b/02-dgs-framework-with-spring-data-jpa/src/main/java/dev/vittorioexp/dgs/dataloader/PropertyFromAgencyDataLoader.java new file mode 100644 index 0000000..191f5f0 --- /dev/null +++ b/02-dgs-framework-with-spring-data-jpa/src/main/java/dev/vittorioexp/dgs/dataloader/PropertyFromAgencyDataLoader.java @@ -0,0 +1,28 @@ +package dev.vittorioexp.dgs.dataloader; + +import com.netflix.graphql.dgs.DgsDataLoader; +import dev.vittorioexp.dgs.model.Property; +import dev.vittorioexp.dgs.service.PropertyService; +import org.dataloader.MappedBatchLoader; +import org.springframework.beans.factory.annotation.Autowired; + +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionStage; + + +@DgsDataLoader +public class PropertyFromAgencyDataLoader implements MappedBatchLoader> { + + @Autowired + PropertyService propertyService; + + @Override + public CompletionStage>> load(Set keys) { + return CompletableFuture.supplyAsync( + () -> propertyService.getPropertiesByAgencyIds(keys) + ); + } +} diff --git a/02-dgs-framework-with-spring-data-jpa/src/main/java/dev/vittorioexp/dgs/error/GraphQLRuntimeException.java b/02-dgs-framework-with-spring-data-jpa/src/main/java/dev/vittorioexp/dgs/error/GraphQLRuntimeException.java new file mode 100644 index 0000000..62433db --- /dev/null +++ b/02-dgs-framework-with-spring-data-jpa/src/main/java/dev/vittorioexp/dgs/error/GraphQLRuntimeException.java @@ -0,0 +1,19 @@ +package dev.vittorioexp.dgs.error; + + +import com.netflix.graphql.types.errors.ErrorType; + +public class GraphQLRuntimeException extends RuntimeException { + + private final ErrorType errorType; + + public GraphQLRuntimeException(String errorMessage, ErrorType errorType) { + super(errorMessage); + this.errorType = errorType; + } + + public ErrorType getErrorType() { + return errorType; + } + +} \ No newline at end of file diff --git a/02-dgs-framework-with-spring-data-jpa/src/main/java/dev/vittorioexp/dgs/exceptionhandler/CustomDataFetchingExceptionHandler.java b/02-dgs-framework-with-spring-data-jpa/src/main/java/dev/vittorioexp/dgs/exceptionhandler/CustomDataFetchingExceptionHandler.java new file mode 100644 index 0000000..3f528fc --- /dev/null +++ b/02-dgs-framework-with-spring-data-jpa/src/main/java/dev/vittorioexp/dgs/exceptionhandler/CustomDataFetchingExceptionHandler.java @@ -0,0 +1,40 @@ +package dev.vittorioexp.dgs.exceptionhandler; + +import com.netflix.graphql.types.errors.TypedGraphQLError; +import dev.vittorioexp.dgs.error.GraphQLRuntimeException; +import graphql.GraphQLError; +import graphql.execution.DataFetcherExceptionHandler; +import graphql.execution.DataFetcherExceptionHandlerParameters; +import graphql.execution.DataFetcherExceptionHandlerResult; +import org.springframework.stereotype.Component; + +import java.util.concurrent.CompletableFuture; + +@Component +public class CustomDataFetchingExceptionHandler implements DataFetcherExceptionHandler { + + @Override + public CompletableFuture handleException( + DataFetcherExceptionHandlerParameters handlerParameters + ) { + if (handlerParameters.getException() instanceof GraphQLRuntimeException graphQLRuntimeException) { + + GraphQLError graphqlError = TypedGraphQLError.newInternalErrorBuilder() + .message(graphQLRuntimeException.getMessage()) + .location(handlerParameters.getSourceLocation()) + .path(handlerParameters.getPath()) + .errorType(graphQLRuntimeException.getErrorType()) + .build(); + + DataFetcherExceptionHandlerResult result = DataFetcherExceptionHandlerResult.newResult() + .error(graphqlError) + .build(); + + return CompletableFuture.completedFuture(result); + } else { + return DataFetcherExceptionHandler.super.handleException(handlerParameters); + } + } +} + + diff --git a/02-dgs-framework-with-spring-data-jpa/src/main/java/dev/vittorioexp/dgs/model/Agency.java b/02-dgs-framework-with-spring-data-jpa/src/main/java/dev/vittorioexp/dgs/model/Agency.java new file mode 100644 index 0000000..df84b0f --- /dev/null +++ b/02-dgs-framework-with-spring-data-jpa/src/main/java/dev/vittorioexp/dgs/model/Agency.java @@ -0,0 +1,49 @@ +package dev.vittorioexp.dgs.model; + +import lombok.*; + +import javax.persistence.*; +import java.util.List; + +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +@Entity +@Table(name = "agency") +public class Agency { + @Id + @SequenceGenerator( + name = "agency_sequence", + sequenceName = "agency_sequence", + allocationSize = 1 + ) + @GeneratedValue( + strategy = GenerationType.SEQUENCE, + generator = "agency_sequence" + ) + private Integer id; + + @Column(nullable = false) + private String name; + + @Column(nullable = false) + private String taxCode; + + @OneToMany( + mappedBy = "agency", + cascade = CascadeType.ALL + ) + @EqualsAndHashCode.Exclude + @ToString.Exclude + private List agents; + + @OneToMany( + mappedBy = "agency", + cascade = CascadeType.ALL + ) + @EqualsAndHashCode.Exclude + @ToString.Exclude + private List properties; + +} diff --git a/02-dgs-framework-with-spring-data-jpa/src/main/java/dev/vittorioexp/dgs/model/Agent.java b/02-dgs-framework-with-spring-data-jpa/src/main/java/dev/vittorioexp/dgs/model/Agent.java new file mode 100644 index 0000000..29a3285 --- /dev/null +++ b/02-dgs-framework-with-spring-data-jpa/src/main/java/dev/vittorioexp/dgs/model/Agent.java @@ -0,0 +1,32 @@ +package dev.vittorioexp.dgs.model; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; +import org.hibernate.annotations.GenericGenerator; + +import javax.persistence.*; + +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +@Entity +@Table(name = "agent") +public class Agent { + + @Id + @GeneratedValue(generator = "system-uuid") + @GenericGenerator(name = "system-uuid", strategy = "uuid") + @Column(name = "id", unique = true) + private String id; + + @Column(nullable = false) + private String fullName; + + @ManyToOne + @JoinColumn(name = "agency_id") + private Agency agency; + +} diff --git a/02-dgs-framework-with-spring-data-jpa/src/main/java/dev/vittorioexp/dgs/model/Property.java b/02-dgs-framework-with-spring-data-jpa/src/main/java/dev/vittorioexp/dgs/model/Property.java new file mode 100644 index 0000000..d0bc330 --- /dev/null +++ b/02-dgs-framework-with-spring-data-jpa/src/main/java/dev/vittorioexp/dgs/model/Property.java @@ -0,0 +1,46 @@ +package dev.vittorioexp.dgs.model; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import javax.persistence.*; +import java.time.LocalDate; + +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +@Entity +@Table(name = "property") +public class Property { + @Id + @SequenceGenerator( + name = "property_sequence", + sequenceName = "property_sequence", + allocationSize = 1 + ) + @GeneratedValue( + strategy = GenerationType.SEQUENCE, + generator = "property_sequence" + ) + private Integer id; + + @Column(nullable = false) + private String name; + + @Enumerated(EnumType.STRING) + private PropertyType type; + + private String longitude; + + private String latitude; + + private LocalDate purchaseDate; + + @ManyToOne + @JoinColumn(name = "agency_id") + private Agency agency; + +} diff --git a/02-dgs-framework-with-spring-data-jpa/src/main/java/dev/vittorioexp/dgs/model/PropertyInput.java b/02-dgs-framework-with-spring-data-jpa/src/main/java/dev/vittorioexp/dgs/model/PropertyInput.java new file mode 100644 index 0000000..2d0ca49 --- /dev/null +++ b/02-dgs-framework-with-spring-data-jpa/src/main/java/dev/vittorioexp/dgs/model/PropertyInput.java @@ -0,0 +1,20 @@ +package dev.vittorioexp.dgs.model; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@Builder +@AllArgsConstructor +@NoArgsConstructor +public class PropertyInput { + private Integer id; + private String name; + private PropertyType type; + private String longitude; + private String latitude; + private String purchaseDate; + private Integer agencyId; +} diff --git a/02-dgs-framework-with-spring-data-jpa/src/main/java/dev/vittorioexp/dgs/model/PropertyType.java b/02-dgs-framework-with-spring-data-jpa/src/main/java/dev/vittorioexp/dgs/model/PropertyType.java new file mode 100644 index 0000000..51b754d --- /dev/null +++ b/02-dgs-framework-with-spring-data-jpa/src/main/java/dev/vittorioexp/dgs/model/PropertyType.java @@ -0,0 +1,7 @@ +package dev.vittorioexp.dgs.model; + +public enum PropertyType { + WOODED_LAND, + AGRICULTURAL_LAND, + BUILDING_LAND +} diff --git a/02-dgs-framework-with-spring-data-jpa/src/main/java/dev/vittorioexp/dgs/mutation/PropertyMutation.java b/02-dgs-framework-with-spring-data-jpa/src/main/java/dev/vittorioexp/dgs/mutation/PropertyMutation.java new file mode 100644 index 0000000..6fb6bec --- /dev/null +++ b/02-dgs-framework-with-spring-data-jpa/src/main/java/dev/vittorioexp/dgs/mutation/PropertyMutation.java @@ -0,0 +1,34 @@ +package dev.vittorioexp.dgs.mutation; + +import com.netflix.graphql.dgs.DgsComponent; +import com.netflix.graphql.dgs.DgsMutation; +import com.netflix.graphql.dgs.InputArgument; +import dev.vittorioexp.dgs.model.Property; +import dev.vittorioexp.dgs.model.PropertyInput; +import dev.vittorioexp.dgs.service.PropertyService; +import org.springframework.beans.factory.annotation.Autowired; + +import java.util.List; + +@DgsComponent +public class PropertyMutation { + + @Autowired + private PropertyService propertyService; + + @DgsMutation + public List createProperties(@InputArgument List properties) { + return propertyService.addProperties(properties); + } + + @DgsMutation + public List updateProperties(@InputArgument List properties) { + return propertyService.updateProperties(properties); + } + + @DgsMutation + public List deleteProperties(@InputArgument List ids) { + return propertyService.deleteProperties(ids); + } + +} diff --git a/02-dgs-framework-with-spring-data-jpa/src/main/java/dev/vittorioexp/dgs/repository/AgencyRepository.java b/02-dgs-framework-with-spring-data-jpa/src/main/java/dev/vittorioexp/dgs/repository/AgencyRepository.java new file mode 100644 index 0000000..e98fa33 --- /dev/null +++ b/02-dgs-framework-with-spring-data-jpa/src/main/java/dev/vittorioexp/dgs/repository/AgencyRepository.java @@ -0,0 +1,17 @@ +package dev.vittorioexp.dgs.repository; + +import dev.vittorioexp.dgs.model.Agency; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.stereotype.Repository; + +import java.util.List; + +@Repository +public interface AgencyRepository extends JpaRepository { + + @Query("SELECT a FROM Agency a " + + "WHERE ((:ids) IS NULL OR a.id IN (:ids)) " + + "ORDER BY a.id ASC") + List findAllByIds(List ids); +} diff --git a/02-dgs-framework-with-spring-data-jpa/src/main/java/dev/vittorioexp/dgs/repository/AgentRepository.java b/02-dgs-framework-with-spring-data-jpa/src/main/java/dev/vittorioexp/dgs/repository/AgentRepository.java new file mode 100644 index 0000000..4b25927 --- /dev/null +++ b/02-dgs-framework-with-spring-data-jpa/src/main/java/dev/vittorioexp/dgs/repository/AgentRepository.java @@ -0,0 +1,30 @@ +package dev.vittorioexp.dgs.repository; + +import dev.vittorioexp.dgs.model.Agent; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.stereotype.Repository; + +import java.util.List; +import java.util.Set; + +@Repository +public interface AgentRepository extends JpaRepository { + + @Query("SELECT a FROM Agent a " + + "WHERE ((:ids) IS NULL OR a.id IN (:ids)) " + + "ORDER BY a.id ASC") + List findAllByIds(List ids); + + + @Query("SELECT agency.id as agencyId, a as agent " + + "FROM Agent a JOIN a.agency agency " + + "WHERE agency.id IN (:ids)") + List findAllByAgencyIds(Set ids); + + interface AgentWithAgencyId { + Agent getAgent(); + + Integer getAgencyId(); + } +} diff --git a/02-dgs-framework-with-spring-data-jpa/src/main/java/dev/vittorioexp/dgs/repository/PropertyRepository.java b/02-dgs-framework-with-spring-data-jpa/src/main/java/dev/vittorioexp/dgs/repository/PropertyRepository.java new file mode 100644 index 0000000..e964cbc --- /dev/null +++ b/02-dgs-framework-with-spring-data-jpa/src/main/java/dev/vittorioexp/dgs/repository/PropertyRepository.java @@ -0,0 +1,30 @@ +package dev.vittorioexp.dgs.repository; + +import dev.vittorioexp.dgs.model.Property; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.stereotype.Repository; + +import java.util.List; +import java.util.Set; + +@Repository +public interface PropertyRepository extends JpaRepository { + + @Query("SELECT p FROM Property p " + + "WHERE ((:ids) IS NULL OR p.id IN (:ids)) " + + "ORDER BY p.id ASC") + List findAllByIds(List ids); + + + @Query("SELECT agency.id as agencyId, p as property " + + "FROM Property p JOIN p.agency agency " + + "WHERE agency.id IN (:ids)") + List findAllByAgencyIds(Set ids); + + interface PropertyWithAgencyId { + Property getProperty(); + + Integer getAgencyId(); + } +} diff --git a/02-dgs-framework-with-spring-data-jpa/src/main/java/dev/vittorioexp/dgs/service/AgencyService.java b/02-dgs-framework-with-spring-data-jpa/src/main/java/dev/vittorioexp/dgs/service/AgencyService.java new file mode 100644 index 0000000..1d45dea --- /dev/null +++ b/02-dgs-framework-with-spring-data-jpa/src/main/java/dev/vittorioexp/dgs/service/AgencyService.java @@ -0,0 +1,20 @@ +package dev.vittorioexp.dgs.service; + +import dev.vittorioexp.dgs.model.Agency; +import dev.vittorioexp.dgs.repository.AgencyRepository; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.util.List; + +@Service +public class AgencyService { + + @Autowired + private AgencyRepository agencyRepository; + + public List getAgencies(List ids) { + return agencyRepository.findAllByIds(ids); + } + +} diff --git a/02-dgs-framework-with-spring-data-jpa/src/main/java/dev/vittorioexp/dgs/service/AgentService.java b/02-dgs-framework-with-spring-data-jpa/src/main/java/dev/vittorioexp/dgs/service/AgentService.java new file mode 100644 index 0000000..f75e4b8 --- /dev/null +++ b/02-dgs-framework-with-spring-data-jpa/src/main/java/dev/vittorioexp/dgs/service/AgentService.java @@ -0,0 +1,38 @@ +package dev.vittorioexp.dgs.service; + +import dev.vittorioexp.dgs.model.Agent; +import dev.vittorioexp.dgs.repository.AgentRepository; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; + +@Service +public class AgentService { + + @Autowired + private AgentRepository agentRepository; + + public List getAgents(List ids) { + return agentRepository.findAllByIds(ids); + } + + public Map> getAgentsByAgencyIds(Set ids) { + return agentRepository + .findAllByAgencyIds(ids) + .stream() + .collect( + Collectors.groupingBy( + AgentRepository.AgentWithAgencyId::getAgencyId, + Collectors.mapping( + AgentRepository.AgentWithAgencyId::getAgent, + Collectors.toList() + ) + ) + ); + } + +} diff --git a/02-dgs-framework-with-spring-data-jpa/src/main/java/dev/vittorioexp/dgs/service/PropertyService.java b/02-dgs-framework-with-spring-data-jpa/src/main/java/dev/vittorioexp/dgs/service/PropertyService.java new file mode 100644 index 0000000..6684a87 --- /dev/null +++ b/02-dgs-framework-with-spring-data-jpa/src/main/java/dev/vittorioexp/dgs/service/PropertyService.java @@ -0,0 +1,142 @@ +package dev.vittorioexp.dgs.service; + +import com.netflix.graphql.types.errors.ErrorType; +import dev.vittorioexp.dgs.error.GraphQLRuntimeException; +import dev.vittorioexp.dgs.model.Property; +import dev.vittorioexp.dgs.model.PropertyInput; +import dev.vittorioexp.dgs.repository.AgencyRepository; +import dev.vittorioexp.dgs.repository.PropertyRepository; +import dev.vittorioexp.dgs.utils.DataValidator; +import dev.vittorioexp.dgs.utils.EntityExistenceChecker; +import dev.vittorioexp.dgs.utils.EntityMapper; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; + +@Service +public class PropertyService { + + @Autowired + private PropertyRepository propertyRepository; + + @Autowired + private AgencyRepository agencyRepository; + + @Autowired + private EntityExistenceChecker entityExistenceChecker; + + private void checkPropertyInputBeforeCreate(PropertyInput property) { + + // check mandatory fields + if (property.getName() == null) + throw new GraphQLRuntimeException("Property must have non null 'name' field", ErrorType.BAD_REQUEST); + + if (property.getAgencyId() == null) + throw new GraphQLRuntimeException("Property must have non null 'agencyId' field", ErrorType.BAD_REQUEST); + + // if nullable fields are presents, check if they are valid + DataValidator.validateNullableFields(property); + + // Throw an exception if the Agency does not exist + entityExistenceChecker.assertAgencyExistsById(property.getAgencyId()); + } + + private void checkPropertyInputBeforeUpdate(PropertyInput property) { + + if (property.getId() == null) + throw new GraphQLRuntimeException("Property must have non null 'id' field", ErrorType.BAD_REQUEST); + + // if nullable fields are presents, check if they are valid + DataValidator.validateNullableFields(property); + + // Throw an exception if the Property does not exist + entityExistenceChecker.assertPropertyExistsById(property.getId()); + + // Throw an exception if the Agency does not exist + if (property.getAgencyId() != null) + entityExistenceChecker.assertAgencyExistsById(property.getAgencyId()); + + } + + private void checkPropertyBeforeDelete(Integer id) { + if (id == null) + throw new GraphQLRuntimeException("'id' field cannot be null", ErrorType.BAD_REQUEST); + + // Throw an exception if the Property does not exist + entityExistenceChecker.assertPropertyExistsById(id); + } + + private void checkPropertyBeforeSave(Property property) { + + if (property == null) + throw new GraphQLRuntimeException("Property is null and cannot be saved", ErrorType.INTERNAL); + + if (property.getName() == null) + throw new GraphQLRuntimeException("Property 'name' field is null", ErrorType.INTERNAL); + + if (property.getAgency() == null) + throw new GraphQLRuntimeException("Property 'agency' field is null", ErrorType.INTERNAL); + } + + + public List getProperties(List ids) { + return propertyRepository.findAllByIds(ids); + } + + public Map> getPropertiesByAgencyIds(Set ids) { + return propertyRepository + .findAllByAgencyIds(ids) + .stream() + .collect( + Collectors.groupingBy( + PropertyRepository.PropertyWithAgencyId::getAgencyId, + Collectors.mapping( + PropertyRepository.PropertyWithAgencyId::getProperty, + Collectors.toList() + ) + ) + ); + } + + public List addProperties(List properties) { + return properties + .stream() + .peek(this::checkPropertyInputBeforeCreate) + .map(propertyInput -> + EntityMapper.buildProperty( + propertyInput, + Property.builder().build(), + propertyInput.getAgencyId() != null ? agencyRepository.findById(propertyInput.getAgencyId()).orElse(null) : null + )) + .peek(this::checkPropertyBeforeSave) + .map(propertyRepository::save) + .collect(Collectors.toList()); + } + + public List updateProperties(List properties) { + return properties + .stream() + .peek(this::checkPropertyInputBeforeUpdate) + .map(propertyInput -> + EntityMapper.buildProperty( + propertyInput, + propertyRepository.findById(propertyInput.getId()).orElse(null), + propertyInput.getAgencyId() != null ? agencyRepository.findById(propertyInput.getAgencyId()).orElse(null) : null + )) + .peek(this::checkPropertyBeforeSave) + .map(propertyRepository::save) + .collect(Collectors.toList()); + } + + public List deleteProperties(List ids) { + return ids + .stream() + .peek(this::checkPropertyBeforeDelete) + .peek(propertyRepository::deleteById) + .collect(Collectors.toList()); + } +} diff --git a/02-dgs-framework-with-spring-data-jpa/src/main/java/dev/vittorioexp/dgs/utils/DataValidator.java b/02-dgs-framework-with-spring-data-jpa/src/main/java/dev/vittorioexp/dgs/utils/DataValidator.java new file mode 100644 index 0000000..0733f17 --- /dev/null +++ b/02-dgs-framework-with-spring-data-jpa/src/main/java/dev/vittorioexp/dgs/utils/DataValidator.java @@ -0,0 +1,24 @@ +package dev.vittorioexp.dgs.utils; + +import com.netflix.graphql.types.errors.ErrorType; +import dev.vittorioexp.dgs.error.GraphQLRuntimeException; +import dev.vittorioexp.dgs.model.PropertyInput; + +import java.time.LocalDate; + +public class DataValidator { + + public static boolean isValidLocalDate(Object date) { + try { + LocalDate.parse(date.toString()); + } catch (Exception e) { + return false; + } + return true; + } + + public static void validateNullableFields(PropertyInput propertyInput) { + if (propertyInput.getPurchaseDate() != null && !DataValidator.isValidLocalDate(propertyInput.getPurchaseDate())) + throw new GraphQLRuntimeException("Property must have a valid 'purchaseDate' date field (YYYY-MM-DD)", ErrorType.BAD_REQUEST); + } +} diff --git a/02-dgs-framework-with-spring-data-jpa/src/main/java/dev/vittorioexp/dgs/utils/EntityExistenceChecker.java b/02-dgs-framework-with-spring-data-jpa/src/main/java/dev/vittorioexp/dgs/utils/EntityExistenceChecker.java new file mode 100644 index 0000000..f6bc781 --- /dev/null +++ b/02-dgs-framework-with-spring-data-jpa/src/main/java/dev/vittorioexp/dgs/utils/EntityExistenceChecker.java @@ -0,0 +1,32 @@ +package dev.vittorioexp.dgs.utils; + +import com.netflix.graphql.types.errors.ErrorType; +import dev.vittorioexp.dgs.error.GraphQLRuntimeException; +import dev.vittorioexp.dgs.repository.AgencyRepository; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +@Component +public class EntityExistenceChecker { + + @Autowired + private AgencyRepository agencyRepository; + + public void assertAgencyExistsById(Integer agencyId) { + agencyRepository + .findById(agencyId) + .orElseThrow( + () -> new GraphQLRuntimeException( + "Agency with ID " + agencyId + " could not be fetched", ErrorType.NOT_FOUND) + ); + } + + public void assertPropertyExistsById(Integer propertyId) { + agencyRepository + .findById(propertyId) + .orElseThrow( + () -> new GraphQLRuntimeException( + "Property with ID " + propertyId + " could not be fetched", ErrorType.NOT_FOUND) + ); + } +} diff --git a/02-dgs-framework-with-spring-data-jpa/src/main/java/dev/vittorioexp/dgs/utils/EntityMapper.java b/02-dgs-framework-with-spring-data-jpa/src/main/java/dev/vittorioexp/dgs/utils/EntityMapper.java new file mode 100644 index 0000000..251f400 --- /dev/null +++ b/02-dgs-framework-with-spring-data-jpa/src/main/java/dev/vittorioexp/dgs/utils/EntityMapper.java @@ -0,0 +1,36 @@ +package dev.vittorioexp.dgs.utils; + +import dev.vittorioexp.dgs.model.Agency; +import dev.vittorioexp.dgs.model.Property; +import dev.vittorioexp.dgs.model.PropertyInput; + +import java.time.LocalDate; + +public class EntityMapper { + + public static Property buildProperty( + PropertyInput propertyInput, + Property property, + Agency agency + ) { + if (propertyInput.getName() != null) + property.setName(propertyInput.getName()); + + if (propertyInput.getType() != null) + property.setType(propertyInput.getType()); + + if (propertyInput.getLatitude() != null) + property.setLatitude(propertyInput.getLatitude()); + + if (propertyInput.getLongitude() != null) + property.setLongitude(propertyInput.getLongitude()); + + if (propertyInput.getPurchaseDate() != null) + property.setPurchaseDate(LocalDate.parse(propertyInput.getPurchaseDate())); + + if (propertyInput.getAgencyId() != null) + property.setAgency(agency); + + return property; + } +} diff --git a/02-dgs-framework-with-spring-data-jpa/src/main/resources/application.yml b/02-dgs-framework-with-spring-data-jpa/src/main/resources/application.yml new file mode 100644 index 0000000..f139ce9 --- /dev/null +++ b/02-dgs-framework-with-spring-data-jpa/src/main/resources/application.yml @@ -0,0 +1,42 @@ +server: + port: 8081 + + +# ******** POSTGRESQL CONFIGURATION ******** +spring: + datasource: + url: jdbc:postgresql://localhost:5432/dgs + username: dgs + password: postgres + driver-class-name: org.postgresql.Driver + jpa: + hibernate: + ddl-auto: create-drop + show-sql: true + properties: + hibernate: + format_sql: true + enable_lazy_load_no_trans: true + devtools: + restart: + enabled: false + + +# ******** MYSQL CONFIGURATION ******** +#spring: +# datasource: +# url: jdbc:mysql://localhost:3306/dgs +# username: dgs +# password: mysql +# driver-class-name: com.mysql.cj.jdbc.Driver +# jpa: +# hibernate: +# ddl-auto: create-drop +# show-sql: true +# properties: +# hibernate: +# format_sql: true +# enable_lazy_load_no_trans: true +# devtools: +# restart: +# enabled: false \ No newline at end of file diff --git a/02-dgs-framework-with-spring-data-jpa/src/main/resources/schema/schema.graphql b/02-dgs-framework-with-spring-data-jpa/src/main/resources/schema/schema.graphql new file mode 100644 index 0000000..9ca467d --- /dev/null +++ b/02-dgs-framework-with-spring-data-jpa/src/main/resources/schema/schema.graphql @@ -0,0 +1,52 @@ +type Query { + agencies(ids: [Int]): [Agency] + agents(ids: [String]): [Agent] + properties(ids: [Int]): [Property] +} + +type Mutation { + createProperties(properties: [PropertyInput]): [Property] + updateProperties(properties: [PropertyInput]): [Property] + deleteProperties(ids: [Int]) : [Int] +} + +type Agency { + id: Int! + name: String! + taxCode: String! + agents: [Agent]! + properties: [Property]! +} + +type Agent { + id: String! #UUID + fullName: String! + agency: Agency! +} + +type Property { + id: Int! + name: String! + type: PropertyType + latitude: String + longitude: String + purchaseDate: String + agency: Agency! +} + +input PropertyInput { + id: Int + name: String + type: PropertyType + latitude: String + longitude: String + purchaseDate: String + agencyId: Int +} + +enum PropertyType { + WOODED_LAND, + AGRICULTURAL_LAND, + BUILDING_LAND +} + diff --git a/02-dgs-framework-with-spring-data-jpa/src/test/java/dev/vittorioexp/dgs/DgsApplicationTests.java b/02-dgs-framework-with-spring-data-jpa/src/test/java/dev/vittorioexp/dgs/DgsApplicationTests.java new file mode 100644 index 0000000..1af0a10 --- /dev/null +++ b/02-dgs-framework-with-spring-data-jpa/src/test/java/dev/vittorioexp/dgs/DgsApplicationTests.java @@ -0,0 +1,13 @@ +package dev.vittorioexp.dgs; + +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; + +@SpringBootTest +class DgsApplicationTests { + + @Test + void contextLoads() { + } + +} diff --git a/README.md b/README.md index ba924be..972531a 100644 --- a/README.md +++ b/README.md @@ -1,21 +1,23 @@ Java DGS Framework example ===== -This repository is an example application for the [DGS Framework](https://netflix.github.io/dgs). -The example is a standalone GraphQL server in Java. - -It shows the following features: -* [Datafetchers](https://github.com/Netflix/dgs-examples-java/blob/main/src/main/java/com/example/demo/datafetchers/ShowsDatafetcher.java#L26) -* [Mutations](https://github.com/Netflix/dgs-examples-java/blob/main/src/main/java/com/example/demo/datafetchers/ReviewsDataFetcher.java#L50) -* [DataLoader to prevent the N+1 problem](https://github.com/Netflix/dgs-examples-java/blob/main/src/main/java/com/example/demo/datafetchers/ReviewsDataFetcher.java#L40) -* [Query testing](https://github.com/Netflix/dgs-examples-java/blob/main/src/test/java/com/example/demo/ShowsDatafetcherTest.java#L61) -* [Using a generated Query API](https://github.com/Netflix/dgs-examples-java/blob/main/src/test/java/com/example/demo/ShowsDatafetcherTest.java#L89) -* [File Upload](https://github.com/Netflix/dgs-examples-java/blob/main/src/main/java/com/example/demo/datafetchers/ArtworkUploadDataFetcher.java#L22) -* [Using the Gradle codegen plugin](https://github.com/Netflix/dgs-examples-java/blob/main/build.gradle.kts#L45) -* [A custom instrumentation implementation](https://github.com/Netflix/dgs-examples-java/blob/main/src/main/java/com/example/demo/instrumentation/ExampleTracingInstrumentation.java#L20) -* [Subscriptions](https://github.com/Netflix/dgs-examples-java/blob/main/src/main/java/com/example/demo/datafetchers/ReviewsDataFetcher.java#L60) -* [Testing a subscription](https://github.com/Netflix/dgs-examples-java/blob/main/src/test/java/com/example/demo/ReviewSubscriptionTest.java#L46) -* [Registering an optional scalar from graphql-java](https://github.com/Netflix/dgs-examples-java/blob/main/src/main/java/com/example/demo/scalars/DateTimeScalar.java#L16) +This repository contains a set of example applications for the [DGS Framework](https://netflix.github.io/dgs). +The examples are standalone GraphQL servers in Java. + +They show the following features: +* Datafetchers +* Mutations +* DataLoader to prevent the N+1 problem +* Lombok for model classes +* Spring Data JPA +* Query testing +* Using a generated Query API +* File Upload +* Using the Gradle codegen plugin +* A custom instrumentation implementation +* Subscriptions +* Testing a subscription +* Registering an optional scalar from graphql-java Other examples --- @@ -23,30 +25,4 @@ Other examples There are other examples of using the DGS framework as well: * [Kotlin implementation of this example](https://github.com/Netflix/dgs-examples-kotlin) -* [Federation examples (with Apollo Gateway)](https://github.com/Netflix/dgs-federation-example) - -Shows and Reviews ----- - -This example is built around two main types: [Show](https://github.com/Netflix/dgs-examples-java/blob/main/src/main/resources/schema/schema.graphqls#L14) and [Review](https://github.com/Netflix/dgs-examples-java/blob/main/src/main/resources/schema/schema.graphqls#L22). -A `Show` represents a series or movie you would find on Netflix. -For ease of running the demo, the list of shows is hardcoded in [ShowsServiceImpl](https://github.com/Netflix/dgs-examples-java/blob/main/src/main/java/com/example/demo/services/ShowsServiceImpl.java). -A show can have `Reviews`. -Again, for ease of running the demo, a list of reviews is generated during startup for each show in [DefaultReviewsService](https://github.com/Netflix/dgs-examples-java/blob/main/src/main/java/com/example/demo/services/DefaultReviewsService.java). - -Reviews can also be added by users of the API using a [mutation](https://github.com/Netflix/dgs-examples-java/blob/main/src/main/resources/schema/schema.graphqls#L6), and a [GraphQL Subscription](https://github.com/Netflix/dgs-examples-java/blob/main/src/main/resources/schema/schema.graphqls#L11) is available to watch for added reviews. - -There's also a mutation available to add [Artwork](https://github.com/Netflix/dgs-examples-java/blob/main/src/main/resources/schema/schema.graphqls#L7) for a show, demonstrating file uploads. -Uploaded files are stored in a folder `uploaded-images` in the work directory where ethe application is started. - -Starting the example ----- - -The example requires Java 11. -Run the application in an IDE using its [main class](https://github.com/Netflix/dgs-examples-java/blob/main/src/main/java/com/example/demo/DemoApplication.java) or using Gradle: - -``` -./gradlew bootRun -``` - -Interact with the application using GraphiQL on http://localhost:8080/graphiql. +* [Federation examples (with Apollo Gateway)](https://github.com/Netflix/dgs-federation-example) \ No newline at end of file