Task: Upgraded the dgs graphql to submodules and parent modules for extends projects

This commit is contained in:
2021-08-08 12:33:34 +07:00
parent 83477a9946
commit 32f356569d
39 changed files with 170 additions and 58 deletions

View File

@@ -0,0 +1,11 @@
package com.cubetiqs.graphql.demo
import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.boot.runApplication
@SpringBootApplication
class GraphqlDemoApplication
fun main(args: Array<String>) {
runApplication<GraphqlDemoApplication>(*args)
}

View File

@@ -0,0 +1,6 @@
package com.cubetiqs.graphql.demo.config
import org.springframework.context.annotation.Configuration
@Configuration
class GraphQLConfig

View File

@@ -0,0 +1,8 @@
package com.cubetiqs.graphql.demo.config
import org.springframework.context.annotation.Configuration
import org.springframework.transaction.annotation.EnableTransactionManagement
@Configuration
@EnableTransactionManagement
class ManagementConfig

View File

@@ -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)
}
}

View File

@@ -0,0 +1,23 @@
package com.cubetiqs.graphql.demo.config
import org.springframework.context.annotation.Configuration
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity
import org.springframework.security.config.annotation.web.builders.HttpSecurity
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter
import org.springframework.security.config.http.SessionCreationPolicy
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
class WebSecurityConfig : WebSecurityConfigurerAdapter() {
override fun configure(http: HttpSecurity) {
http.csrf().disable()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
.anyRequest()
.permitAll()
}
}

View File

@@ -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 GMutation

View File

@@ -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 GQuery

View File

@@ -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

View File

@@ -0,0 +1,10 @@
package com.cubetiqs.graphql.demo.domain
import org.springframework.data.jpa.domain.support.AuditingEntityListener
import java.io.Serializable
import javax.persistence.EntityListeners
import javax.persistence.MappedSuperclass
@MappedSuperclass
@EntityListeners(AuditingEntityListener::class)
abstract class AbstractEntity<T, ID : Serializable> : Serializable

View File

@@ -0,0 +1,5 @@
package com.cubetiqs.graphql.demo.domain
import java.io.Serializable
abstract class AbstractInput<T> : Serializable

View File

@@ -0,0 +1,70 @@
package com.cubetiqs.graphql.demo.domain.account
import com.cubetiqs.graphql.demo.domain.AbstractEntity
import com.cubetiqs.graphql.demo.domain.user.User
import com.fasterxml.jackson.annotation.JsonBackReference
import org.hibernate.Hibernate
import org.springframework.data.annotation.CreatedDate
import org.springframework.data.annotation.LastModifiedDate
import java.math.BigDecimal
import java.util.*
import javax.persistence.*
@Entity
@Table(name = "accounts", indexes = [
Index(name = "idx_account_code", columnList = "code")
])
@EntityListeners(value = [AccountEntityListener::class])
open class Account(
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
open var id: Long? = null,
@Column(length = 9)
open var code: String? = null,
@Column
open var balance: BigDecimal = BigDecimal.ZERO,
@Column
open var currentBalance: BigDecimal = BigDecimal.ZERO,
@Column(length = 10)
@Enumerated(EnumType.STRING)
open var type: AccountType = AccountType.BASIC,
@Column(length = 3)
@Enumerated(EnumType.STRING)
open var currency: AccountCurrency = AccountCurrency.USD,
@Version
open var version: Long? = null,
@CreatedDate
@Temporal(TemporalType.TIMESTAMP)
open var createdDate: Date? = null,
@LastModifiedDate
@Temporal(TemporalType.TIMESTAMP)
open var updatedDate: Date? = null,
@JsonBackReference
@ManyToOne(fetch = FetchType.LAZY,cascade = [CascadeType.REFRESH, CascadeType.DETACH])
@JoinColumn(name = "user_id")
open var user: User? = null,
) : AbstractEntity<Account, Long>() {
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other == null || Hibernate.getClass(this) != Hibernate.getClass(other)) return false
other as Account
return id != null && id == other.id
}
override fun hashCode(): Int = 2083479647
@Override
override fun toString(): String {
return this::class.simpleName + "(id = $id , balance = $balance , currentBalance = $currentBalance , type = $type , currency = $currency , version = $version , createdDate = $createdDate , updatedDate = $updatedDate , user = ${user?.id} )"
}
}

