Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Feat] Api docs v2 #125 #136

Merged
merged 6 commits into from
Feb 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
76 changes: 15 additions & 61 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,17 @@ buildscript {
}

plugins {
id 'org.springframework.boot' version '2.5.5'
id 'org.springframework.boot' version '2.7.18'
id 'io.spring.dependency-management' version '1.0.11.RELEASE'
id 'com.ewerk.gradle.plugins.querydsl' version "1.0.10"
id 'java'
id 'org.asciidoctor.jvm.convert' version "3.3.2"
id 'org.flywaydb.flyway' version '9.16.1' // flyway gradle plugin 의존성
id 'org.sonarqube' version '3.5.0.2730' // sonarqube gradle plugin 의존성
id 'jacoco' // jacoco gradle plugin 의존성
}

group = 'com.kustacks'
version = '1.1.2'
version = '2.5.0'
sourceCompatibility = '17'

configurations {
Expand All @@ -41,12 +40,9 @@ sonarqube {
dependencies {
// Web
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
implementation 'org.springframework.boot:spring-boot-starter-websocket'
implementation 'org.springframework.boot:spring-boot-starter-validation'
implementation 'org.springframework.retry:spring-retry'
implementation 'org.springframework:spring-aspects'
implementation 'org.springframework.session:spring-session:1.3.5.RELEASE'
annotationProcessor 'org.springframework.boot:spring-boot-configuration-processor'

// DB
Expand All @@ -69,7 +65,7 @@ dependencies {
implementation 'io.micrometer:micrometer-registry-prometheus'

// flyway
implementation 'org.flywaydb:flyway-core'
implementation 'org.flywaydb:flyway-mysql'

// DevTool
compileOnly 'org.projectlombok:lombok'
Expand All @@ -85,10 +81,13 @@ dependencies {
// Firebase
implementation 'com.google.firebase:firebase-admin:8.1.0'

// API Docs
// RestDocs
asciidoctorExtensions 'org.springframework.restdocs:spring-restdocs-asciidoctor'
testImplementation 'org.springframework.restdocs:spring-restdocs-mockmvc'

// Swagger
implementation 'org.springdoc:springdoc-openapi-ui:1.6.11'

// Test
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testImplementation 'io.rest-assured:rest-assured:4.2.0'
Expand All @@ -98,20 +97,14 @@ dependencies {

// Test Container
testImplementation 'org.junit.jupiter:junit-jupiter:5.8.1'
testImplementation 'org.testcontainers:testcontainers:1.17.6'
testImplementation 'org.testcontainers:junit-jupiter:1.17.6'
testImplementation 'org.testcontainers:mariadb:1.17.6'
}

ext {
snippetsDir = file 'build/generated-snippets'
testImplementation 'org.testcontainers:testcontainers:1.19.3'
testImplementation 'org.testcontainers:junit-jupiter:1.19.3'
testImplementation 'org.testcontainers:mariadb:1.19.3'
}

test.onlyIf { System.getenv('DEPLOY_ENV') == 'dev' }

test {
outputs.dir snippetsDir

jacoco {
destinationFile = file("$buildDir/jacoco/jacoco.exec")
}
Expand All @@ -129,50 +122,6 @@ test {
finalizedBy 'jacocoTestReport'
}

//Querydsl 추가, 자동 생성된 Q클래스 gradle clean으로 제거
clean {
delete file('src/main/generated')
}

asciidoctor.onlyIf { System.getenv('DEPLOY_ENV') == 'dev' }

asciidoctor {
inputs.dir snippetsDir
dependsOn test
attributes "snippets": snippetsDir,
"version": version,
"stylesheet": "asciitheme/clean.css"

doFirst {
println "=====Start asciidoctor"
//asciidoctor 실행전 기존에 생성된 API 문서 삭제
delete file('src/main/resources/static/docs/api-docs.html')
}

doLast {
println "=====Finish asciidoctor"
}
}

task copyDocument(type: Copy) {
dependsOn asciidoctor
from file("build/asciidoc/html5")
// resources/static/docs 로 복사하여 서버가 돌아가고 있을때 /docs/index.html 로 접속하면 볼수 있음
into file("src/main/resources/static/docs")
}

build {
dependsOn copyDocument
}

bootJar {
enabled = true
dependsOn asciidoctor
from("${asciidoctor.outputDir}/html5") {
into "static/docs"
}
}

jar {
enabled = false
manifest {
Expand All @@ -199,6 +148,11 @@ compileQuerydsl.doFirst {
if(file(querydslDir).exists())
delete(file(querydslDir))
}

//Querydsl 추가, 자동 생성된 Q클래스 gradle clean으로 제거
clean {
delete file('src/main/generated')
}
// -- Jacoco 설정 -------------------------------------------------------
jacoco {
// jacoco version
Expand Down
Original file line number Diff line number Diff line change
@@ -1,23 +1,32 @@
package com.kustacks.kuring.admin.adapter.in.web;

import com.kustacks.kuring.admin.application.port.in.dto.RealNotificationCommand;
import static com.kustacks.kuring.common.dto.ResponseCodeAndMessages.ADMIN_REAL_NOTICE_CREATE_SUCCESS;
import static com.kustacks.kuring.common.dto.ResponseCodeAndMessages.ADMIN_TEST_NOTICE_CREATE_SUCCESS;

import com.kustacks.kuring.admin.adapter.in.web.dto.RealNotificationRequest;
import com.kustacks.kuring.admin.adapter.in.web.dto.TestNotificationRequest;
import com.kustacks.kuring.admin.application.port.in.AdminCommandUseCase;
import com.kustacks.kuring.admin.application.port.in.dto.RealNotificationCommand;
import com.kustacks.kuring.admin.domain.AdminRole;
import com.kustacks.kuring.auth.authorization.AuthenticationPrincipal;
import com.kustacks.kuring.auth.context.Authentication;
import com.kustacks.kuring.auth.secured.Secured;
import com.kustacks.kuring.common.dto.BaseResponse;
import io.swagger.v3.oas.annotations.Hidden;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.security.SecurityRequirement;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;

import static com.kustacks.kuring.common.dto.ResponseCodeAndMessages.ADMIN_REAL_NOTICE_CREATE_SUCCESS;
import static com.kustacks.kuring.common.dto.ResponseCodeAndMessages.ADMIN_TEST_NOTICE_CREATE_SUCCESS;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@Tag(name = "Admin-Command", description = "관리자가 주체가 되는 정보 수정")
@Validated
@RestController
@RequiredArgsConstructor
Expand All @@ -26,6 +35,8 @@ public class AdminCommandApiV2 {

private final AdminCommandUseCase adminCommandUseCase;

@Operation(summary = "테스트 공지 전송", description = "테스트 공지를 전송합니다, 실제 운영시 사용하지 않습니다")
@SecurityRequirement(name = "JWT")
@Secured(AdminRole.ROLE_ROOT)
@PostMapping("/notices/dev")
public ResponseEntity<BaseResponse<String>> createTestNotice(
Expand All @@ -35,6 +46,8 @@ public ResponseEntity<BaseResponse<String>> createTestNotice(
return ResponseEntity.ok().body(new BaseResponse<>(ADMIN_TEST_NOTICE_CREATE_SUCCESS, null));
}

@Operation(summary = "전체 공지 전송", description = "전체 공지를 전송합니다, 실제 운영시 사용합니다")
@SecurityRequirement(name = "JWT")
@Secured(AdminRole.ROLE_ROOT)
@PostMapping("/notices/prod")
public ResponseEntity<BaseResponse<String>> createRealNotice(
Expand All @@ -46,6 +59,7 @@ public ResponseEntity<BaseResponse<String>> createRealNotice(
return ResponseEntity.ok().body(new BaseResponse<>(ADMIN_REAL_NOTICE_CREATE_SUCCESS, null));
}

@Hidden
@Secured(AdminRole.ROLE_ROOT)
@GetMapping("/subscribe/all")
public ResponseEntity<Void> subscribe() {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,23 @@
package com.kustacks.kuring.admin.adapter.in.web;

import static com.kustacks.kuring.common.dto.ResponseCodeAndMessages.AUTH_AUTHENTICATION_SUCCESS;
import static com.kustacks.kuring.common.dto.ResponseCodeAndMessages.FEEDBACK_SEARCH_SUCCESS;

import com.kustacks.kuring.admin.application.port.in.AdminQueryUseCase;
import com.kustacks.kuring.admin.domain.AdminRole;
import com.kustacks.kuring.auth.authorization.AuthenticationPrincipal;
import com.kustacks.kuring.auth.context.Authentication;
import com.kustacks.kuring.auth.secured.Secured;
import com.kustacks.kuring.common.dto.BaseResponse;
import com.kustacks.kuring.user.application.port.in.dto.AdminFeedbacksResult;
import io.swagger.v3.oas.annotations.Hidden;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.security.SecurityRequirement;
import io.swagger.v3.oas.annotations.tags.Tag;
import java.util.List;
import javax.validation.constraints.Max;
import javax.validation.constraints.Min;
import lombok.RequiredArgsConstructor;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
Expand All @@ -16,13 +27,7 @@
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import javax.validation.constraints.Max;
import javax.validation.constraints.Min;
import java.util.List;

import static com.kustacks.kuring.common.dto.ResponseCodeAndMessages.AUTH_AUTHENTICATION_SUCCESS;
import static com.kustacks.kuring.common.dto.ResponseCodeAndMessages.FEEDBACK_SEARCH_SUCCESS;

@Tag(name = "Admin-Query", description = "관리자가 주체가 되는 정보 조회")
@Validated
@RestController
@RequiredArgsConstructor
Expand All @@ -31,33 +36,44 @@ public class AdminQueryApiV2 {

private final AdminQueryUseCase adminQueryUseCase;

@Operation(summary = "피드백 조회", description = "사용자의 모든 피드백을 조회합니다")
@SecurityRequirement(name = "JWT")
@Secured(AdminRole.ROLE_ROOT)
@GetMapping("/feedbacks")
public ResponseEntity<BaseResponse<List<AdminFeedbacksResult>>> getFeedbacks(
@RequestParam(name = "page") @Min(0) int page,
@RequestParam(name = "size") @Min(1) @Max(30) int size)
{
@Parameter(description = "페이지") @RequestParam(name = "page") @Min(0) int page,
@Parameter(description = "단일 페이지의 사이즈, 1 ~ 30까지 허용") @RequestParam(name = "size") @Min(1) @Max(30) int size) {
List<AdminFeedbacksResult> feedbacks = adminQueryUseCase.lookupFeedbacks(page, size);
return ResponseEntity.ok().body(new BaseResponse<>(FEEDBACK_SEARCH_SUCCESS, feedbacks));
}

/**
* Root 이상만 호출 가능한 테스트 API
*
* @return OK
*/
@Hidden
@Secured(AdminRole.ROLE_ROOT)
@GetMapping("/root")
public ResponseEntity<BaseResponse<List<String>>> roleAdminRoot(@AuthenticationPrincipal Authentication authentication) {
return ResponseEntity.ok().body(new BaseResponse<>(AUTH_AUTHENTICATION_SUCCESS, authentication.getAuthorities()));
public ResponseEntity<BaseResponse<List<String>>> roleAdminRoot(
@AuthenticationPrincipal Authentication authentication
) {
return ResponseEntity.ok()
.body(new BaseResponse<>(AUTH_AUTHENTICATION_SUCCESS, authentication.getAuthorities()));
}

/**
* Client 이상만 호출 가능한 테스트 API
*
* @return OK
*/
@Hidden
@Secured(AdminRole.ROLE_CLIENT)
@GetMapping("/client")
public ResponseEntity<BaseResponse<List<String>>> roleAdminClient(@AuthenticationPrincipal Authentication authentication) {
return ResponseEntity.ok().body(new BaseResponse<>(AUTH_AUTHENTICATION_SUCCESS, authentication.getAuthorities()));
public ResponseEntity<BaseResponse<List<String>>> roleAdminClient(
@AuthenticationPrincipal Authentication authentication
) {
return ResponseEntity.ok()
.body(new BaseResponse<>(AUTH_AUTHENTICATION_SUCCESS, authentication.getAuthorities()));
}
}
52 changes: 52 additions & 0 deletions src/main/java/com/kustacks/kuring/config/SwaggerConfiguration.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package com.kustacks.kuring.config;

import io.swagger.v3.oas.models.Components;
import io.swagger.v3.oas.models.OpenAPI;
import io.swagger.v3.oas.models.info.Contact;
import io.swagger.v3.oas.models.info.Info;
import io.swagger.v3.oas.models.security.SecurityScheme;
import io.swagger.v3.oas.models.security.SecurityScheme.In;
import io.swagger.v3.oas.models.security.SecurityScheme.Type;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpHeaders;

@Configuration
public class SwaggerConfiguration {

private static final String FIREBASE_HEADER = "User-Token";

@Bean
public OpenAPI customOpenAPI() {
return new OpenAPI()
.components(new Components()
.addSecuritySchemes("JWT", JwtAuth())
.addSecuritySchemes("User-Token", firebaseAuth())
).info(getInfo());
}

private static Info getInfo() {
return new Info()
.title("Kuring Backend API Docs")
.description("Kuring의 Swagger API 문서입니다.")
.version("2.5.0")
.contact(new Contact().name("Kuring Contact")
.url("https://kuring.notion.site/kuring/a69fdf7ff06848c2aedef1fdcf13ca57"));
}

private static SecurityScheme firebaseAuth() {
return new SecurityScheme()
.type(Type.APIKEY)
.in(In.HEADER)
.name(FIREBASE_HEADER);
}

private static SecurityScheme JwtAuth() {
return new SecurityScheme()
.type(Type.HTTP)
.scheme("bearer")
.bearerFormat("JWT")
.in(In.HEADER)
.name(HttpHeaders.AUTHORIZATION);
}
}
Loading
Loading