diff --git a/api/.gitignore b/api/.gitignore new file mode 100644 index 0000000..c2065bc --- /dev/null +++ b/api/.gitignore @@ -0,0 +1,37 @@ +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/ diff --git a/api/build.gradle.kts b/api/build.gradle.kts new file mode 100644 index 0000000..ee9030d --- /dev/null +++ b/api/build.gradle.kts @@ -0,0 +1,52 @@ +import java.io.ByteArrayOutputStream + +plugins { + id("org.springframework.boot") + id("io.spring.dependency-management") + kotlin("jvm") + kotlin("plugin.spring") +} + +val kotlinVersion = "1.4.32" +val springBootVersion = "2.4.5" + +// find the last commit +fun getGitHashLastCommit(): String { + val stdout = ByteArrayOutputStream() + exec { + commandLine("git", "rev-parse", "HEAD") + standardOutput = stdout + } + + return stdout.toString().trim() +} + +springBoot { + buildInfo { + properties { + additional["commitId"] = getGitHashLastCommit() + additional["springBootVersion"] = springBootVersion + additional["kotlinVersion"] = kotlinVersion + } + } +} + +dependencies { + // SWAGGER - FRAMEWORK + implementation("io.springfox:springfox-boot-starter:3.0.0") + + // SPRING FRAMEWORK AND CORE + implementation("org.springframework.boot:spring-boot-starter-web") + implementation("com.fasterxml.jackson.module:jackson-module-kotlin") + + // Development Runtime + developmentOnly("org.springframework.boot:spring-boot-devtools") + + implementation("org.jetbrains.kotlin:kotlin-reflect") + implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8") + testImplementation("org.springframework.boot:spring-boot-starter-test") +} + +tasks.withType { + useJUnitPlatform() +} diff --git a/api/gradle/wrapper/gradle-wrapper.jar b/api/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000..e708b1c Binary files /dev/null and b/api/gradle/wrapper/gradle-wrapper.jar differ diff --git a/api/gradle/wrapper/gradle-wrapper.properties b/api/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..442d913 --- /dev/null +++ b/api/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-6.8.3-bin.zip +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/api/src/main/kotlin/com/cubetiqs/web/ApiApplication.kt b/api/src/main/kotlin/com/cubetiqs/web/ApiApplication.kt new file mode 100644 index 0000000..0bb1387 --- /dev/null +++ b/api/src/main/kotlin/com/cubetiqs/web/ApiApplication.kt @@ -0,0 +1,11 @@ +package com.cubetiqs.web + +import org.springframework.boot.autoconfigure.SpringBootApplication +import org.springframework.boot.runApplication + +@SpringBootApplication +class ApiApplication + +fun main(args: Array) { + runApplication(*args) +} diff --git a/api/src/main/kotlin/com/cubetiqs/web/StaticContextInitializer.kt b/api/src/main/kotlin/com/cubetiqs/web/StaticContextInitializer.kt new file mode 100644 index 0000000..aecc660 --- /dev/null +++ b/api/src/main/kotlin/com/cubetiqs/web/StaticContextInitializer.kt @@ -0,0 +1,12 @@ +package com.cubetiqs.web + +import com.cubetiqs.web.stereotype.FunctionComponent +import org.springframework.context.annotation.Lazy + +@FunctionComponent +@Lazy(false) +class StaticContextInitializer { + init { + println("Static context is loaded...!") + } +} \ No newline at end of file diff --git a/api/src/main/kotlin/com/cubetiqs/web/config/DefaultConfig.kt b/api/src/main/kotlin/com/cubetiqs/web/config/DefaultConfig.kt new file mode 100644 index 0000000..edd4d7a --- /dev/null +++ b/api/src/main/kotlin/com/cubetiqs/web/config/DefaultConfig.kt @@ -0,0 +1,6 @@ +package com.cubetiqs.web.config + +import org.springframework.context.annotation.Configuration + +@Configuration +class DefaultConfig \ No newline at end of file diff --git a/api/src/main/kotlin/com/cubetiqs/web/config/SpringFoxConfig.kt b/api/src/main/kotlin/com/cubetiqs/web/config/SpringFoxConfig.kt new file mode 100644 index 0000000..6d5e46f --- /dev/null +++ b/api/src/main/kotlin/com/cubetiqs/web/config/SpringFoxConfig.kt @@ -0,0 +1,78 @@ +package com.cubetiqs.web.config + +import com.cubetiqs.web.property.AppProperties +import com.cubetiqs.web.util.AppConstants +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.boot.info.BuildProperties +import org.springframework.context.annotation.Bean +import org.springframework.context.annotation.Configuration +import springfox.documentation.builders.ApiInfoBuilder +import springfox.documentation.builders.PathSelectors +import springfox.documentation.builders.RequestHandlerSelectors +import springfox.documentation.service.* +import springfox.documentation.spi.DocumentationType +import springfox.documentation.spi.service.contexts.SecurityContext +import springfox.documentation.spring.web.plugins.Docket + +@Configuration +class SpringFoxConfig @Autowired constructor( + val buildProperties: BuildProperties, + val appProperties: AppProperties, +) { + companion object { + private const val AUTHORIZATION_KEY: String = "Bearer" + private const val AUTHORIZATION_HEADER: String = "Authorization" + } + + private fun defaultAuth(): List { + val authorizationScope = AuthorizationScope("global", "accessEverything") + val authorizationScopes: Array = arrayOfNulls(1) + authorizationScopes[0] = authorizationScope + return listOf(SecurityReference(AUTHORIZATION_KEY, authorizationScopes)) + } + + private fun securityContext(): SecurityContext? { + return SecurityContext.builder() + .securityReferences(defaultAuth()) + .build() + } + + @Bean + fun api(): Docket { + return Docket(DocumentationType.SWAGGER_2) + .select() + .apis(RequestHandlerSelectors.basePackage(AppConstants.BASE_PACKAGE_API_DOCS)) + .paths(PathSelectors.any()) + .build() + .securityContexts(listOf(securityContext())) + .securitySchemes(listOf(apiKey())) + .apiInfo(apiInfo()) + + // Allow to configurable custom pagination of request in swagger ui // + // .directModelSubstitute(Pageable::class.java, SwaggerPageable::class.java) + // .directModelSubstitute(UrlParamable::class.java, UrlParamableSwaggerView::class.java) + } + + private fun apiInfo(): ApiInfo { + return ApiInfoBuilder() + .description(appProperties.appDescription) + .title(appProperties.appName) + .version(buildProperties.version) + .contact( + Contact( + "CUBETIQ Solution", + "https://cubetiqs.com", + "ops@cubetiqs.com", + ) + ) + .build() + } + + private fun apiKey(): ApiKey { + return ApiKey( + AUTHORIZATION_KEY, + AUTHORIZATION_HEADER, + "header", + ) + } +} diff --git a/api/src/main/kotlin/com/cubetiqs/web/controller/ApiDoc.kt b/api/src/main/kotlin/com/cubetiqs/web/controller/ApiDoc.kt new file mode 100644 index 0000000..8f4456b --- /dev/null +++ b/api/src/main/kotlin/com/cubetiqs/web/controller/ApiDoc.kt @@ -0,0 +1,15 @@ +package com.cubetiqs.web.controller + +import org.springframework.stereotype.Controller +import org.springframework.web.bind.annotation.GetMapping +import org.springframework.web.servlet.view.RedirectView +import springfox.documentation.annotations.ApiIgnore + +@ApiIgnore +@Controller +class ApiDoc { + @GetMapping(value = [ "/api-doc", "/api-docs"]) + fun redirect(): RedirectView { + return RedirectView("/swagger-ui/") + } +} \ No newline at end of file diff --git a/api/src/main/kotlin/com/cubetiqs/web/controller/BaseController.kt b/api/src/main/kotlin/com/cubetiqs/web/controller/BaseController.kt new file mode 100644 index 0000000..8a23dcd --- /dev/null +++ b/api/src/main/kotlin/com/cubetiqs/web/controller/BaseController.kt @@ -0,0 +1,13 @@ +package com.cubetiqs.web.controller + +import org.springframework.http.HttpStatus +import org.springframework.http.ResponseEntity + +interface BaseController { + fun response( + data: Any?, + status: HttpStatus = HttpStatus.OK, + ): ResponseEntity { + return ResponseEntity.status(status).body(data) + } +} \ No newline at end of file diff --git a/api/src/main/kotlin/com/cubetiqs/web/controller/IndexController.kt b/api/src/main/kotlin/com/cubetiqs/web/controller/IndexController.kt new file mode 100644 index 0000000..f0d38e2 --- /dev/null +++ b/api/src/main/kotlin/com/cubetiqs/web/controller/IndexController.kt @@ -0,0 +1,32 @@ +package com.cubetiqs.web.controller + +import com.cubetiqs.web.util.RouteConstants +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.boot.info.BuildProperties +import org.springframework.core.env.Environment +import org.springframework.http.ResponseEntity +import org.springframework.web.bind.annotation.GetMapping +import org.springframework.web.bind.annotation.RequestMapping +import org.springframework.web.bind.annotation.RestController +import springfox.documentation.annotations.ApiIgnore + +@ApiIgnore +@RestController +@RequestMapping(RouteConstants.INDEX) +class IndexController @Autowired constructor( + private val buildProperties: BuildProperties, + private val environment: Environment, +) : BaseController { + @GetMapping + fun index(): ResponseEntity { + val view = mutableMapOf() + view["info"] = "API Operation is running normally on ${environment.activeProfiles.joinToString(separator = ",")}" + view["name"] = buildProperties.name + view["service"] = buildProperties.artifact + view["version"] = buildProperties.version + view["date"] = buildProperties.time + view["commit"] = buildProperties["commitId"] + + return response(view) + } +} \ No newline at end of file diff --git a/api/src/main/kotlin/com/cubetiqs/web/exception/BaseException.kt b/api/src/main/kotlin/com/cubetiqs/web/exception/BaseException.kt new file mode 100644 index 0000000..c0cdcba --- /dev/null +++ b/api/src/main/kotlin/com/cubetiqs/web/exception/BaseException.kt @@ -0,0 +1,14 @@ +package com.cubetiqs.web.exception + +open class BaseException : RuntimeException { + constructor() : super() + constructor(message: String?) : super(message) + constructor(message: String?, cause: Throwable?) : super(message, cause) + constructor(cause: Throwable?) : super(cause) + constructor(message: String?, cause: Throwable?, enableSuppression: Boolean, writableStackTrace: Boolean) : super( + message, + cause, + enableSuppression, + writableStackTrace + ) +} \ No newline at end of file diff --git a/api/src/main/kotlin/com/cubetiqs/web/infrastructure/component/BaseFunctionComponent.kt b/api/src/main/kotlin/com/cubetiqs/web/infrastructure/component/BaseFunctionComponent.kt new file mode 100644 index 0000000..e0e9b9e --- /dev/null +++ b/api/src/main/kotlin/com/cubetiqs/web/infrastructure/component/BaseFunctionComponent.kt @@ -0,0 +1,5 @@ +package com.cubetiqs.web.infrastructure.component + +interface BaseFunctionComponent { + fun execute(input: I?): O? +} \ No newline at end of file diff --git a/api/src/main/kotlin/com/cubetiqs/web/infrastructure/data/BaseEntity.kt b/api/src/main/kotlin/com/cubetiqs/web/infrastructure/data/BaseEntity.kt new file mode 100644 index 0000000..a4444ab --- /dev/null +++ b/api/src/main/kotlin/com/cubetiqs/web/infrastructure/data/BaseEntity.kt @@ -0,0 +1,7 @@ +package com.cubetiqs.web.infrastructure.data + +import java.io.Serializable + +interface BaseEntity : Serializable { + fun setId(id: ID) +} \ No newline at end of file diff --git a/api/src/main/kotlin/com/cubetiqs/web/infrastructure/event/BaseEvent.kt b/api/src/main/kotlin/com/cubetiqs/web/infrastructure/event/BaseEvent.kt new file mode 100644 index 0000000..50dabb0 --- /dev/null +++ b/api/src/main/kotlin/com/cubetiqs/web/infrastructure/event/BaseEvent.kt @@ -0,0 +1,5 @@ +package com.cubetiqs.web.infrastructure.event + +import java.io.Serializable + +interface BaseEvent : Serializable \ No newline at end of file diff --git a/api/src/main/kotlin/com/cubetiqs/web/infrastructure/listener/BaseEventListener.kt b/api/src/main/kotlin/com/cubetiqs/web/infrastructure/listener/BaseEventListener.kt new file mode 100644 index 0000000..bdd6b68 --- /dev/null +++ b/api/src/main/kotlin/com/cubetiqs/web/infrastructure/listener/BaseEventListener.kt @@ -0,0 +1,9 @@ +package com.cubetiqs.web.infrastructure.listener + +import com.cubetiqs.web.infrastructure.event.BaseEvent + +interface BaseEventListener { + fun persist(event: BaseEvent) { + return + } +} \ No newline at end of file diff --git a/api/src/main/kotlin/com/cubetiqs/web/infrastructure/repository/BaseRepository.kt b/api/src/main/kotlin/com/cubetiqs/web/infrastructure/repository/BaseRepository.kt new file mode 100644 index 0000000..f71e5d1 --- /dev/null +++ b/api/src/main/kotlin/com/cubetiqs/web/infrastructure/repository/BaseRepository.kt @@ -0,0 +1,3 @@ +package com.cubetiqs.web.infrastructure.repository + +interface BaseRepository \ No newline at end of file diff --git a/api/src/main/kotlin/com/cubetiqs/web/infrastructure/service/BaseService.kt b/api/src/main/kotlin/com/cubetiqs/web/infrastructure/service/BaseService.kt new file mode 100644 index 0000000..306e40e --- /dev/null +++ b/api/src/main/kotlin/com/cubetiqs/web/infrastructure/service/BaseService.kt @@ -0,0 +1,3 @@ +package com.cubetiqs.web.infrastructure.service + +interface BaseService \ No newline at end of file diff --git a/api/src/main/kotlin/com/cubetiqs/web/model/ob/BaseOb.kt b/api/src/main/kotlin/com/cubetiqs/web/model/ob/BaseOb.kt new file mode 100644 index 0000000..e8cdb0b --- /dev/null +++ b/api/src/main/kotlin/com/cubetiqs/web/model/ob/BaseOb.kt @@ -0,0 +1,5 @@ +package com.cubetiqs.web.model.ob + +import java.io.Serializable + +interface BaseOb : Serializable \ No newline at end of file diff --git a/api/src/main/kotlin/com/cubetiqs/web/model/request/BaseResponseModel.kt b/api/src/main/kotlin/com/cubetiqs/web/model/request/BaseResponseModel.kt new file mode 100644 index 0000000..fc4affc --- /dev/null +++ b/api/src/main/kotlin/com/cubetiqs/web/model/request/BaseResponseModel.kt @@ -0,0 +1,5 @@ +package com.cubetiqs.web.model.request + +import com.cubetiqs.web.model.ob.BaseOb + +interface BaseResponseModel : BaseOb \ No newline at end of file diff --git a/api/src/main/kotlin/com/cubetiqs/web/model/response/BaseRequestModel.kt b/api/src/main/kotlin/com/cubetiqs/web/model/response/BaseRequestModel.kt new file mode 100644 index 0000000..8c8e1b1 --- /dev/null +++ b/api/src/main/kotlin/com/cubetiqs/web/model/response/BaseRequestModel.kt @@ -0,0 +1,5 @@ +package com.cubetiqs.web.model.response + +import com.cubetiqs.web.model.ob.BaseOb + +interface BaseRequestModel : BaseOb \ No newline at end of file diff --git a/api/src/main/kotlin/com/cubetiqs/web/modules/AppModule.kt b/api/src/main/kotlin/com/cubetiqs/web/modules/AppModule.kt new file mode 100644 index 0000000..6acc120 --- /dev/null +++ b/api/src/main/kotlin/com/cubetiqs/web/modules/AppModule.kt @@ -0,0 +1,5 @@ +package com.cubetiqs.web.modules + +interface AppModule { + fun getModule(): Class<*> +} \ No newline at end of file diff --git a/api/src/main/kotlin/com/cubetiqs/web/property/AppProperties.kt b/api/src/main/kotlin/com/cubetiqs/web/property/AppProperties.kt new file mode 100644 index 0000000..b154314 --- /dev/null +++ b/api/src/main/kotlin/com/cubetiqs/web/property/AppProperties.kt @@ -0,0 +1,13 @@ +package com.cubetiqs.web.property + +import com.cubetiqs.web.stereotype.FunctionComponent +import org.springframework.beans.factory.annotation.Value + +@FunctionComponent +class AppProperties ( + @Value("\${cubetiq.app.name:CUBETIQ API Service}") + val appName: String, + + @Value("\${cubetiq.app.description:CUBETIQ APIs Documentation}") + val appDescription: String, +) \ No newline at end of file diff --git a/api/src/main/kotlin/com/cubetiqs/web/stereotype/FunctionComponent.kt b/api/src/main/kotlin/com/cubetiqs/web/stereotype/FunctionComponent.kt new file mode 100644 index 0000000..be835b5 --- /dev/null +++ b/api/src/main/kotlin/com/cubetiqs/web/stereotype/FunctionComponent.kt @@ -0,0 +1,20 @@ +package com.cubetiqs.web.stereotype + +import org.springframework.context.annotation.Lazy +import org.springframework.core.annotation.AliasFor +import org.springframework.stereotype.Component + +/** + * @author sombochea + * @email sombochea@cubetiqs.com + * @date 18/05/21 + * @since 1.0 + */ +@Retention(AnnotationRetention.RUNTIME) +@Target(AnnotationTarget.ANNOTATION_CLASS, AnnotationTarget.CLASS) +@Lazy(value = true) +@Component +annotation class FunctionComponent( + @get: AliasFor(annotation = Component::class) + val value: String = "" +) \ No newline at end of file diff --git a/api/src/main/kotlin/com/cubetiqs/web/util/AppConstants.kt b/api/src/main/kotlin/com/cubetiqs/web/util/AppConstants.kt new file mode 100644 index 0000000..53b3dae --- /dev/null +++ b/api/src/main/kotlin/com/cubetiqs/web/util/AppConstants.kt @@ -0,0 +1,9 @@ +package com.cubetiqs.web.util + +object RouteConstants { + const val INDEX = "/" +} + +object AppConstants { + val BASE_PACKAGE_API_DOCS get() = "com.cubetiqs.web" +} \ No newline at end of file diff --git a/api/src/main/resources/application-dev.yml b/api/src/main/resources/application-dev.yml new file mode 100644 index 0000000..47fbb02 --- /dev/null +++ b/api/src/main/resources/application-dev.yml @@ -0,0 +1,2 @@ +server: + port: 8080 \ No newline at end of file diff --git a/api/src/main/resources/application.yml b/api/src/main/resources/application.yml new file mode 100644 index 0000000..76cdbcd --- /dev/null +++ b/api/src/main/resources/application.yml @@ -0,0 +1,16 @@ +spring: + profiles: + active: ${APP_PROFILE:dev} + application: + name: cubetiq-api-service + +cubetiq: + app: + data-dir: ${APP_DATA_DIR:${user.home}/${spring.application.name}} + name: CUBETIQ API Service + description: CUBETIQ API's Documentation + +logging: + file: + path: ${LOGGING_FILE_PATH:${cubetiq.app.data-dir}/logs/} + name: ${logging.file.path}/app.log \ No newline at end of file diff --git a/api/src/test/kotlin/com/cubetiqs/web/ApiApplicationTests.kt b/api/src/test/kotlin/com/cubetiqs/web/ApiApplicationTests.kt new file mode 100644 index 0000000..8a6e2db --- /dev/null +++ b/api/src/test/kotlin/com/cubetiqs/web/ApiApplicationTests.kt @@ -0,0 +1,13 @@ +package com.cubetiqs.web + +import org.junit.jupiter.api.Test +import org.springframework.boot.test.context.SpringBootTest + +@SpringBootTest +class ApiApplicationTests { + + @Test + fun contextLoads() { + } + +} diff --git a/settings.gradle.kts b/settings.gradle.kts index 061d949..3582e66 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -1 +1,5 @@ -rootProject.name = "spring-kotlin-gradle-modules" \ No newline at end of file +rootProject.name = "spring-web-modules" + +include( + "api" +) \ No newline at end of file