View File

@@ -0,0 +1,6 @@
package com.cubetiqs.graphql.demo.domain.account
enum class AccountCurrency {
USD,
KHR,
}

View File

@@ -0,0 +1,25 @@
package com.cubetiqs.graphql.demo.domain.account
import java.util.*
import javax.persistence.PostPersist
import javax.persistence.PrePersist
import javax.persistence.PreUpdate
class AccountEntityListener {
@PrePersist
fun beforeSave(account: Account) {
if (account.createdDate == null) {
account.createdDate = Date()
}
}
@PreUpdate
fun beforeUpdate(account: Account) {
account.updatedDate = Date()
}
@PostPersist
fun afterSaved(account: Account) {
account.code = account.id.toString().padStart(9, '0')
}
}

View File

@@ -0,0 +1,7 @@
package com.cubetiqs.graphql.demo.domain.account
data class AccountInput(
var userId: Long? = null,
var type: AccountType? = null,
var currency: AccountCurrency? = null,
)

View File

@@ -0,0 +1,10 @@
package com.cubetiqs.graphql.demo.domain.account
object AccountMapper {
fun fromInputToAccount(input: AccountInput): Account {
return Account(
type = input.type ?: AccountType.BASIC,
currency = input.currency ?: AccountCurrency.USD,
)
}
}

View File

@@ -0,0 +1,7 @@
package com.cubetiqs.graphql.demo.domain.account
enum class AccountType {
BASIC,
PREMIUM,
BUSINESS,
}

View File

@@ -0,0 +1,75 @@
package com.cubetiqs.graphql.demo.domain.user
import com.cubetiqs.graphql.demo.domain.AbstractEntity
import com.cubetiqs.graphql.demo.domain.account.Account
import com.fasterxml.jackson.annotation.JsonManagedReference
import org.hibernate.Hibernate
import org.springframework.data.annotation.CreatedDate
import org.springframework.data.annotation.LastModifiedDate
import java.util.*
import javax.persistence.*
@Entity
@Table(
name = "users", indexes = [
Index(name = "idx_user_code", columnList = "code"),
Index(name = "idx_user_username", columnList = "username"),
]
)
@EntityListeners(value = [UserEntityListener::class])
open class User(
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
open var id: Long? = null,
@Column(unique = true, length = 65, nullable = false)
open var code: String? = null,
@Column(unique = true, length = 35, nullable = false)
open var username: String? = null,
@Column(length = 100)
open var password: String? = null,
@Column(length = 50)
open var name: String? = null,
@Version
open var version: Long? = null,
@CreatedDate
@Temporal(TemporalType.TIMESTAMP)
open var createdDate: Date? = null,
@LastModifiedDate
@Temporal(TemporalType.TIMESTAMP)
open var updatedDate: Date? = null,
@JsonManagedReference
@OneToMany(fetch = FetchType.LAZY,mappedBy = "user", cascade = [CascadeType.ALL], orphanRemoval = true)
open var accounts: MutableSet<Account> = mutableSetOf(),
@Basic
open var enabled: Boolean = true,
) : AbstractEntity<User, Long>() {
@Transient
fun updatePassword(newPassword: String) {
// hash it here
this.password = newPassword
}
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other == null || Hibernate.getClass(this) != Hibernate.getClass(other)) return false
other as User
return id != null && id == other.id
}
override fun hashCode(): Int = 562048007
@Override
override fun toString(): String {
return this::class.simpleName + "(id = $id , code = $code , username = $username , password = $password , name = $name , version = $version , createdDate = $createdDate , updatedDate = $updatedDate , enabled = $enabled )"
}
}

View File

@@ -0,0 +1,22 @@
package com.cubetiqs.graphql.demo.domain.user
import java.util.*
import javax.persistence.PrePersist
import javax.persistence.PreUpdate
class UserEntityListener {
@PrePersist
fun beforeSave(user: User) {
user.id = null
user.code = UUID.randomUUID().toString()
if (user.createdDate == null) {
user.createdDate = Date()
}
}
@PreUpdate
fun beforeUpdate(user: User) {
user.updatedDate = Date()
}
}

View File

