Add basic module and redis data with jpa and example
Some checks failed
continuous-integration/drone/push Build is failing
Some checks failed
continuous-integration/drone/push Build is failing
This commit is contained in:
parent
bc81cb0988
commit
8c999a9e7c
154
README.md
154
README.md
@ -1,16 +1,170 @@
|
|||||||
# CUBETIQ Web Modules (Template)
|
# CUBETIQ Web Modules (Template)
|
||||||
|
|
||||||
- Setup and Default Web Configuration
|
- Setup and Default Web Configuration
|
||||||
- Swagger UI and API's Documentation (SpringFox)
|
- Swagger UI and API's Documentation (SpringFox)
|
||||||
- General Purpose for External and Internal use-cases
|
- General Purpose for External and Internal use-cases
|
||||||
- Dockerfile and Docker profile build support
|
- Dockerfile and Docker profile build support
|
||||||
|
|
||||||
# Modules
|
# Modules
|
||||||
|
|
||||||
- API (Default Module)
|
- API (Default Module)
|
||||||
|
|
||||||
|
### Spring Data
|
||||||
|
|
||||||
|
- Add `spring-data-jpa` dependency in `build.gradle.kts`
|
||||||
|
|
||||||
|
```kotlin
|
||||||
|
implementation("org.springframework.boot:spring-boot-starter-data-jpa")
|
||||||
|
```
|
||||||
|
|
||||||
|
- Add `spring-data-redis` dependency in `build.gradle.kts` (Redis + Driver)
|
||||||
|
|
||||||
|
```kotlin
|
||||||
|
implementation("org.springframework.boot:spring-boot-starter-data-redis")
|
||||||
|
```
|
||||||
|
|
||||||
|
### Spring Boot Properties
|
||||||
|
|
||||||
|
- Recommend
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
spring:
|
||||||
|
application:
|
||||||
|
name: ${APP_NAME:spring-web-api}
|
||||||
|
app:
|
||||||
|
data-dir: ${APP_DATA_DIR:${user.home}/${spring.application.name}}
|
||||||
|
```
|
||||||
|
|
||||||
|
- Upload File Properties
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
server:
|
||||||
|
tomcat:
|
||||||
|
max-http-form-post-size: ${SERVER_MAX_HTTP_FORM_POST_SIZE:50MB}
|
||||||
|
|
||||||
|
spring:
|
||||||
|
servlet:
|
||||||
|
multipart:
|
||||||
|
max-file-size: 256MB
|
||||||
|
max-request-size: 256MB
|
||||||
|
enabled: true
|
||||||
|
```
|
||||||
|
|
||||||
|
- Logging
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
logging:
|
||||||
|
file:
|
||||||
|
path: ${LOGGING_FILE_PATH:${app.data-dir}/logs/}
|
||||||
|
name: ${logging.file.path}/app.log
|
||||||
|
```
|
||||||
|
|
||||||
|
### Spring Data Redis
|
||||||
|
|
||||||
|
- Redis Properties
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
spring:
|
||||||
|
redis:
|
||||||
|
host: ${REDIS_HOST:localhost}
|
||||||
|
port: ${REDIS_PORT:6379}
|
||||||
|
password: ${REDIS_PASSWORD:your_password}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Spring Data JPA Properties
|
||||||
|
|
||||||
|
- Datasource Enhancement (Default: PostgresSQL)
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
spring:
|
||||||
|
datasource:
|
||||||
|
driverClassName: ${DATA_SOURCE_DRIVER_CLASS_NAME:org.postgresql.Driver}
|
||||||
|
url: ${DATA_SOURCE_URL:jdbc:postgresql://localhost:5432/demo}
|
||||||
|
username: ${POSTGRES_USERNAME:postgres}
|
||||||
|
password: ${POSTGRES_PASSWORD:postgres}
|
||||||
|
hikari:
|
||||||
|
max-lifetime: ${DATA_SOURCE_MAX_LIFETIME:1800000}
|
||||||
|
connection-timeout: ${DATA_SOURCE_CONNECTION_TIMEOUT:30000}
|
||||||
|
idle-timeout: ${DATA_SOURCE_IDLE_TIMEOUT:600000}
|
||||||
|
maximum-pool-size: ${DATA_SOURCE_MAXIMUM_POOL_SIZE:10}
|
||||||
|
allow-pool-suspension: ${DATA_SOURCE_ALLOW_POOL_SUSPENSION:true}
|
||||||
|
tomcat:
|
||||||
|
max_active: ${DATA_SOURCE_TOMCAT_MAX_ACTIVE:100}
|
||||||
|
max_idle: ${DATA_SOURCE_TOMCAT_MAX_IDLE:10}
|
||||||
|
min-idle: ${DATA_SOURCE_TOMCAT_MIN_IDLE:10}
|
||||||
|
initial_size: ${DATA_SOURCE_TOMCAT_INITIAL_SIZE:10}
|
||||||
|
remove_abandoned: ${DATA_SOURCE_TOMCAT_REMOVE_ABANDONED:true}
|
||||||
|
jpa:
|
||||||
|
database-platform: ${JPA_DATABASE_PLATFORM:org.hibernate.dialect.PostgreSQLDialect}
|
||||||
|
show-sql: ${JPA_SHOW_SQL:false}
|
||||||
|
hibernate:
|
||||||
|
ddl-auto: ${JPA_HIBERNATE_DDL_AUTO:update}
|
||||||
|
properties:
|
||||||
|
hibernate:
|
||||||
|
dialect: ${JPA_HIBERNATE_DIALECT:org.hibernate.dialect.PostgreSQLDialect}
|
||||||
|
open-in-view: ${JPA_OPEN_IN_VIEW:false}
|
||||||
|
```
|
||||||
|
|
||||||
|
- PostgreSQL
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
spring:
|
||||||
|
datasource:
|
||||||
|
driverClassName: ${DATA_SOURCE_DRIVER_CLASS_NAME:org.postgresql.Driver}
|
||||||
|
url: jdbc:postgresql://${POSTGRES_HOST:localhost}:${POSTGRES_PORT:5432}/${POSTGRES_DB:demo}
|
||||||
|
username: ${POSTGRES_USERNAME:postgres}
|
||||||
|
password: ${POSTGRES_PASSWORD:postgres}
|
||||||
|
```
|
||||||
|
|
||||||
|
- MySQL
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
spring:
|
||||||
|
datasource:
|
||||||
|
driverClassName: ${DATA_SOURCE_DRIVER_CLASS_NAME:com.mysql.cj.jdbc.Driver}
|
||||||
|
url: jdbc:mysql://${MYSQL_HOST:localhost}:${MYSQL_PORT:3306}/${MYSQL_DB:demo}?createDatabaseIfNotExist=true&useUnicode=true&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=UTC
|
||||||
|
username: ${MYSQL_USERNAME:demo}
|
||||||
|
password: ${MYSQL_PASSWORD:demo}
|
||||||
|
jpa:
|
||||||
|
database-platform: ${JPA_DATABASE_PLATFORM:org.hibernate.dialect.MySQLDialect}
|
||||||
|
properties:
|
||||||
|
hibernate:
|
||||||
|
dialect: ${JPA_HIBERNATE_DIALECT:org.hibernate.dialect.MySQLDialect}
|
||||||
|
```
|
||||||
|
|
||||||
|
- H2 (Embedded)
|
||||||
|
-
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
spring:
|
||||||
|
datasource:
|
||||||
|
driverClassName: ${DATA_SOURCE_DRIVER_CLASS_NAME:org.h2.Driver}
|
||||||
|
url: jdbc:h2:file:${H2_DB_PATH:./data/db};DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE
|
||||||
|
username: ${H2_USERNAME:sa}
|
||||||
|
password: ${H2_PASSWORD:password}
|
||||||
|
jpa:
|
||||||
|
database-platform: ${JPA_DATABASE_PLATFORM:org.hibernate.dialect.H2Dialect}
|
||||||
|
h2:
|
||||||
|
console:
|
||||||
|
enabled: ${H2_CONSOLE_ENABLED:true}
|
||||||
|
```
|
||||||
|
|
||||||
|
- Avoid the Lazy Initialization Problem
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
spring:
|
||||||
|
jpa:
|
||||||
|
properties:
|
||||||
|
hibernate:
|
||||||
|
enable_lazy_load_no_trans: ${HIBERNATE_LAZY_NO_TRANS:true}
|
||||||
|
```
|
||||||
|
|
||||||
# Contributors
|
# Contributors
|
||||||
|
|
||||||
- Sambo Chea <sombochea@cubetiqs.com>
|
- Sambo Chea <sombochea@cubetiqs.com>
|
||||||
|
|
||||||
### Language and Framework
|
### Language and Framework
|
||||||
|
|
||||||
- Spring Boot: 2.6.6
|
- Spring Boot: 2.6.6
|
||||||
- Kotlin: 1.6.20
|
- Kotlin: 1.6.20
|
||||||
- Gradle: 7.4.1
|
- Gradle: 7.4.1
|
||||||
|
@ -5,6 +5,7 @@ plugins {
|
|||||||
id("io.spring.dependency-management")
|
id("io.spring.dependency-management")
|
||||||
kotlin("jvm")
|
kotlin("jvm")
|
||||||
kotlin("plugin.spring")
|
kotlin("plugin.spring")
|
||||||
|
kotlin("plugin.jpa")
|
||||||
}
|
}
|
||||||
|
|
||||||
val kotlinVersion = "1.6.20"
|
val kotlinVersion = "1.6.20"
|
||||||
@ -32,6 +33,10 @@ springBoot {
|
|||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
|
// Spring Data JPA (Required for Database Layer)
|
||||||
|
implementation("org.springframework.boot:spring-boot-starter-data-jpa")
|
||||||
|
implementation("org.springframework.boot:spring-boot-starter-data-redis")
|
||||||
|
|
||||||
// Migrating from SpringFox
|
// Migrating from SpringFox
|
||||||
implementation("org.springdoc:springdoc-openapi-ui:1.6.7")
|
implementation("org.springdoc:springdoc-openapi-ui:1.6.7")
|
||||||
|
|
||||||
@ -44,6 +49,10 @@ dependencies {
|
|||||||
|
|
||||||
implementation("org.jetbrains.kotlin:kotlin-reflect")
|
implementation("org.jetbrains.kotlin:kotlin-reflect")
|
||||||
implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
|
implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
|
||||||
|
|
||||||
|
runtimeOnly("com.h2database:h2")
|
||||||
|
runtimeOnly("org.postgresql:postgresql")
|
||||||
|
|
||||||
testImplementation("org.springframework.boot:spring-boot-starter-test")
|
testImplementation("org.springframework.boot:spring-boot-starter-test")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -0,0 +1,20 @@
|
|||||||
|
package com.cubetiqs.web.modules.redis
|
||||||
|
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired
|
||||||
|
import org.springframework.context.annotation.Bean
|
||||||
|
import org.springframework.context.annotation.Configuration
|
||||||
|
import org.springframework.data.redis.connection.RedisConnectionFactory
|
||||||
|
import org.springframework.data.redis.core.RedisTemplate
|
||||||
|
|
||||||
|
@RedisModule
|
||||||
|
@Configuration
|
||||||
|
class RedisConfig @Autowired constructor(
|
||||||
|
private val connectionFactory: RedisConnectionFactory,
|
||||||
|
) {
|
||||||
|
@Bean
|
||||||
|
fun redisTemplate(): RedisTemplate<String, RedisKVModel> {
|
||||||
|
val template = RedisTemplate<String, RedisKVModel>()
|
||||||
|
template.setConnectionFactory(connectionFactory)
|
||||||
|
return template
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,31 @@
|
|||||||
|
package com.cubetiqs.web.modules.redis
|
||||||
|
|
||||||
|
import com.cubetiqs.web.util.RouteConstants
|
||||||
|
import io.swagger.v3.oas.annotations.tags.Tag
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired
|
||||||
|
import org.springframework.data.redis.core.RedisTemplate
|
||||||
|
import org.springframework.web.bind.annotation.*
|
||||||
|
|
||||||
|
@RedisModule
|
||||||
|
@Tag(name = "Redis Controller")
|
||||||
|
@RestController
|
||||||
|
@RequestMapping(RouteConstants.INDEX + "redis")
|
||||||
|
class RedisController @Autowired constructor(
|
||||||
|
private val redisTemplate: RedisTemplate<String, RedisKVModel>,
|
||||||
|
) {
|
||||||
|
@GetMapping("/{key}")
|
||||||
|
fun getAll(
|
||||||
|
@PathVariable("key") key: String,
|
||||||
|
): Collection<RedisKVModel?> {
|
||||||
|
return redisTemplate.opsForValue().multiGet(listOf(key)) ?: listOf()
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("/{key}")
|
||||||
|
fun set(
|
||||||
|
@PathVariable("key") key: String,
|
||||||
|
@RequestBody body: RedisKVModel
|
||||||
|
): RedisKVModel {
|
||||||
|
redisTemplate.opsForValue().set(key, body)
|
||||||
|
return body
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,14 @@
|
|||||||
|
package com.cubetiqs.web.modules.redis
|
||||||
|
|
||||||
|
import java.io.Serializable
|
||||||
|
|
||||||
|
data class RedisKVModel(
|
||||||
|
var key: String? = null,
|
||||||
|
var value: Any? = null,
|
||||||
|
) : Serializable {
|
||||||
|
companion object {
|
||||||
|
fun create(key: String, value: Any): RedisKVModel {
|
||||||
|
return RedisKVModel(key, value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,6 @@
|
|||||||
|
package com.cubetiqs.web.modules.redis
|
||||||
|
|
||||||
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty
|
||||||
|
|
||||||
|
@ConditionalOnProperty(name = ["spring.redis.enabled"], havingValue = "true")
|
||||||
|
annotation class RedisModule
|
@ -0,0 +1,60 @@
|
|||||||
|
package com.cubetiqs.web.modules.user
|
||||||
|
|
||||||
|
import com.cubetiqs.web.util.RouteConstants
|
||||||
|
import io.swagger.v3.oas.annotations.Parameter
|
||||||
|
import io.swagger.v3.oas.annotations.tags.Tag
|
||||||
|
import org.springdoc.core.converters.models.PageableAsQueryParam
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired
|
||||||
|
import org.springframework.data.domain.Page
|
||||||
|
import org.springframework.data.domain.Pageable
|
||||||
|
import org.springframework.web.bind.annotation.*
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
@UserModule
|
||||||
|
@Tag(name = "User Controller")
|
||||||
|
@RestController
|
||||||
|
@RequestMapping(RouteConstants.INDEX + "user")
|
||||||
|
class UserController @Autowired constructor(
|
||||||
|
private val userRepository: UserRepository,
|
||||||
|
) {
|
||||||
|
@GetMapping
|
||||||
|
@PageableAsQueryParam
|
||||||
|
fun getAll(
|
||||||
|
@Parameter(hidden = true)
|
||||||
|
pageable: Pageable?,
|
||||||
|
): Page<UserEntity> {
|
||||||
|
return userRepository.findAll(pageable ?: Pageable.unpaged())
|
||||||
|
}
|
||||||
|
|
||||||
|
@ResponseStatus(value = org.springframework.http.HttpStatus.CREATED)
|
||||||
|
@PostMapping
|
||||||
|
fun create(
|
||||||
|
@RequestBody body: UserEntity
|
||||||
|
): UserEntity {
|
||||||
|
return userRepository.save(body)
|
||||||
|
}
|
||||||
|
|
||||||
|
@ResponseStatus(value = org.springframework.http.HttpStatus.OK)
|
||||||
|
@PutMapping("/{id}")
|
||||||
|
fun update(
|
||||||
|
@PathVariable id: String,
|
||||||
|
@RequestBody body: UserEntity
|
||||||
|
): UserEntity {
|
||||||
|
val user = userRepository.findById(UUID.fromString(id)).orElseThrow {
|
||||||
|
throw IllegalArgumentException("User not found")
|
||||||
|
}
|
||||||
|
body.id = user.id
|
||||||
|
return userRepository.save(body)
|
||||||
|
}
|
||||||
|
|
||||||
|
@ResponseStatus(value = org.springframework.http.HttpStatus.NO_CONTENT)
|
||||||
|
@DeleteMapping("/{id}")
|
||||||
|
fun delete(
|
||||||
|
@PathVariable id: String,
|
||||||
|
) {
|
||||||
|
val user = userRepository.findById(UUID.fromString(id)).orElseThrow {
|
||||||
|
throw IllegalArgumentException("User not found")
|
||||||
|
}
|
||||||
|
userRepository.delete(user)
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,30 @@
|
|||||||
|
package com.cubetiqs.web.modules.user
|
||||||
|
|
||||||
|
import org.hibernate.Hibernate
|
||||||
|
import java.io.Serializable
|
||||||
|
import java.util.*
|
||||||
|
import javax.persistence.*
|
||||||
|
|
||||||
|
@Entity
|
||||||
|
@Table(name = "user")
|
||||||
|
open class UserEntity(
|
||||||
|
@Id
|
||||||
|
@GeneratedValue(strategy = GenerationType.AUTO)
|
||||||
|
open var id: UUID? = null,
|
||||||
|
|
||||||
|
@Column(name = "name", length = 50)
|
||||||
|
open var name: String? = null,
|
||||||
|
|
||||||
|
@Column(name = "username", length = 50, unique = true)
|
||||||
|
open var username: String? = null,
|
||||||
|
) : Serializable {
|
||||||
|
override fun equals(other: Any?): Boolean {
|
||||||
|
if (this === other) return true
|
||||||
|
if (other == null || Hibernate.getClass(this) != Hibernate.getClass(other)) return false
|
||||||
|
other as UserEntity
|
||||||
|
|
||||||
|
return id != null && id == other.id
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun hashCode(): Int = javaClass.hashCode()
|
||||||
|
}
|
@ -0,0 +1,6 @@
|
|||||||
|
package com.cubetiqs.web.modules.user
|
||||||
|
|
||||||
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty
|
||||||
|
|
||||||
|
@ConditionalOnProperty(name = ["module.user.enabled", "spring.datasource.enabled"], havingValue = "true")
|
||||||
|
annotation class UserModule
|
@ -0,0 +1,9 @@
|
|||||||
|
package com.cubetiqs.web.modules.user
|
||||||
|
|
||||||
|
import org.springframework.data.jpa.repository.JpaRepository
|
||||||
|
import org.springframework.stereotype.Repository
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
@UserModule
|
||||||
|
@Repository
|
||||||
|
interface UserRepository : JpaRepository<UserEntity, UUID>
|
@ -1,8 +1,22 @@
|
|||||||
spring:
|
spring:
|
||||||
profiles:
|
profiles:
|
||||||
active: ${APP_PROFILE:dev}
|
active: ${APP_PROFILE:demo}
|
||||||
application:
|
application:
|
||||||
name: cubetiq-api-service
|
name: cubetiq-api-service
|
||||||
|
redis:
|
||||||
|
enabled: ${REDIS_ENABLED:false}
|
||||||
|
host: ${REDIS_HOST:localhost}
|
||||||
|
password: ${REDIS_PASSWORD:null}
|
||||||
|
datasource:
|
||||||
|
enabled: ${DATASOURCE_ENABLED:false}
|
||||||
|
driverClassName: ${DATA_SOURCE_DRIVER_CLASS_NAME:org.h2.Driver}
|
||||||
|
url: jdbc:h2:file:${H2_DB_PATH:${cubetiq.app.data-dir}/data/db};DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE
|
||||||
|
username: ${H2_USERNAME:sa}
|
||||||
|
password: ${H2_PASSWORD:password}
|
||||||
|
|
||||||
|
module:
|
||||||
|
user:
|
||||||
|
enabled: ${MODULE_USER_ENABLED:false}
|
||||||
|
|
||||||
cubetiq:
|
cubetiq:
|
||||||
app:
|
app:
|
||||||
|
8
api/src/main/resources/banner.txt
Normal file
8
api/src/main/resources/banner.txt
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
----------------------- CUBEIQ @sombochea ---------------------------
|
||||||
|
________ _____ ___ __ ______
|
||||||
|
__ ___/________ ___________(_)_______ _______ _ __ | / /_____ ___ /_
|
||||||
|
_____ \ ___ __ \__ ___/__ / __ __ \__ __ `/ __ | /| / / _ _ \__ __ \
|
||||||
|
____/ / __ /_/ /_ / _ / _ / / /_ /_/ / __ |/ |/ / / __/_ /_/ /
|
||||||
|
/____/ _ .___/ /_/ /_/ /_/ /_/ _\__, / ____/|__/ \___/ /_.___/
|
||||||
|
/_/ /____/
|
||||||
|
Spring Boot Version: ${spring-boot.formatted-version}
|
@ -5,7 +5,7 @@ plugins {
|
|||||||
id("io.spring.dependency-management") version "1.0.11.RELEASE" apply false
|
id("io.spring.dependency-management") version "1.0.11.RELEASE" apply false
|
||||||
kotlin("jvm") version "1.6.20" apply false
|
kotlin("jvm") version "1.6.20" apply false
|
||||||
kotlin("plugin.spring") version "1.6.20" apply false
|
kotlin("plugin.spring") version "1.6.20" apply false
|
||||||
// kotlin("plugin.jpa") version "1.6.10" apply false
|
kotlin("plugin.jpa") version "1.6.20" apply false
|
||||||
}
|
}
|
||||||
|
|
||||||
allprojects {
|
allprojects {
|
||||||
|
@ -15,8 +15,13 @@ metadata:
|
|||||||
namespace: spring-web-dev
|
namespace: spring-web-dev
|
||||||
name: spring-web-dev-secret
|
name: spring-web-dev-secret
|
||||||
stringData:
|
stringData:
|
||||||
|
REDIS_HOST: redis-service
|
||||||
REDIS_PASSWORD: demo
|
REDIS_PASSWORD: demo
|
||||||
POSTGRES_USER: demo
|
POSTGRES_USER: demo
|
||||||
POSTGRES_DB: demo
|
POSTGRES_DB: demo
|
||||||
POSTGRES_PASSWORD: demo
|
POSTGRES_PASSWORD: demo
|
||||||
|
APP_DATA_DIR: /opt/cubetiq/data
|
||||||
|
MODULE_USER_ENABLED: true
|
||||||
|
DATASOURCE_ENABLED: true
|
||||||
|
REDIS_ENABLED: true
|
||||||
type: Opaque
|
type: Opaque
|
Loading…
Reference in New Issue
Block a user