Task: Add Netflix DGS framework and DGS codegen for GraphQL and still issued with subscriptions that not work on server and add some configurations and add webflux included web starter and removed all graphql kickstart and add frontend client with apollo client and react in typescript for graphql demo
This commit is contained in:
parent
fbb7937d36
commit
626f8b9fc5
@ -1,11 +1,12 @@
|
|||||||
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
|
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
|
||||||
|
|
||||||
plugins {
|
plugins {
|
||||||
id("org.springframework.boot") version "2.6.0-SNAPSHOT"
|
id("org.springframework.boot") version "2.5.3"
|
||||||
id("io.spring.dependency-management") version "1.0.11.RELEASE"
|
id("io.spring.dependency-management") version "1.0.11.RELEASE"
|
||||||
kotlin("jvm") version "1.5.21"
|
kotlin("jvm") version "1.5.21"
|
||||||
kotlin("plugin.spring") version "1.5.21"
|
kotlin("plugin.spring") version "1.5.21"
|
||||||
kotlin("plugin.jpa") version "1.5.21"
|
kotlin("plugin.jpa") version "1.5.21"
|
||||||
|
id("com.netflix.dgs.codegen") version "5.0.5"
|
||||||
}
|
}
|
||||||
|
|
||||||
group = "com.cubetiqs"
|
group = "com.cubetiqs"
|
||||||
@ -16,16 +17,13 @@ repositories {
|
|||||||
maven { url = uri("https://m.ctdn.net") }
|
maven { url = uri("https://m.ctdn.net") }
|
||||||
}
|
}
|
||||||
|
|
||||||
extra["graphqlVersion"] = "11.0.0"
|
extra["dgsVersion"] = "4.5.0"
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation("com.graphql-java:graphql-java-extended-scalars:16.0.0")
|
implementation("com.netflix.graphql.dgs:graphql-dgs-spring-boot-starter:${property("dgsVersion")}")
|
||||||
implementation("com.graphql-java-kickstart:graphql-spring-boot-starter:${property("graphqlVersion")}")
|
|
||||||
implementation("com.graphql-java-kickstart:playground-spring-boot-starter:${property("graphqlVersion")}")
|
|
||||||
implementation("com.graphql-java-kickstart:voyager-spring-boot-starter:${property("graphqlVersion")}")
|
|
||||||
|
|
||||||
implementation("org.springframework.boot:spring-boot-starter-actuator")
|
implementation("org.springframework.boot:spring-boot-starter-actuator")
|
||||||
implementation("org.springframework.boot:spring-boot-starter-data-jpa")
|
implementation("org.springframework.boot:spring-boot-starter-data-jpa")
|
||||||
|
implementation("org.springframework.boot:spring-boot-starter-web")
|
||||||
implementation("org.springframework.boot:spring-boot-starter-webflux")
|
implementation("org.springframework.boot:spring-boot-starter-webflux")
|
||||||
implementation("com.fasterxml.jackson.module:jackson-module-kotlin")
|
implementation("com.fasterxml.jackson.module:jackson-module-kotlin")
|
||||||
implementation("io.projectreactor.kotlin:reactor-kotlin-extensions")
|
implementation("io.projectreactor.kotlin:reactor-kotlin-extensions")
|
||||||
@ -38,7 +36,6 @@ dependencies {
|
|||||||
|
|
||||||
testImplementation("org.springframework.boot:spring-boot-starter-test")
|
testImplementation("org.springframework.boot:spring-boot-starter-test")
|
||||||
testImplementation("io.projectreactor:reactor-test")
|
testImplementation("io.projectreactor:reactor-test")
|
||||||
testImplementation("com.graphql-java-kickstart:graphql-spring-boot-starter-test:${property("graphqlVersion")}")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
tasks.withType<KotlinCompile> {
|
tasks.withType<KotlinCompile> {
|
||||||
@ -51,3 +48,9 @@ tasks.withType<KotlinCompile> {
|
|||||||
tasks.withType<Test> {
|
tasks.withType<Test> {
|
||||||
useJUnitPlatform()
|
useJUnitPlatform()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
tasks.withType<com.netflix.graphql.dgs.codegen.gradle.GenerateJavaTask> {
|
||||||
|
packageName = "com.cubetiqs.graphql.demo.dgmodel"
|
||||||
|
schemaPaths = mutableListOf("${projectDir}/src/main/resources/schema")
|
||||||
|
generateClient = true
|
||||||
|
}
|
23
frontend/client/.gitignore
vendored
Normal file
23
frontend/client/.gitignore
vendored
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||||
|
|
||||||
|
# dependencies
|
||||||
|
/node_modules
|
||||||
|
/.pnp
|
||||||
|
.pnp.js
|
||||||
|
|
||||||
|
# testing
|
||||||
|
/coverage
|
||||||
|
|
||||||
|
# production
|
||||||
|
/build
|
||||||
|
|
||||||
|
# misc
|
||||||
|
.DS_Store
|
||||||
|
.env.local
|
||||||
|
.env.development.local
|
||||||
|
.env.test.local
|
||||||
|
.env.production.local
|
||||||
|
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
3
frontend/client/README.md
Normal file
3
frontend/client/README.md
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
# React Client App
|
||||||
|
- Apollo Client
|
||||||
|
- GraphQL
|
46
frontend/client/package.json
Normal file
46
frontend/client/package.json
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
{
|
||||||
|
"name": "client",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"private": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@apollo/client": "^3.4.5",
|
||||||
|
"@testing-library/jest-dom": "^5.11.4",
|
||||||
|
"@testing-library/react": "^11.1.0",
|
||||||
|
"@testing-library/user-event": "^12.1.10",
|
||||||
|
"@types/jest": "^26.0.15",
|
||||||
|
"@types/node": "^12.0.0",
|
||||||
|
"@types/react": "^17.0.0",
|
||||||
|
"@types/react-dom": "^17.0.0",
|
||||||
|
"graphql": "^15.5.1",
|
||||||
|
"react": "^17.0.2",
|
||||||
|
"react-dom": "^17.0.2",
|
||||||
|
"react-scripts": "4.0.3",
|
||||||
|
"subscriptions-transport-ws": "^0.9.19",
|
||||||
|
"typescript": "^4.1.2",
|
||||||
|
"web-vitals": "^1.0.1"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"start": "react-scripts start",
|
||||||
|
"build": "react-scripts build",
|
||||||
|
"test": "react-scripts test",
|
||||||
|
"eject": "react-scripts eject"
|
||||||
|
},
|
||||||
|
"eslintConfig": {
|
||||||
|
"extends": [
|
||||||
|
"react-app",
|
||||||
|
"react-app/jest"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"browserslist": {
|
||||||
|
"production": [
|
||||||
|
">0.2%",
|
||||||
|
"not dead",
|
||||||
|
"not op_mini all"
|
||||||
|
],
|
||||||
|
"development": [
|
||||||
|
"last 1 chrome version",
|
||||||
|
"last 1 firefox version",
|
||||||
|
"last 1 safari version"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
BIN
frontend/client/public/favicon.ico
Normal file
BIN
frontend/client/public/favicon.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 3.8 KiB |
43
frontend/client/public/index.html
Normal file
43
frontend/client/public/index.html
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
|
<meta name="theme-color" content="#000000" />
|
||||||
|
<meta
|
||||||
|
name="description"
|
||||||
|
content="Web site created using create-react-app"
|
||||||
|
/>
|
||||||
|
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
|
||||||
|
<!--
|
||||||
|
manifest.json provides metadata used when your web app is installed on a
|
||||||
|
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
|
||||||
|
-->
|
||||||
|
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
|
||||||
|
<!--
|
||||||
|
Notice the use of %PUBLIC_URL% in the tags above.
|
||||||
|
It will be replaced with the URL of the `public` folder during the build.
|
||||||
|
Only files inside the `public` folder can be referenced from the HTML.
|
||||||
|
|
||||||
|
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
|
||||||
|
work correctly both with client-side routing and a non-root public URL.
|
||||||
|
Learn how to configure a non-root public URL by running `npm run build`.
|
||||||
|
-->
|
||||||
|
<title>React App</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||||
|
<div id="root"></div>
|
||||||
|
<!--
|
||||||
|
This HTML file is a template.
|
||||||
|
If you open it directly in the browser, you will see an empty page.
|
||||||
|
|
||||||
|
You can add webfonts, meta tags, or analytics to this file.
|
||||||
|
The build step will place the bundled scripts into the <body> tag.
|
||||||
|
|
||||||
|
To begin the development, run `npm start` or `yarn start`.
|
||||||
|
To create a production bundle, use `npm run build` or `yarn build`.
|
||||||
|
-->
|
||||||
|
</body>
|
||||||
|
</html>
|
BIN
frontend/client/public/logo192.png
Normal file
BIN
frontend/client/public/logo192.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 5.2 KiB |
BIN
frontend/client/public/logo512.png
Normal file
BIN
frontend/client/public/logo512.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 9.4 KiB |
25
frontend/client/public/manifest.json
Normal file
25
frontend/client/public/manifest.json
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
{
|
||||||
|
"short_name": "React App",
|
||||||
|
"name": "Create React App Sample",
|
||||||
|
"icons": [
|
||||||
|
{
|
||||||
|
"src": "favicon.ico",
|
||||||
|
"sizes": "64x64 32x32 24x24 16x16",
|
||||||
|
"type": "image/x-icon"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"src": "logo192.png",
|
||||||
|
"type": "image/png",
|
||||||
|
"sizes": "192x192"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"src": "logo512.png",
|
||||||
|
"type": "image/png",
|
||||||
|
"sizes": "512x512"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"start_url": ".",
|
||||||
|
"display": "standalone",
|
||||||
|
"theme_color": "#000000",
|
||||||
|
"background_color": "#ffffff"
|
||||||
|
}
|
3
frontend/client/public/robots.txt
Normal file
3
frontend/client/public/robots.txt
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
# https://www.robotstxt.org/robotstxt.html
|
||||||
|
User-agent: *
|
||||||
|
Disallow:
|
0
frontend/client/src/App.css
Normal file
0
frontend/client/src/App.css
Normal file
65
frontend/client/src/App.tsx
Normal file
65
frontend/client/src/App.tsx
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import './App.css';
|
||||||
|
import {gql, useQuery, useSubscription} from "@apollo/client";
|
||||||
|
|
||||||
|
interface User {
|
||||||
|
id: number
|
||||||
|
code: string
|
||||||
|
name: string
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Account {
|
||||||
|
id: number
|
||||||
|
code: string
|
||||||
|
balance: number
|
||||||
|
user: User
|
||||||
|
}
|
||||||
|
|
||||||
|
interface AccountResult {
|
||||||
|
fetchAccounts: Array<Account>
|
||||||
|
}
|
||||||
|
|
||||||
|
const ACCOUNTS = gql`
|
||||||
|
{
|
||||||
|
fetchAccounts {
|
||||||
|
id
|
||||||
|
code
|
||||||
|
balance
|
||||||
|
user {
|
||||||
|
code
|
||||||
|
name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
|
const HELLO = gql`
|
||||||
|
subscription {
|
||||||
|
hello
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
|
function App() {
|
||||||
|
// const {error, loading, data} = useQuery<AccountResult>(ACCOUNTS)
|
||||||
|
const {error, loading, data} = useSubscription(HELLO)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<h1>Accounts</h1>
|
||||||
|
{
|
||||||
|
loading || !data ? <p>Loading...</p> :
|
||||||
|
// data.fetchAccounts.map(account => (
|
||||||
|
// <>
|
||||||
|
// <div>Account ID: {account.id}</div>
|
||||||
|
// <div>Account Code: {account.code}</div>
|
||||||
|
// <div>Account User: {account.user.name}</div>
|
||||||
|
// </>
|
||||||
|
// )
|
||||||
|
// )
|
||||||
|
<p>data</p>
|
||||||
|
}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default App;
|
0
frontend/client/src/index.css
Normal file
0
frontend/client/src/index.css
Normal file
54
frontend/client/src/index.tsx
Normal file
54
frontend/client/src/index.tsx
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import ReactDOM from 'react-dom';
|
||||||
|
import './index.css';
|
||||||
|
import App from './App';
|
||||||
|
import reportWebVitals from './reportWebVitals';
|
||||||
|
|
||||||
|
import {split, HttpLink, ApolloClient, InMemoryCache} from '@apollo/client';
|
||||||
|
import {ApolloProvider} from '@apollo/client/react';
|
||||||
|
import {getMainDefinition} from '@apollo/client/utilities';
|
||||||
|
import {WebSocketLink} from '@apollo/client/link/ws';
|
||||||
|
|
||||||
|
const APP_HOST = process.env.APP_HOST || 'localhost:8081'
|
||||||
|
|
||||||
|
const httpLink = new HttpLink({
|
||||||
|
uri: `http://${APP_HOST}/graphql`
|
||||||
|
});
|
||||||
|
|
||||||
|
const wsLink = new WebSocketLink({
|
||||||
|
uri: `ws://${APP_HOST}/subscriptions`,
|
||||||
|
options: {
|
||||||
|
reconnect: true
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const splitLink = split(
|
||||||
|
({query}) => {
|
||||||
|
const definition = getMainDefinition(query);
|
||||||
|
return (
|
||||||
|
definition.kind === 'OperationDefinition' &&
|
||||||
|
definition.operation === 'subscription'
|
||||||
|
);
|
||||||
|
},
|
||||||
|
wsLink,
|
||||||
|
httpLink,
|
||||||
|
);
|
||||||
|
|
||||||
|
const client = new ApolloClient({
|
||||||
|
link: splitLink,
|
||||||
|
cache: new InMemoryCache()
|
||||||
|
});
|
||||||
|
|
||||||
|
ReactDOM.render(
|
||||||
|
<React.StrictMode>
|
||||||
|
<ApolloProvider client={client}>
|
||||||
|
<App/>
|
||||||
|
</ApolloProvider>
|
||||||
|
</React.StrictMode>,
|
||||||
|
document.getElementById('root')
|
||||||
|
);
|
||||||
|
|
||||||
|
// If you want to start measuring performance in your app, pass a function
|
||||||
|
// to log results (for example: reportWebVitals(console.log))
|
||||||
|
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
|
||||||
|
reportWebVitals();
|
1
frontend/client/src/react-app-env.d.ts
vendored
Normal file
1
frontend/client/src/react-app-env.d.ts
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
/// <reference types="react-scripts" />
|
15
frontend/client/src/reportWebVitals.ts
Normal file
15
frontend/client/src/reportWebVitals.ts
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
import { ReportHandler } from 'web-vitals';
|
||||||
|
|
||||||
|
const reportWebVitals = (onPerfEntry?: ReportHandler) => {
|
||||||
|
if (onPerfEntry && onPerfEntry instanceof Function) {
|
||||||
|
import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => {
|
||||||
|
getCLS(onPerfEntry);
|
||||||
|
getFID(onPerfEntry);
|
||||||
|
getFCP(onPerfEntry);
|
||||||
|
getLCP(onPerfEntry);
|
||||||
|
getTTFB(onPerfEntry);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export default reportWebVitals;
|
5
frontend/client/src/setupTests.ts
Normal file
5
frontend/client/src/setupTests.ts
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
// jest-dom adds custom jest matchers for asserting on DOM nodes.
|
||||||
|
// allows you to do things like:
|
||||||
|
// expect(element).toHaveTextContent(/react/i)
|
||||||
|
// learn more: https://github.com/testing-library/jest-dom
|
||||||
|
import '@testing-library/jest-dom';
|
26
frontend/client/tsconfig.json
Normal file
26
frontend/client/tsconfig.json
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "es5",
|
||||||
|
"lib": [
|
||||||
|
"dom",
|
||||||
|
"dom.iterable",
|
||||||
|
"esnext"
|
||||||
|
],
|
||||||
|
"allowJs": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"allowSyntheticDefaultImports": true,
|
||||||
|
"strict": true,
|
||||||
|
"forceConsistentCasingInFileNames": true,
|
||||||
|
"noFallthroughCasesInSwitch": true,
|
||||||
|
"module": "esnext",
|
||||||
|
"moduleResolution": "node",
|
||||||
|
"resolveJsonModule": true,
|
||||||
|
"isolatedModules": true,
|
||||||
|
"noEmit": true,
|
||||||
|
"jsx": "react-jsx"
|
||||||
|
},
|
||||||
|
"include": [
|
||||||
|
"src"
|
||||||
|
]
|
||||||
|
}
|
11571
frontend/client/yarn.lock
Normal file
11571
frontend/client/yarn.lock
Normal file
File diff suppressed because it is too large
Load Diff
@ -1,15 +1,6 @@
|
|||||||
package com.cubetiqs.graphql.demo.config
|
package com.cubetiqs.graphql.demo.config
|
||||||
|
|
||||||
import graphql.scalars.ExtendedScalars
|
|
||||||
import graphql.schema.idl.RuntimeWiring
|
|
||||||
import org.springframework.context.annotation.Bean
|
|
||||||
import org.springframework.context.annotation.Configuration
|
import org.springframework.context.annotation.Configuration
|
||||||
|
|
||||||
@Configuration
|
@Configuration
|
||||||
class GraphQLConfig {
|
class GraphQLConfig
|
||||||
@Bean
|
|
||||||
fun extendsScalars(): RuntimeWiring.Builder {
|
|
||||||
return RuntimeWiring.newRuntimeWiring()
|
|
||||||
.scalar(ExtendedScalars.DateTime)
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,18 @@
|
|||||||
|
package com.cubetiqs.graphql.demo.config
|
||||||
|
|
||||||
|
import org.springframework.context.annotation.Configuration
|
||||||
|
import org.springframework.web.servlet.config.annotation.CorsRegistry
|
||||||
|
import org.springframework.web.servlet.config.annotation.EnableWebMvc
|
||||||
|
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
@EnableWebMvc
|
||||||
|
class WebConfig : WebMvcConfigurer {
|
||||||
|
override fun addCorsMappings(corsRegistry: CorsRegistry) {
|
||||||
|
println("Hello World")
|
||||||
|
corsRegistry.addMapping("/**")
|
||||||
|
.allowedOrigins("*")
|
||||||
|
.allowedMethods("*")
|
||||||
|
.maxAge(3600)
|
||||||
|
}
|
||||||
|
}
|
@ -1,12 +1,8 @@
|
|||||||
package com.cubetiqs.graphql.demo.context
|
package com.cubetiqs.graphql.demo.context
|
||||||
|
|
||||||
import org.springframework.core.annotation.AliasFor
|
import com.netflix.graphql.dgs.DgsComponent
|
||||||
import org.springframework.stereotype.Component
|
|
||||||
|
|
||||||
@Retention(AnnotationRetention.RUNTIME)
|
@Retention(AnnotationRetention.RUNTIME)
|
||||||
@Target(AnnotationTarget.CLASS)
|
@Target(AnnotationTarget.CLASS)
|
||||||
@Component
|
@DgsComponent
|
||||||
annotation class GMutation(
|
annotation class GMutation
|
||||||
@get:AliasFor(annotation = Component::class)
|
|
||||||
val value: String = "",
|
|
||||||
)
|
|
@ -1,12 +1,8 @@
|
|||||||
package com.cubetiqs.graphql.demo.context
|
package com.cubetiqs.graphql.demo.context
|
||||||
|
|
||||||
import org.springframework.core.annotation.AliasFor
|
import com.netflix.graphql.dgs.DgsComponent
|
||||||
import org.springframework.stereotype.Component
|
|
||||||
|
|
||||||
@Retention(AnnotationRetention.RUNTIME)
|
@Retention(AnnotationRetention.RUNTIME)
|
||||||
@Target(AnnotationTarget.CLASS)
|
@Target(AnnotationTarget.CLASS)
|
||||||
@Component
|
@DgsComponent
|
||||||
annotation class GQuery(
|
annotation class GQuery
|
||||||
@get:AliasFor(annotation = Component::class)
|
|
||||||
val value: String = "",
|
|
||||||
)
|
|
@ -0,0 +1,8 @@
|
|||||||
|
package com.cubetiqs.graphql.demo.context
|
||||||
|
|
||||||
|
import com.netflix.graphql.dgs.DgsComponent
|
||||||
|
|
||||||
|
@Retention(AnnotationRetention.RUNTIME)
|
||||||
|
@Target(AnnotationTarget.CLASS)
|
||||||
|
@DgsComponent
|
||||||
|
annotation class GSubscription
|
@ -6,20 +6,26 @@ import com.cubetiqs.graphql.demo.domain.account.AccountInput
|
|||||||
import com.cubetiqs.graphql.demo.domain.account.AccountMapper
|
import com.cubetiqs.graphql.demo.domain.account.AccountMapper
|
||||||
import com.cubetiqs.graphql.demo.repository.AccountRepository
|
import com.cubetiqs.graphql.demo.repository.AccountRepository
|
||||||
import com.cubetiqs.graphql.demo.repository.UserRepository
|
import com.cubetiqs.graphql.demo.repository.UserRepository
|
||||||
import graphql.kickstart.tools.GraphQLMutationResolver
|
import com.netflix.graphql.dgs.DgsData
|
||||||
|
import com.netflix.graphql.dgs.DgsMutation
|
||||||
import org.springframework.beans.factory.annotation.Autowired
|
import org.springframework.beans.factory.annotation.Autowired
|
||||||
import org.springframework.transaction.annotation.Propagation
|
import org.springframework.transaction.annotation.Propagation
|
||||||
import org.springframework.transaction.annotation.Transactional
|
import org.springframework.transaction.annotation.Transactional
|
||||||
|
|
||||||
@GMutation
|
@GMutation
|
||||||
class AccountMutationResolver @Autowired constructor(
|
class AccountMutationResolver {
|
||||||
private val accountRepository: AccountRepository,
|
@Autowired
|
||||||
private val userRepository: UserRepository,
|
private lateinit var accountRepository: AccountRepository
|
||||||
) : GraphQLMutationResolver {
|
|
||||||
|
@Autowired
|
||||||
|
private lateinit var userRepository: UserRepository
|
||||||
|
|
||||||
|
@DgsData(parentType = "Mutation", field = "openAccount")
|
||||||
@Transactional(propagation = Propagation.REQUIRES_NEW)
|
@Transactional(propagation = Propagation.REQUIRES_NEW)
|
||||||
fun openAccount(input: AccountInput): Account {
|
fun openAccount(input: AccountInput): Account {
|
||||||
val account = AccountMapper.fromInputToAccount(input)
|
val account = AccountMapper.fromInputToAccount(input)
|
||||||
val user = userRepository.findById(input.userId ?: 0).orElse(null) ?: throw Exception("User not found to open an account!")
|
val user = userRepository.findById(input.userId ?: 0).orElse(null)
|
||||||
|
?: throw Exception("User not found to open an account!")
|
||||||
account.user = user
|
account.user = user
|
||||||
return accountRepository.save(account)
|
return accountRepository.save(account)
|
||||||
}
|
}
|
||||||
|
@ -5,7 +5,7 @@ import com.cubetiqs.graphql.demo.domain.user.User
|
|||||||
import com.cubetiqs.graphql.demo.domain.user.UserInput
|
import com.cubetiqs.graphql.demo.domain.user.UserInput
|
||||||
import com.cubetiqs.graphql.demo.domain.user.UserMapper
|
import com.cubetiqs.graphql.demo.domain.user.UserMapper
|
||||||
import com.cubetiqs.graphql.demo.repository.UserRepository
|
import com.cubetiqs.graphql.demo.repository.UserRepository
|
||||||
import graphql.kickstart.tools.GraphQLMutationResolver
|
import com.netflix.graphql.dgs.DgsMutation
|
||||||
import org.springframework.beans.factory.annotation.Autowired
|
import org.springframework.beans.factory.annotation.Autowired
|
||||||
import org.springframework.transaction.annotation.Propagation
|
import org.springframework.transaction.annotation.Propagation
|
||||||
import org.springframework.transaction.annotation.Transactional
|
import org.springframework.transaction.annotation.Transactional
|
||||||
@ -13,7 +13,8 @@ import org.springframework.transaction.annotation.Transactional
|
|||||||
@GMutation
|
@GMutation
|
||||||
class UserMutationResolver @Autowired constructor(
|
class UserMutationResolver @Autowired constructor(
|
||||||
private val userRepository: UserRepository,
|
private val userRepository: UserRepository,
|
||||||
) : GraphQLMutationResolver {
|
) {
|
||||||
|
@DgsMutation(field = "createUser")
|
||||||
@Transactional(propagation = Propagation.REQUIRES_NEW)
|
@Transactional(propagation = Propagation.REQUIRES_NEW)
|
||||||
fun createUser(input: UserInput): User {
|
fun createUser(input: UserInput): User {
|
||||||
if (userRepository.existsAllByUsername(input.username ?: "")) throw Exception("Username has been already existed!")
|
if (userRepository.existsAllByUsername(input.username ?: "")) throw Exception("Username has been already existed!")
|
||||||
|
@ -3,14 +3,15 @@ package com.cubetiqs.graphql.demo.resolver.query
|
|||||||
import com.cubetiqs.graphql.demo.context.GQuery
|
import com.cubetiqs.graphql.demo.context.GQuery
|
||||||
import com.cubetiqs.graphql.demo.domain.account.Account
|
import com.cubetiqs.graphql.demo.domain.account.Account
|
||||||
import com.cubetiqs.graphql.demo.repository.AccountRepository
|
import com.cubetiqs.graphql.demo.repository.AccountRepository
|
||||||
import graphql.kickstart.tools.GraphQLQueryResolver
|
import com.netflix.graphql.dgs.DgsQuery
|
||||||
import org.springframework.beans.factory.annotation.Autowired
|
import org.springframework.beans.factory.annotation.Autowired
|
||||||
import org.springframework.data.domain.Pageable
|
import org.springframework.data.domain.Pageable
|
||||||
|
|
||||||
@GQuery
|
@GQuery
|
||||||
class AccountQueryResolver @Autowired constructor(
|
class AccountQueryResolver @Autowired constructor(
|
||||||
private val accountRepository: AccountRepository,
|
private val accountRepository: AccountRepository,
|
||||||
) : GraphQLQueryResolver {
|
) {
|
||||||
|
@DgsQuery(field = "fetchAccounts")
|
||||||
fun fetchAccounts(): Collection<Account> {
|
fun fetchAccounts(): Collection<Account> {
|
||||||
val accounts = accountRepository.findAll(Pageable.unpaged())
|
val accounts = accountRepository.findAll(Pageable.unpaged())
|
||||||
return accounts.content
|
return accounts.content
|
||||||
|
@ -1,13 +1,19 @@
|
|||||||
package com.cubetiqs.graphql.demo.resolver.query
|
package com.cubetiqs.graphql.demo.resolver.query
|
||||||
|
|
||||||
import com.cubetiqs.graphql.demo.context.GQuery
|
import com.cubetiqs.graphql.demo.context.GQuery
|
||||||
import graphql.kickstart.tools.GraphQLQueryResolver
|
import com.netflix.graphql.dgs.DgsQuery
|
||||||
import reactor.core.publisher.Mono
|
import reactor.core.publisher.Mono
|
||||||
import java.util.concurrent.CompletableFuture
|
import java.util.concurrent.CompletableFuture
|
||||||
|
|
||||||
@GQuery
|
@GQuery
|
||||||
class HelloQueryResolver : GraphQLQueryResolver {
|
class HelloQueryResolver {
|
||||||
|
@DgsQuery(field = "hello")
|
||||||
fun hello(): CompletableFuture<String> {
|
fun hello(): CompletableFuture<String> {
|
||||||
return Mono.just("Hello Query...!").toFuture()
|
return Mono.just("Hello Query...!").toFuture()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@DgsQuery(field = "helloByName")
|
||||||
|
fun helloByName(name: String): CompletableFuture<String> {
|
||||||
|
return Mono.just("Hello $name...!").toFuture()
|
||||||
|
}
|
||||||
}
|
}
|
@ -3,14 +3,15 @@ package com.cubetiqs.graphql.demo.resolver.query
|
|||||||
import com.cubetiqs.graphql.demo.context.GQuery
|
import com.cubetiqs.graphql.demo.context.GQuery
|
||||||
import com.cubetiqs.graphql.demo.domain.user.User
|
import com.cubetiqs.graphql.demo.domain.user.User
|
||||||
import com.cubetiqs.graphql.demo.repository.UserRepository
|
import com.cubetiqs.graphql.demo.repository.UserRepository
|
||||||
import graphql.kickstart.tools.GraphQLQueryResolver
|
import com.netflix.graphql.dgs.DgsQuery
|
||||||
import org.springframework.beans.factory.annotation.Autowired
|
import org.springframework.beans.factory.annotation.Autowired
|
||||||
import org.springframework.data.domain.Pageable
|
import org.springframework.data.domain.Pageable
|
||||||
|
|
||||||
@GQuery
|
@GQuery
|
||||||
class UserQueryResolver @Autowired constructor(
|
class UserQueryResolver @Autowired constructor(
|
||||||
private val userRepository: UserRepository,
|
private val userRepository: UserRepository,
|
||||||
) : GraphQLQueryResolver {
|
) {
|
||||||
|
@DgsQuery(field = "fetchUsers")
|
||||||
fun fetchUsers(): Collection<User> {
|
fun fetchUsers(): Collection<User> {
|
||||||
val users = userRepository.queryAllByEnabledIsTrue(Pageable.unpaged())
|
val users = userRepository.queryAllByEnabledIsTrue(Pageable.unpaged())
|
||||||
return users.content
|
return users.content
|
||||||
|
@ -1,14 +1,15 @@
|
|||||||
package com.cubetiqs.graphql.demo.resolver.subscription
|
package com.cubetiqs.graphql.demo.resolver.subscription
|
||||||
|
|
||||||
import graphql.kickstart.tools.GraphQLSubscriptionResolver
|
import com.cubetiqs.graphql.demo.context.GSubscription
|
||||||
|
import com.netflix.graphql.dgs.DgsSubscription
|
||||||
import graphql.schema.DataFetchingEnvironment
|
import graphql.schema.DataFetchingEnvironment
|
||||||
import org.reactivestreams.Publisher
|
import org.reactivestreams.Publisher
|
||||||
import org.springframework.stereotype.Service
|
|
||||||
import reactor.core.publisher.Flux
|
import reactor.core.publisher.Flux
|
||||||
import java.time.Duration
|
import java.time.Duration
|
||||||
|
|
||||||
@Service
|
@GSubscription
|
||||||
class HelloSubscriptionResolver : GraphQLSubscriptionResolver {
|
class HelloSubscriptionResolver {
|
||||||
|
@DgsSubscription(field = "hello")
|
||||||
fun hello(env: DataFetchingEnvironment): Publisher<Int> {
|
fun hello(env: DataFetchingEnvironment): Publisher<Int> {
|
||||||
return Flux.range(1, 10).delayElements(Duration.ofSeconds(1))
|
return Flux.range(1, 10).delayElements(Duration.ofSeconds(1))
|
||||||
}
|
}
|
||||||
|
@ -17,7 +17,8 @@ spring:
|
|||||||
properties:
|
properties:
|
||||||
hibernate:
|
hibernate:
|
||||||
enable_lazy_load_no_trans: ${HIBERNATE_LAZY_NO_TRANS:true}
|
enable_lazy_load_no_trans: ${HIBERNATE_LAZY_NO_TRANS:true}
|
||||||
|
main:
|
||||||
|
allow-bean-definition-overriding: true
|
||||||
# Spring Boot Actuator
|
# Spring Boot Actuator
|
||||||
management:
|
management:
|
||||||
endpoints:
|
endpoints:
|
||||||
@ -25,15 +26,8 @@ management:
|
|||||||
exposure:
|
exposure:
|
||||||
include: health,info,metrics
|
include: health,info,metrics
|
||||||
|
|
||||||
# GraphQL Voyager
|
# DGS GraphQL
|
||||||
voyager:
|
dgs:
|
||||||
enabled: true
|
|
||||||
cdn:
|
|
||||||
enabled: false
|
|
||||||
|
|
||||||
# GraphQL Playground
|
|
||||||
graphql:
|
graphql:
|
||||||
playground:
|
graphiql:
|
||||||
enabled: true
|
enabled: true
|
||||||
cdn:
|
|
||||||
enabled: false
|
|
@ -1,59 +0,0 @@
|
|||||||
enum AccountType {
|
|
||||||
BASIC
|
|
||||||
PREMIUM
|
|
||||||
BUSINESS
|
|
||||||
}
|
|
||||||
|
|
||||||
enum AccountCurrency {
|
|
||||||
USD
|
|
||||||
KHR
|
|
||||||
}
|
|
||||||
|
|
||||||
type User {
|
|
||||||
id: ID
|
|
||||||
code: String
|
|
||||||
username: String
|
|
||||||
name: String
|
|
||||||
enabled: Boolean
|
|
||||||
}
|
|
||||||
|
|
||||||
type Account {
|
|
||||||
id: ID
|
|
||||||
code: String
|
|
||||||
balance: Float
|
|
||||||
currentBalance: Float
|
|
||||||
type: AccountType
|
|
||||||
currency: AccountCurrency
|
|
||||||
user: User!
|
|
||||||
}
|
|
||||||
|
|
||||||
input UserInput {
|
|
||||||
username: String
|
|
||||||
password: String
|
|
||||||
name: String
|
|
||||||
enabled: Boolean
|
|
||||||
}
|
|
||||||
|
|
||||||
input AccountInput {
|
|
||||||
userId: Int
|
|
||||||
type: AccountType
|
|
||||||
currency: AccountCurrency
|
|
||||||
}
|
|
||||||
|
|
||||||
type Query {
|
|
||||||
hello: String
|
|
||||||
|
|
||||||
fetchUsers: [User]!
|
|
||||||
|
|
||||||
fetchAccounts: [Account]!
|
|
||||||
}
|
|
||||||
|
|
||||||
type Subscription {
|
|
||||||
hello: Int
|
|
||||||
}
|
|
||||||
|
|
||||||
type Mutation {
|
|
||||||
createUser(input: UserInput): User!
|
|
||||||
|
|
||||||
openAccount(input: AccountInput): Account!
|
|
||||||
}
|
|
26
src/main/resources/schema/account.graphql
Normal file
26
src/main/resources/schema/account.graphql
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
enum AccountType {
|
||||||
|
BASIC
|
||||||
|
PREMIUM
|
||||||
|
BUSINESS
|
||||||
|
}
|
||||||
|
|
||||||
|
enum AccountCurrency {
|
||||||
|
USD
|
||||||
|
KHR
|
||||||
|
}
|
||||||
|
|
||||||
|
type Account {
|
||||||
|
id: ID
|
||||||
|
code: String
|
||||||
|
balance: Float
|
||||||
|
currentBalance: Float
|
||||||
|
type: AccountType
|
||||||
|
currency: AccountCurrency
|
||||||
|
user: User!
|
||||||
|
}
|
||||||
|
|
||||||
|
input AccountInput {
|
||||||
|
userId: Int
|
||||||
|
type: AccountType
|
||||||
|
currency: AccountCurrency
|
||||||
|
}
|
16
src/main/resources/schema/expense.graphql
Normal file
16
src/main/resources/schema/expense.graphql
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
input ExpenseFilter {
|
||||||
|
isIncome: Boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
type Expense {
|
||||||
|
id: ID
|
||||||
|
description: String
|
||||||
|
amount: Int
|
||||||
|
isIncome: Boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
type ExpensePage {
|
||||||
|
list: [Expense]
|
||||||
|
totalPages: Int
|
||||||
|
currentPage: Int
|
||||||
|
}
|
21
src/main/resources/schema/schema.graphqls
Normal file
21
src/main/resources/schema/schema.graphqls
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
type Query {
|
||||||
|
hello: String
|
||||||
|
|
||||||
|
helloByName(name: String!): String
|
||||||
|
|
||||||
|
fetchUsers: [User]!
|
||||||
|
|
||||||
|
fetchAccounts: [Account]!
|
||||||
|
|
||||||
|
fetchExpenses(filter: ExpenseFilter, pageNumber: Int, pageSize: Int) : ExpensePage
|
||||||
|
}
|
||||||
|
|
||||||
|
type Subscription {
|
||||||
|
hello: Int
|
||||||
|
}
|
||||||
|
|
||||||
|
type Mutation {
|
||||||
|
createUser(input: UserInput): User!
|
||||||
|
|
||||||
|
openAccount(input: AccountInput): Account!
|
||||||
|
}
|
14
src/main/resources/schema/user.graphql
Normal file
14
src/main/resources/schema/user.graphql
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
type User {
|
||||||
|
id: ID
|
||||||
|
code: String
|
||||||
|
username: String
|
||||||
|
name: String
|
||||||
|
enabled: Boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
input UserInput {
|
||||||
|
username: String
|
||||||
|
password: String
|
||||||
|
name: String
|
||||||
|
enabled: Boolean
|
||||||
|
}
|
@ -1,13 +1,55 @@
|
|||||||
package com.cubetiqs.graphql.demo
|
package com.cubetiqs.graphql.demo
|
||||||
|
|
||||||
|
import com.cubetiqs.graphql.demo.resolver.subscription.HelloSubscriptionResolver
|
||||||
|
import com.netflix.graphql.dgs.DgsQueryExecutor
|
||||||
|
import com.netflix.graphql.dgs.autoconfig.DgsAutoConfiguration
|
||||||
|
import graphql.ExecutionResult
|
||||||
import org.junit.jupiter.api.Test
|
import org.junit.jupiter.api.Test
|
||||||
|
import org.reactivestreams.Publisher
|
||||||
|
import org.reactivestreams.Subscriber
|
||||||
|
import org.reactivestreams.Subscription
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired
|
||||||
import org.springframework.boot.test.context.SpringBootTest
|
import org.springframework.boot.test.context.SpringBootTest
|
||||||
|
|
||||||
@SpringBootTest
|
@SpringBootTest(
|
||||||
|
classes = [
|
||||||
|
DgsAutoConfiguration::class,
|
||||||
|
HelloSubscriptionResolver::class,
|
||||||
|
]
|
||||||
|
)
|
||||||
class GraphqlDemoApplicationTests {
|
class GraphqlDemoApplicationTests {
|
||||||
|
@Autowired
|
||||||
|
lateinit var dgsQueryExecutor: DgsQueryExecutor
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun contextLoads() {
|
fun helloSubscription() {
|
||||||
|
dgsQueryExecutor.execute(
|
||||||
|
"""
|
||||||
|
subscription {
|
||||||
|
hello
|
||||||
|
}
|
||||||
|
""".trimIndent()
|
||||||
|
)
|
||||||
|
.getData<Publisher<ExecutionResult>>()
|
||||||
|
.subscribe(object : Subscriber<ExecutionResult> {
|
||||||
|
override fun onSubscribe(s: Subscription) {
|
||||||
|
s.request(2)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onNext(t: ExecutionResult) {
|
||||||
|
println(t.getData<Any?>())
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onError(t: Throwable?) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onComplete() {
|
||||||
|
println("Hello World")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user