@@ -0,0 +1,10 @@
package com.cubetiqs.graphql.demo.domain.user
import com.cubetiqs.graphql.demo.domain.AbstractInput
data class UserInput(
val username: String? = null,
val password: String? = null,
val name: String? = null,
val enabled: Boolean? = null,
) : AbstractInput<UserInput>()

View File

@@ -0,0 +1,12 @@
package com.cubetiqs.graphql.demo.domain.user
object UserMapper {
fun fromInputToUser(input: UserInput): User {
return User(
username = input.username,
password = input.password,
name = input.name,
enabled = input.enabled ?: true,
)
}
}

View File

@@ -0,0 +1,8 @@
package com.cubetiqs.graphql.demo.repository
import com.cubetiqs.graphql.demo.domain.account.Account
import org.springframework.data.jpa.repository.JpaRepository
import org.springframework.stereotype.Repository
@Repository
interface AccountRepository : JpaRepository<Account, Long>

View File

@@ -0,0 +1,17 @@
package com.cubetiqs.graphql.demo.repository
import com.cubetiqs.graphql.demo.domain.user.User
import org.springframework.data.domain.Page
import org.springframework.data.domain.Pageable
import org.springframework.data.jpa.repository.JpaRepository
import org.springframework.data.jpa.repository.Query
import org.springframework.stereotype.Repository
@Repository
interface UserRepository : JpaRepository<User, Long> {
@Query("select u from User u where u.enabled = true")
fun queryAllByEnabledIsTrue(pageable: Pageable): Page<User>
@Query("select (count(u) > 0) from User u where u.username = ?1")
fun existsAllByUsername(username: String): Boolean
}

View File

@@ -0,0 +1,32 @@
package com.cubetiqs.graphql.demo.resolver.mutation
import com.cubetiqs.graphql.demo.context.GMutation
import com.cubetiqs.graphql.demo.dgmodel.DgsConstants
import com.cubetiqs.graphql.demo.domain.account.Account
import com.cubetiqs.graphql.demo.domain.account.AccountInput
import com.cubetiqs.graphql.demo.domain.account.AccountMapper
import com.cubetiqs.graphql.demo.repository.AccountRepository
import com.cubetiqs.graphql.demo.repository.UserRepository
import com.netflix.graphql.dgs.DgsMutation
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.transaction.annotation.Propagation
import org.springframework.transaction.annotation.Transactional
@GMutation
class AccountMutationResolver {
@Autowired
private lateinit var accountRepository: AccountRepository
@Autowired
private lateinit var userRepository: UserRepository
@DgsMutation(field = DgsConstants.MUTATION.OpenAccount)
@Transactional(propagation = Propagation.REQUIRES_NEW)
fun openAccount(input: AccountInput): Account {
val account = AccountMapper.fromInputToAccount(input)
val user = userRepository.findById(input.userId ?: 0).orElse(null)
?: throw Exception("User not found to open an account!")
account.user = user
return accountRepository.save(account)
}
}

View File

@@ -0,0 +1,26 @@
package com.cubetiqs.graphql.demo.resolver.mutation
import com.cubetiqs.graphql.demo.context.GMutation
import com.cubetiqs.graphql.demo.dgmodel.DgsConstants
import com.cubetiqs.graphql.demo.domain.user.User
import com.cubetiqs.graphql.demo.domain.user.UserInput
import com.cubetiqs.graphql.demo.domain.user.UserMapper
import com.cubetiqs.graphql.demo.repository.UserRepository
import com.netflix.graphql.dgs.DgsMutation
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.transaction.annotation.Propagation
import org.springframework.transaction.annotation.Transactional
@GMutation
class UserMutationResolver @Autowired constructor(
private val userRepository: UserRepository,
) {
@DgsMutation(field = DgsConstants.MUTATION.CreateUser)
@Transactional(propagation = Propagation.REQUIRES_NEW)
fun createUser(input: UserInput): User {
if (userRepository.existsAllByUsername(input.username ?: "")) throw Exception("Username has been already existed!")
val user = UserMapper.fromInputToUser(input)
return userRepository.save(user)
}
}

View File

@@ -0,0 +1,20 @@
package com.cubetiqs.graphql.demo.resolver.query
import com.cubetiqs.graphql.demo.context.GQuery
import com.cubetiqs.graphql.demo.dgmodel.DgsConstants
import com.cubetiqs.graphql.demo.domain.account.Account
import com.cubetiqs.graphql.demo.repository.AccountRepository
import com.netflix.graphql.dgs.DgsQuery
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.data.domain.Pageable
@GQuery
class AccountQueryResolver @Autowired constructor(
private val accountRepository: AccountRepository,
) {
@DgsQuery(field = DgsConstants.QUERY.FetchAccounts)
fun fetchAccounts(): Collection<Account> {
val accounts = accountRepository.findAll(Pageable.unpaged())
return accounts.content
}
}

View File

@@ -0,0 +1,22 @@
package com.cubetiqs.graphql.demo.resolver.query
import com.cubetiqs.graphql.demo.context.GQuery
import com.cubetiqs.graphql.demo.dgmodel.DgsConstants
import com.netflix.graphql.dgs.DgsQuery
import org.springframework.security.access.prepost.PreAuthorize
import reactor.core.publisher.Mono
import java.util.concurrent.CompletableFuture
@GQuery
class HelloQueryResolver {
@DgsQuery(field = "hello")
fun hello(): CompletableFuture<String> {
return Mono.just("Hello Query...!").toFuture()
}
@PreAuthorize("hasAnyRole('ADMIN')")
@DgsQuery(field = DgsConstants.QUERY.HelloByName)
fun helloByName(name: String): CompletableFuture<String> {
return Mono.just("Hello $name...!").toFuture()
}
}

View File

@@ -0,0 +1,20 @@
package com.cubetiqs.graphql.demo.resolver.query
import com.cubetiqs.graphql.demo.context.GQuery
import com.cubetiqs.graphql.demo.dgmodel.DgsConstants
import com.cubetiqs.graphql.demo.domain.user.User
import com.cubetiqs.graphql.demo.repository.UserRepository
import com.netflix.graphql.dgs.DgsQuery
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.data.domain.Pageable
@GQuery
class UserQueryResolver @Autowired constructor(
private val userRepository: UserRepository,
) {
@DgsQuery(field = DgsConstants.QUERY.FetchUsers)
fun fetchUsers(): Collection<User> {
val users = userRepository.queryAllByEnabledIsTrue(Pageable.unpaged())
return users.content
}
}

View File

@@ -0,0 +1,16 @@
package com.cubetiqs.graphql.demo.resolver.subscription
import com.cubetiqs.graphql.demo.context.GSubscription
import com.netflix.graphql.dgs.DgsSubscription
import graphql.schema.DataFetchingEnvironment
import org.reactivestreams.Publisher
import reactor.core.publisher.Flux
import java.time.Duration
@GSubscription
class HelloSubscriptionResolver {
@DgsSubscription(field = "hello")
fun hello(env: DataFetchingEnvironment): Publisher<Int> {
return Flux.range(1, 10).delayElements(Duration.ofSeconds(1))
}
}

View File

@@ -0,0 +1,33 @@
server:
port: 8081
# Spring Boot
spring:
datasource:
url: jdbc:postgresql://${DB_HOST:localhost}:5432/${DB_NAME:graphql-demo}
username: ${DB_USER:your-username}
password: ${DB_PASSWORD:your-password}
driver-class-name: org.postgresql.Driver
jpa:
database: postgresql
database-platform: org.hibernate.dialect.PostgreSQLDialect
hibernate:
ddl-auto: ${HIBERNATE_DDL:update}
show-sql: ${JPA_SHOW_SQL:true}
properties:
hibernate:
enable_lazy_load_no_trans: ${HIBERNATE_LAZY_NO_TRANS:true}
main:
allow-bean-definition-overriding: true
# Spring Boot Actuator
management:
endpoints:
web:
exposure:
include: health,info,metrics
# DGS GraphQL
dgs:
graphql:
graphiql:
enabled: true

View 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
}

View 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
}

View 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!
}

View 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
}

View File

@@ -0,0 +1,55 @@
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.reactivestreams.Publisher
import org.reactivestreams.Subscriber
import org.reactivestreams.Subscription
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.context.SpringBootTest
@SpringBootTest(
classes = [
DgsAutoConfiguration::class,
HelloSubscriptionResolver::class,
]
)
class GraphqlDemoApplicationTests {
@Autowired
lateinit var dgsQueryExecutor: DgsQueryExecutor
@Test
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")
}
}
)
}
}