Merge pull request 'docs: Swagger/OpenAPI 문서 전체 구현' (#6) from feature/admin-and-api into develop
Reviewed-on: #6
This commit is contained in:
커밋
1955165985
7
pom.xml
7
pom.xml
@ -87,6 +87,13 @@
|
|||||||
<scope>runtime</scope>
|
<scope>runtime</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
<!-- SpringDoc OpenAPI (Swagger UI) -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springdoc</groupId>
|
||||||
|
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
|
||||||
|
<version>2.8.6</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
<!-- H2 (로컬 개발용) -->
|
<!-- H2 (로컬 개발용) -->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.h2database</groupId>
|
<groupId>com.h2database</groupId>
|
||||||
|
|||||||
@ -7,6 +7,13 @@ import com.gcsc.guide.entity.User;
|
|||||||
import com.gcsc.guide.repository.UserRepository;
|
import com.gcsc.guide.repository.UserRepository;
|
||||||
import com.gcsc.guide.service.ActivityService;
|
import com.gcsc.guide.service.ActivityService;
|
||||||
import com.google.api.client.googleapis.auth.oauth2.GoogleIdToken;
|
import com.google.api.client.googleapis.auth.oauth2.GoogleIdToken;
|
||||||
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
|
import io.swagger.v3.oas.annotations.media.Content;
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import io.swagger.v3.oas.annotations.responses.ApiResponse;
|
||||||
|
import io.swagger.v3.oas.annotations.responses.ApiResponses;
|
||||||
|
import io.swagger.v3.oas.annotations.security.SecurityRequirement;
|
||||||
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
import jakarta.servlet.http.HttpServletRequest;
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
import jakarta.validation.Valid;
|
import jakarta.validation.Valid;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
@ -21,6 +28,7 @@ import org.springframework.web.server.ResponseStatusException;
|
|||||||
@RestController
|
@RestController
|
||||||
@RequestMapping("/api/auth")
|
@RequestMapping("/api/auth")
|
||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
|
@Tag(name = "01. 인증", description = "Google OAuth2 로그인 및 JWT 토큰 관리")
|
||||||
public class AuthController {
|
public class AuthController {
|
||||||
|
|
||||||
private static final String AUTO_ADMIN_EMAIL = "htlee@gcsc.co.kr";
|
private static final String AUTO_ADMIN_EMAIL = "htlee@gcsc.co.kr";
|
||||||
@ -30,9 +38,16 @@ public class AuthController {
|
|||||||
private final UserRepository userRepository;
|
private final UserRepository userRepository;
|
||||||
private final ActivityService activityService;
|
private final ActivityService activityService;
|
||||||
|
|
||||||
/**
|
@Operation(summary = "Google 로그인",
|
||||||
* Google ID Token으로 로그인/회원가입 처리 후 JWT 발급
|
description = "Google ID Token을 검증하고 JWT를 발급합니다. "
|
||||||
*/
|
+ "신규 사용자는 PENDING 상태로 생성되며, htlee@gcsc.co.kr은 자동 ACTIVE + 관리자 부여됩니다.",
|
||||||
|
security = {})
|
||||||
|
@ApiResponses({
|
||||||
|
@ApiResponse(responseCode = "200", description = "로그인 성공, JWT 토큰 발급",
|
||||||
|
content = @Content(schema = @Schema(implementation = AuthResponse.class))),
|
||||||
|
@ApiResponse(responseCode = "401", description = "유효하지 않은 Google 토큰 또는 허용되지 않은 이메일 도메인",
|
||||||
|
content = @Content)
|
||||||
|
})
|
||||||
@PostMapping("/google")
|
@PostMapping("/google")
|
||||||
public ResponseEntity<AuthResponse> googleLogin(
|
public ResponseEntity<AuthResponse> googleLogin(
|
||||||
@Valid @RequestBody GoogleLoginRequest request,
|
@Valid @RequestBody GoogleLoginRequest request,
|
||||||
@ -70,9 +85,14 @@ public class AuthController {
|
|||||||
return ResponseEntity.ok(new AuthResponse(token, UserResponse.from(userWithRoles)));
|
return ResponseEntity.ok(new AuthResponse(token, UserResponse.from(userWithRoles)));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
@Operation(summary = "현재 사용자 정보 조회",
|
||||||
* 현재 인증된 사용자 정보 조회
|
description = "JWT 토큰으로 인증된 현재 사용자의 상세 정보와 롤 목록을 반환합니다.")
|
||||||
*/
|
@ApiResponses({
|
||||||
|
@ApiResponse(responseCode = "200", description = "사용자 정보 조회 성공"),
|
||||||
|
@ApiResponse(responseCode = "401", description = "인증 실패 (토큰 없음/만료)", content = @Content),
|
||||||
|
@ApiResponse(responseCode = "404", description = "사용자를 찾을 수 없음", content = @Content)
|
||||||
|
})
|
||||||
|
@SecurityRequirement(name = "Bearer JWT")
|
||||||
@GetMapping("/me")
|
@GetMapping("/me")
|
||||||
public ResponseEntity<UserResponse> getCurrentUser(Authentication authentication) {
|
public ResponseEntity<UserResponse> getCurrentUser(Authentication authentication) {
|
||||||
Long userId = (Long) authentication.getPrincipal();
|
Long userId = (Long) authentication.getPrincipal();
|
||||||
@ -83,9 +103,10 @@ public class AuthController {
|
|||||||
return ResponseEntity.ok(UserResponse.from(user));
|
return ResponseEntity.ok(UserResponse.from(user));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
@Operation(summary = "로그아웃",
|
||||||
* 로그아웃 (Stateless JWT이므로 서버 측 처리 없음, 프론트에서 토큰 삭제)
|
description = "Stateless JWT 방식이므로 서버 측 처리 없이 204를 반환합니다. 클라이언트에서 토큰을 삭제하세요.")
|
||||||
*/
|
@ApiResponse(responseCode = "204", description = "로그아웃 성공")
|
||||||
|
@SecurityRequirement(name = "Bearer JWT")
|
||||||
@PostMapping("/logout")
|
@PostMapping("/logout")
|
||||||
public ResponseEntity<Void> logout() {
|
public ResponseEntity<Void> logout() {
|
||||||
return ResponseEntity.noContent().build();
|
return ResponseEntity.noContent().build();
|
||||||
|
|||||||
54
src/main/java/com/gcsc/guide/config/OpenApiConfig.java
Normal file
54
src/main/java/com/gcsc/guide/config/OpenApiConfig.java
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
package com.gcsc.guide.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.SecurityRequirement;
|
||||||
|
import io.swagger.v3.oas.models.security.SecurityScheme;
|
||||||
|
import io.swagger.v3.oas.models.servers.Server;
|
||||||
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
public class OpenApiConfig {
|
||||||
|
|
||||||
|
private static final String SECURITY_SCHEME_NAME = "Bearer JWT";
|
||||||
|
|
||||||
|
@Value("${server.port:8080}")
|
||||||
|
private int serverPort;
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public OpenAPI openAPI() {
|
||||||
|
return new OpenAPI()
|
||||||
|
.info(new Info()
|
||||||
|
.title("GC Guide API")
|
||||||
|
.description("GC SI 개발자 가이드 사이트 백엔드 API.\n\n"
|
||||||
|
+ "### 인증 방식\n"
|
||||||
|
+ "1. `POST /api/auth/google`에 Google ID Token을 전송하여 JWT를 발급받습니다.\n"
|
||||||
|
+ "2. 발급받은 JWT를 `Authorization: Bearer {token}` 헤더에 포함하여 요청합니다.\n\n"
|
||||||
|
+ "### 권한 구분\n"
|
||||||
|
+ "- **Public**: 인증 없이 접근 가능\n"
|
||||||
|
+ "- **Authenticated**: 로그인 필요 (ACTIVE 상태)\n"
|
||||||
|
+ "- **Admin**: 관리자 권한 필요 (isAdmin=true)")
|
||||||
|
.version("1.0.0")
|
||||||
|
.contact(new Contact()
|
||||||
|
.name("GC SI Dev Team")
|
||||||
|
.email("htlee@gcsc.co.kr")))
|
||||||
|
.servers(List.of(
|
||||||
|
new Server().url("https://guide.gc-si.dev").description("Production"),
|
||||||
|
new Server().url("http://localhost:" + serverPort).description("Local")))
|
||||||
|
.addSecurityItem(new SecurityRequirement().addList(SECURITY_SCHEME_NAME))
|
||||||
|
.components(new Components()
|
||||||
|
.addSecuritySchemes(SECURITY_SCHEME_NAME,
|
||||||
|
new SecurityScheme()
|
||||||
|
.name(SECURITY_SCHEME_NAME)
|
||||||
|
.type(SecurityScheme.Type.HTTP)
|
||||||
|
.scheme("bearer")
|
||||||
|
.bearerFormat("JWT")
|
||||||
|
.description("Google 로그인 후 발급받은 JWT 토큰")));
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -38,7 +38,10 @@ public class SecurityConfig {
|
|||||||
"/api/auth/**",
|
"/api/auth/**",
|
||||||
"/api/health",
|
"/api/health",
|
||||||
"/actuator/health",
|
"/actuator/health",
|
||||||
"/h2-console/**"
|
"/h2-console/**",
|
||||||
|
"/swagger-ui/**",
|
||||||
|
"/swagger-ui.html",
|
||||||
|
"/v3/api-docs/**"
|
||||||
).permitAll()
|
).permitAll()
|
||||||
.requestMatchers("/api/admin/**").authenticated()
|
.requestMatchers("/api/admin/**").authenticated()
|
||||||
.anyRequest().authenticated()
|
.anyRequest().authenticated()
|
||||||
|
|||||||
@ -3,6 +3,14 @@ package com.gcsc.guide.controller;
|
|||||||
import com.gcsc.guide.dto.LoginHistoryResponse;
|
import com.gcsc.guide.dto.LoginHistoryResponse;
|
||||||
import com.gcsc.guide.dto.TrackPageViewRequest;
|
import com.gcsc.guide.dto.TrackPageViewRequest;
|
||||||
import com.gcsc.guide.service.ActivityService;
|
import com.gcsc.guide.service.ActivityService;
|
||||||
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
|
import io.swagger.v3.oas.annotations.media.ArraySchema;
|
||||||
|
import io.swagger.v3.oas.annotations.media.Content;
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import io.swagger.v3.oas.annotations.responses.ApiResponse;
|
||||||
|
import io.swagger.v3.oas.annotations.responses.ApiResponses;
|
||||||
|
import io.swagger.v3.oas.annotations.security.SecurityRequirement;
|
||||||
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
import jakarta.validation.Valid;
|
import jakarta.validation.Valid;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
@ -11,17 +19,21 @@ import org.springframework.web.bind.annotation.*;
|
|||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
|
||||||
* 활동 기록 API
|
|
||||||
*/
|
|
||||||
@RestController
|
@RestController
|
||||||
@RequestMapping("/api/activity")
|
@RequestMapping("/api/activity")
|
||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
|
@Tag(name = "05. 활동 기록", description = "페이지 뷰 추적 및 로그인 이력 조회")
|
||||||
|
@SecurityRequirement(name = "Bearer JWT")
|
||||||
public class ActivityController {
|
public class ActivityController {
|
||||||
|
|
||||||
private final ActivityService activityService;
|
private final ActivityService activityService;
|
||||||
|
|
||||||
/** 페이지 뷰 기록 */
|
@Operation(summary = "페이지 뷰 기록",
|
||||||
|
description = "현재 사용자가 특정 페이지를 조회했음을 기록합니다. 프론트엔드에서 페이지 이동 시 호출합니다.")
|
||||||
|
@ApiResponses({
|
||||||
|
@ApiResponse(responseCode = "200", description = "기록 성공"),
|
||||||
|
@ApiResponse(responseCode = "401", description = "인증 실패", content = @Content)
|
||||||
|
})
|
||||||
@PostMapping("/track")
|
@PostMapping("/track")
|
||||||
public ResponseEntity<Void> trackPageView(
|
public ResponseEntity<Void> trackPageView(
|
||||||
Authentication authentication,
|
Authentication authentication,
|
||||||
@ -31,7 +43,13 @@ public class ActivityController {
|
|||||||
return ResponseEntity.ok().build();
|
return ResponseEntity.ok().build();
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 현재 사용자의 로그인 이력 조회 */
|
@Operation(summary = "현재 사용자의 로그인 이력 조회",
|
||||||
|
description = "JWT로 인증된 현재 사용자의 최근 로그인 이력(IP, User-Agent, 시간)을 반환합니다.")
|
||||||
|
@ApiResponses({
|
||||||
|
@ApiResponse(responseCode = "200", description = "조회 성공",
|
||||||
|
content = @Content(array = @ArraySchema(schema = @Schema(implementation = LoginHistoryResponse.class)))),
|
||||||
|
@ApiResponse(responseCode = "401", description = "인증 실패", content = @Content)
|
||||||
|
})
|
||||||
@GetMapping("/login-history")
|
@GetMapping("/login-history")
|
||||||
public ResponseEntity<List<LoginHistoryResponse>> getLoginHistory(Authentication authentication) {
|
public ResponseEntity<List<LoginHistoryResponse>> getLoginHistory(Authentication authentication) {
|
||||||
Long userId = (Long) authentication.getPrincipal();
|
Long userId = (Long) authentication.getPrincipal();
|
||||||
|
|||||||
@ -4,6 +4,15 @@ import com.gcsc.guide.dto.AddPermissionRequest;
|
|||||||
import com.gcsc.guide.dto.CreateRoleRequest;
|
import com.gcsc.guide.dto.CreateRoleRequest;
|
||||||
import com.gcsc.guide.dto.RoleResponse;
|
import com.gcsc.guide.dto.RoleResponse;
|
||||||
import com.gcsc.guide.service.RoleService;
|
import com.gcsc.guide.service.RoleService;
|
||||||
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
|
import io.swagger.v3.oas.annotations.Parameter;
|
||||||
|
import io.swagger.v3.oas.annotations.media.ArraySchema;
|
||||||
|
import io.swagger.v3.oas.annotations.media.Content;
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import io.swagger.v3.oas.annotations.responses.ApiResponse;
|
||||||
|
import io.swagger.v3.oas.annotations.responses.ApiResponses;
|
||||||
|
import io.swagger.v3.oas.annotations.security.SecurityRequirement;
|
||||||
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
import jakarta.validation.Valid;
|
import jakarta.validation.Valid;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
@ -12,61 +21,115 @@ import org.springframework.web.bind.annotation.*;
|
|||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
|
||||||
* 관리자 롤/권한 관리 API
|
|
||||||
*/
|
|
||||||
@RestController
|
@RestController
|
||||||
@RequestMapping("/api/admin/roles")
|
@RequestMapping("/api/admin/roles")
|
||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
|
@Tag(name = "03. 관리자 - 롤/권한", description = "롤 CRUD 및 URL 패턴 기반 권한 관리")
|
||||||
|
@SecurityRequirement(name = "Bearer JWT")
|
||||||
public class AdminRoleController {
|
public class AdminRoleController {
|
||||||
|
|
||||||
private final RoleService roleService;
|
private final RoleService roleService;
|
||||||
|
|
||||||
/** 전체 롤 목록 */
|
@Operation(summary = "전체 롤 목록 조회",
|
||||||
|
description = "등록된 모든 롤과 각 롤에 할당된 URL 패턴을 조회합니다.")
|
||||||
|
@ApiResponses({
|
||||||
|
@ApiResponse(responseCode = "200", description = "조회 성공",
|
||||||
|
content = @Content(array = @ArraySchema(schema = @Schema(implementation = RoleResponse.class)))),
|
||||||
|
@ApiResponse(responseCode = "401", description = "인증 실패", content = @Content),
|
||||||
|
@ApiResponse(responseCode = "403", description = "관리자 권한 필요", content = @Content)
|
||||||
|
})
|
||||||
@GetMapping
|
@GetMapping
|
||||||
public ResponseEntity<List<RoleResponse>> getRoles() {
|
public ResponseEntity<List<RoleResponse>> getRoles() {
|
||||||
return ResponseEntity.ok(roleService.getRoles());
|
return ResponseEntity.ok(roleService.getRoles());
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 롤 생성 */
|
@Operation(summary = "롤 생성",
|
||||||
|
description = "새로운 롤을 생성합니다. 생성 후 URL 패턴을 별도로 추가해야 합니다.")
|
||||||
|
@ApiResponses({
|
||||||
|
@ApiResponse(responseCode = "201", description = "롤 생성 성공",
|
||||||
|
content = @Content(schema = @Schema(implementation = RoleResponse.class))),
|
||||||
|
@ApiResponse(responseCode = "401", description = "인증 실패", content = @Content),
|
||||||
|
@ApiResponse(responseCode = "403", description = "관리자 권한 필요", content = @Content)
|
||||||
|
})
|
||||||
@PostMapping
|
@PostMapping
|
||||||
public ResponseEntity<RoleResponse> createRole(@Valid @RequestBody CreateRoleRequest request) {
|
public ResponseEntity<RoleResponse> createRole(@Valid @RequestBody CreateRoleRequest request) {
|
||||||
RoleResponse role = roleService.createRole(request.name(), request.description());
|
RoleResponse role = roleService.createRole(request.name(), request.description());
|
||||||
return ResponseEntity.created(URI.create("/api/admin/roles/" + role.id())).body(role);
|
return ResponseEntity.created(URI.create("/api/admin/roles/" + role.id())).body(role);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 롤 수정 */
|
@Operation(summary = "롤 수정",
|
||||||
|
description = "기존 롤의 이름과 설명을 수정합니다.")
|
||||||
|
@ApiResponses({
|
||||||
|
@ApiResponse(responseCode = "200", description = "롤 수정 성공",
|
||||||
|
content = @Content(schema = @Schema(implementation = RoleResponse.class))),
|
||||||
|
@ApiResponse(responseCode = "401", description = "인증 실패", content = @Content),
|
||||||
|
@ApiResponse(responseCode = "403", description = "관리자 권한 필요", content = @Content),
|
||||||
|
@ApiResponse(responseCode = "404", description = "롤을 찾을 수 없음", content = @Content)
|
||||||
|
})
|
||||||
@PutMapping("/{id}")
|
@PutMapping("/{id}")
|
||||||
public ResponseEntity<RoleResponse> updateRole(
|
public ResponseEntity<RoleResponse> updateRole(
|
||||||
@PathVariable Long id,
|
@Parameter(description = "롤 ID", required = true) @PathVariable Long id,
|
||||||
@Valid @RequestBody CreateRoleRequest request) {
|
@Valid @RequestBody CreateRoleRequest request) {
|
||||||
return ResponseEntity.ok(roleService.updateRole(id, request.name(), request.description()));
|
return ResponseEntity.ok(roleService.updateRole(id, request.name(), request.description()));
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 롤 삭제 */
|
@Operation(summary = "롤 삭제",
|
||||||
|
description = "롤을 삭제합니다. 해당 롤에 연결된 URL 패턴도 함께 삭제됩니다.")
|
||||||
|
@ApiResponses({
|
||||||
|
@ApiResponse(responseCode = "204", description = "롤 삭제 성공"),
|
||||||
|
@ApiResponse(responseCode = "401", description = "인증 실패", content = @Content),
|
||||||
|
@ApiResponse(responseCode = "403", description = "관리자 권한 필요", content = @Content),
|
||||||
|
@ApiResponse(responseCode = "404", description = "롤을 찾을 수 없음", content = @Content)
|
||||||
|
})
|
||||||
@DeleteMapping("/{id}")
|
@DeleteMapping("/{id}")
|
||||||
public ResponseEntity<Void> deleteRole(@PathVariable Long id) {
|
public ResponseEntity<Void> deleteRole(
|
||||||
|
@Parameter(description = "롤 ID", required = true) @PathVariable Long id) {
|
||||||
roleService.deleteRole(id);
|
roleService.deleteRole(id);
|
||||||
return ResponseEntity.noContent().build();
|
return ResponseEntity.noContent().build();
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 롤의 URL 패턴 목록 */
|
@Operation(summary = "롤의 URL 패턴 목록 조회",
|
||||||
|
description = "특정 롤에 할당된 URL 패턴(권한) 목록을 조회합니다.")
|
||||||
|
@ApiResponses({
|
||||||
|
@ApiResponse(responseCode = "200", description = "조회 성공",
|
||||||
|
content = @Content(array = @ArraySchema(schema = @Schema(implementation = String.class)))),
|
||||||
|
@ApiResponse(responseCode = "401", description = "인증 실패", content = @Content),
|
||||||
|
@ApiResponse(responseCode = "403", description = "관리자 권한 필요", content = @Content),
|
||||||
|
@ApiResponse(responseCode = "404", description = "롤을 찾을 수 없음", content = @Content)
|
||||||
|
})
|
||||||
@GetMapping("/{id}/permissions")
|
@GetMapping("/{id}/permissions")
|
||||||
public ResponseEntity<List<String>> getPermissions(@PathVariable Long id) {
|
public ResponseEntity<List<String>> getPermissions(
|
||||||
|
@Parameter(description = "롤 ID", required = true) @PathVariable Long id) {
|
||||||
return ResponseEntity.ok(roleService.getPermissions(id));
|
return ResponseEntity.ok(roleService.getPermissions(id));
|
||||||
}
|
}
|
||||||
|
|
||||||
/** URL 패턴 추가 */
|
@Operation(summary = "URL 패턴 추가",
|
||||||
|
description = "롤에 새로운 URL 패턴(권한)을 추가합니다. Ant-style 패턴을 지원합니다 (예: /dev/**, /dev/front/**).")
|
||||||
|
@ApiResponses({
|
||||||
|
@ApiResponse(responseCode = "200", description = "URL 패턴 추가 성공",
|
||||||
|
content = @Content(schema = @Schema(implementation = RoleResponse.class))),
|
||||||
|
@ApiResponse(responseCode = "401", description = "인증 실패", content = @Content),
|
||||||
|
@ApiResponse(responseCode = "403", description = "관리자 권한 필요", content = @Content),
|
||||||
|
@ApiResponse(responseCode = "404", description = "롤을 찾을 수 없음", content = @Content)
|
||||||
|
})
|
||||||
@PostMapping("/{id}/permissions")
|
@PostMapping("/{id}/permissions")
|
||||||
public ResponseEntity<RoleResponse> addPermission(
|
public ResponseEntity<RoleResponse> addPermission(
|
||||||
@PathVariable Long id,
|
@Parameter(description = "롤 ID", required = true) @PathVariable Long id,
|
||||||
@Valid @RequestBody AddPermissionRequest request) {
|
@Valid @RequestBody AddPermissionRequest request) {
|
||||||
return ResponseEntity.ok(roleService.addPermission(id, request.urlPattern()));
|
return ResponseEntity.ok(roleService.addPermission(id, request.urlPattern()));
|
||||||
}
|
}
|
||||||
|
|
||||||
/** URL 패턴 삭제 */
|
@Operation(summary = "URL 패턴 삭제",
|
||||||
|
description = "특정 URL 패턴(권한)을 삭제합니다.")
|
||||||
|
@ApiResponses({
|
||||||
|
@ApiResponse(responseCode = "204", description = "URL 패턴 삭제 성공"),
|
||||||
|
@ApiResponse(responseCode = "401", description = "인증 실패", content = @Content),
|
||||||
|
@ApiResponse(responseCode = "403", description = "관리자 권한 필요", content = @Content),
|
||||||
|
@ApiResponse(responseCode = "404", description = "URL 패턴을 찾을 수 없음", content = @Content)
|
||||||
|
})
|
||||||
@DeleteMapping("/permissions/{permissionId}")
|
@DeleteMapping("/permissions/{permissionId}")
|
||||||
public ResponseEntity<Void> deletePermission(@PathVariable Long permissionId) {
|
public ResponseEntity<Void> deletePermission(
|
||||||
|
@Parameter(description = "URL 패턴 ID", required = true) @PathVariable Long permissionId) {
|
||||||
roleService.deletePermission(permissionId);
|
roleService.deletePermission(permissionId);
|
||||||
return ResponseEntity.noContent().build();
|
return ResponseEntity.noContent().build();
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,23 +2,36 @@ package com.gcsc.guide.controller;
|
|||||||
|
|
||||||
import com.gcsc.guide.dto.StatsResponse;
|
import com.gcsc.guide.dto.StatsResponse;
|
||||||
import com.gcsc.guide.service.UserService;
|
import com.gcsc.guide.service.UserService;
|
||||||
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
|
import io.swagger.v3.oas.annotations.media.Content;
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import io.swagger.v3.oas.annotations.responses.ApiResponse;
|
||||||
|
import io.swagger.v3.oas.annotations.responses.ApiResponses;
|
||||||
|
import io.swagger.v3.oas.annotations.security.SecurityRequirement;
|
||||||
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
import org.springframework.web.bind.annotation.GetMapping;
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
import org.springframework.web.bind.annotation.RequestMapping;
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
import org.springframework.web.bind.annotation.RestController;
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
/**
|
|
||||||
* 관리자 통계 API
|
|
||||||
*/
|
|
||||||
@RestController
|
@RestController
|
||||||
@RequestMapping("/api/admin/stats")
|
@RequestMapping("/api/admin/stats")
|
||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
|
@Tag(name = "04. 관리자 - 통계", description = "사용자 통계 및 시스템 현황 대시보드")
|
||||||
|
@SecurityRequirement(name = "Bearer JWT")
|
||||||
public class AdminStatsController {
|
public class AdminStatsController {
|
||||||
|
|
||||||
private final UserService userService;
|
private final UserService userService;
|
||||||
|
|
||||||
/** 전체 통계 조회 */
|
@Operation(summary = "전체 통계 조회",
|
||||||
|
description = "사용자 상태별 수, 오늘 로그인 수, 전체 롤 수 등 시스템 현황 통계를 반환합니다.")
|
||||||
|
@ApiResponses({
|
||||||
|
@ApiResponse(responseCode = "200", description = "통계 조회 성공",
|
||||||
|
content = @Content(schema = @Schema(implementation = StatsResponse.class))),
|
||||||
|
@ApiResponse(responseCode = "401", description = "인증 실패", content = @Content),
|
||||||
|
@ApiResponse(responseCode = "403", description = "관리자 권한 필요", content = @Content)
|
||||||
|
})
|
||||||
@GetMapping
|
@GetMapping
|
||||||
public ResponseEntity<StatsResponse> getStats() {
|
public ResponseEntity<StatsResponse> getStats() {
|
||||||
return ResponseEntity.ok(userService.getStats());
|
return ResponseEntity.ok(userService.getStats());
|
||||||
|
|||||||
@ -3,6 +3,15 @@ package com.gcsc.guide.controller;
|
|||||||
import com.gcsc.guide.dto.UpdateRolesRequest;
|
import com.gcsc.guide.dto.UpdateRolesRequest;
|
||||||
import com.gcsc.guide.dto.UserResponse;
|
import com.gcsc.guide.dto.UserResponse;
|
||||||
import com.gcsc.guide.service.UserService;
|
import com.gcsc.guide.service.UserService;
|
||||||
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
|
import io.swagger.v3.oas.annotations.Parameter;
|
||||||
|
import io.swagger.v3.oas.annotations.media.ArraySchema;
|
||||||
|
import io.swagger.v3.oas.annotations.media.Content;
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import io.swagger.v3.oas.annotations.responses.ApiResponse;
|
||||||
|
import io.swagger.v3.oas.annotations.responses.ApiResponses;
|
||||||
|
import io.swagger.v3.oas.annotations.security.SecurityRequirement;
|
||||||
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
import jakarta.validation.Valid;
|
import jakarta.validation.Valid;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
@ -10,58 +19,119 @@ import org.springframework.web.bind.annotation.*;
|
|||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
|
||||||
* 관리자 사용자 관리 API
|
|
||||||
*/
|
|
||||||
@RestController
|
@RestController
|
||||||
@RequestMapping("/api/admin/users")
|
@RequestMapping("/api/admin/users")
|
||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
|
@Tag(name = "02. 관리자 - 사용자", description = "사용자 관리 (승인/거절/비활성화/롤/관리자 권한)")
|
||||||
|
@SecurityRequirement(name = "Bearer JWT")
|
||||||
public class AdminUserController {
|
public class AdminUserController {
|
||||||
|
|
||||||
private final UserService userService;
|
private final UserService userService;
|
||||||
|
|
||||||
/** 전체 사용자 목록 조회 (status 필터 선택) */
|
@Operation(summary = "전체 사용자 목록 조회",
|
||||||
|
description = "모든 사용자 목록을 조회합니다. status 파라미터로 특정 상태의 사용자만 필터링할 수 있습니다.")
|
||||||
|
@ApiResponses({
|
||||||
|
@ApiResponse(responseCode = "200", description = "조회 성공",
|
||||||
|
content = @Content(array = @ArraySchema(schema = @Schema(implementation = UserResponse.class)))),
|
||||||
|
@ApiResponse(responseCode = "401", description = "인증 실패", content = @Content),
|
||||||
|
@ApiResponse(responseCode = "403", description = "관리자 권한 필요", content = @Content)
|
||||||
|
})
|
||||||
@GetMapping
|
@GetMapping
|
||||||
public ResponseEntity<List<UserResponse>> getUsers(
|
public ResponseEntity<List<UserResponse>> getUsers(
|
||||||
|
@Parameter(description = "사용자 상태 필터 (PENDING, ACTIVE, REJECTED, DISABLED)",
|
||||||
|
example = "PENDING")
|
||||||
@RequestParam(required = false) String status) {
|
@RequestParam(required = false) String status) {
|
||||||
return ResponseEntity.ok(userService.getUsers(status));
|
return ResponseEntity.ok(userService.getUsers(status));
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 사용자 승인 (PENDING → ACTIVE) */
|
@Operation(summary = "사용자 승인",
|
||||||
|
description = "PENDING 상태의 사용자를 ACTIVE로 변경합니다.")
|
||||||
|
@ApiResponses({
|
||||||
|
@ApiResponse(responseCode = "200", description = "승인 성공",
|
||||||
|
content = @Content(schema = @Schema(implementation = UserResponse.class))),
|
||||||
|
@ApiResponse(responseCode = "401", description = "인증 실패", content = @Content),
|
||||||
|
@ApiResponse(responseCode = "403", description = "관리자 권한 필요", content = @Content),
|
||||||
|
@ApiResponse(responseCode = "404", description = "사용자를 찾을 수 없음", content = @Content)
|
||||||
|
})
|
||||||
@PutMapping("/{id}/approve")
|
@PutMapping("/{id}/approve")
|
||||||
public ResponseEntity<UserResponse> approveUser(@PathVariable Long id) {
|
public ResponseEntity<UserResponse> approveUser(
|
||||||
|
@Parameter(description = "사용자 ID", required = true) @PathVariable Long id) {
|
||||||
return ResponseEntity.ok(userService.approveUser(id));
|
return ResponseEntity.ok(userService.approveUser(id));
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 사용자 거절 (PENDING → REJECTED) */
|
@Operation(summary = "사용자 거절",
|
||||||
|
description = "PENDING 상태의 사용자를 REJECTED로 변경합니다.")
|
||||||
|
@ApiResponses({
|
||||||
|
@ApiResponse(responseCode = "200", description = "거절 성공",
|
||||||
|
content = @Content(schema = @Schema(implementation = UserResponse.class))),
|
||||||
|
@ApiResponse(responseCode = "401", description = "인증 실패", content = @Content),
|
||||||
|
@ApiResponse(responseCode = "403", description = "관리자 권한 필요", content = @Content),
|
||||||
|
@ApiResponse(responseCode = "404", description = "사용자를 찾을 수 없음", content = @Content)
|
||||||
|
})
|
||||||
@PutMapping("/{id}/reject")
|
@PutMapping("/{id}/reject")
|
||||||
public ResponseEntity<UserResponse> rejectUser(@PathVariable Long id) {
|
public ResponseEntity<UserResponse> rejectUser(
|
||||||
|
@Parameter(description = "사용자 ID", required = true) @PathVariable Long id) {
|
||||||
return ResponseEntity.ok(userService.rejectUser(id));
|
return ResponseEntity.ok(userService.rejectUser(id));
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 사용자 비활성화 (ACTIVE → DISABLED) */
|
@Operation(summary = "사용자 비활성화",
|
||||||
|
description = "ACTIVE 상태의 사용자를 DISABLED로 변경합니다.")
|
||||||
|
@ApiResponses({
|
||||||
|
@ApiResponse(responseCode = "200", description = "비활성화 성공",
|
||||||
|
content = @Content(schema = @Schema(implementation = UserResponse.class))),
|
||||||
|
@ApiResponse(responseCode = "401", description = "인증 실패", content = @Content),
|
||||||
|
@ApiResponse(responseCode = "403", description = "관리자 권한 필요", content = @Content),
|
||||||
|
@ApiResponse(responseCode = "404", description = "사용자를 찾을 수 없음", content = @Content)
|
||||||
|
})
|
||||||
@PutMapping("/{id}/disable")
|
@PutMapping("/{id}/disable")
|
||||||
public ResponseEntity<UserResponse> disableUser(@PathVariable Long id) {
|
public ResponseEntity<UserResponse> disableUser(
|
||||||
|
@Parameter(description = "사용자 ID", required = true) @PathVariable Long id) {
|
||||||
return ResponseEntity.ok(userService.disableUser(id));
|
return ResponseEntity.ok(userService.disableUser(id));
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 사용자 롤 업데이트 */
|
@Operation(summary = "사용자 롤 업데이트",
|
||||||
|
description = "사용자에게 할당된 롤을 변경합니다. 기존 롤을 모두 제거하고 전달된 roleIds로 교체합니다.")
|
||||||
|
@ApiResponses({
|
||||||
|
@ApiResponse(responseCode = "200", description = "롤 업데이트 성공",
|
||||||
|
content = @Content(schema = @Schema(implementation = UserResponse.class))),
|
||||||
|
@ApiResponse(responseCode = "401", description = "인증 실패", content = @Content),
|
||||||
|
@ApiResponse(responseCode = "403", description = "관리자 권한 필요", content = @Content),
|
||||||
|
@ApiResponse(responseCode = "404", description = "사용자를 찾을 수 없음", content = @Content)
|
||||||
|
})
|
||||||
@PutMapping("/{id}/roles")
|
@PutMapping("/{id}/roles")
|
||||||
public ResponseEntity<UserResponse> updateUserRoles(
|
public ResponseEntity<UserResponse> updateUserRoles(
|
||||||
@PathVariable Long id,
|
@Parameter(description = "사용자 ID", required = true) @PathVariable Long id,
|
||||||
@Valid @RequestBody UpdateRolesRequest request) {
|
@Valid @RequestBody UpdateRolesRequest request) {
|
||||||
return ResponseEntity.ok(userService.updateUserRoles(id, request.roleIds()));
|
return ResponseEntity.ok(userService.updateUserRoles(id, request.roleIds()));
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 관리자 권한 부여 */
|
@Operation(summary = "관리자 권한 부여",
|
||||||
|
description = "사용자에게 관리자(isAdmin) 권한을 부여합니다.")
|
||||||
|
@ApiResponses({
|
||||||
|
@ApiResponse(responseCode = "200", description = "관리자 권한 부여 성공",
|
||||||
|
content = @Content(schema = @Schema(implementation = UserResponse.class))),
|
||||||
|
@ApiResponse(responseCode = "401", description = "인증 실패", content = @Content),
|
||||||
|
@ApiResponse(responseCode = "403", description = "관리자 권한 필요", content = @Content),
|
||||||
|
@ApiResponse(responseCode = "404", description = "사용자를 찾을 수 없음", content = @Content)
|
||||||
|
})
|
||||||
@PostMapping("/{id}/admin")
|
@PostMapping("/{id}/admin")
|
||||||
public ResponseEntity<UserResponse> grantAdmin(@PathVariable Long id) {
|
public ResponseEntity<UserResponse> grantAdmin(
|
||||||
|
@Parameter(description = "사용자 ID", required = true) @PathVariable Long id) {
|
||||||
return ResponseEntity.ok(userService.grantAdmin(id));
|
return ResponseEntity.ok(userService.grantAdmin(id));
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 관리자 권한 해제 */
|
@Operation(summary = "관리자 권한 해제",
|
||||||
|
description = "사용자의 관리자(isAdmin) 권한을 해제합니다.")
|
||||||
|
@ApiResponses({
|
||||||
|
@ApiResponse(responseCode = "200", description = "관리자 권한 해제 성공",
|
||||||
|
content = @Content(schema = @Schema(implementation = UserResponse.class))),
|
||||||
|
@ApiResponse(responseCode = "401", description = "인증 실패", content = @Content),
|
||||||
|
@ApiResponse(responseCode = "403", description = "관리자 권한 필요", content = @Content),
|
||||||
|
@ApiResponse(responseCode = "404", description = "사용자를 찾을 수 없음", content = @Content)
|
||||||
|
})
|
||||||
@DeleteMapping("/{id}/admin")
|
@DeleteMapping("/{id}/admin")
|
||||||
public ResponseEntity<UserResponse> revokeAdmin(@PathVariable Long id) {
|
public ResponseEntity<UserResponse> revokeAdmin(
|
||||||
|
@Parameter(description = "사용자 ID", required = true) @PathVariable Long id) {
|
||||||
return ResponseEntity.ok(userService.revokeAdmin(id));
|
return ResponseEntity.ok(userService.revokeAdmin(id));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,13 +1,22 @@
|
|||||||
package com.gcsc.guide.controller;
|
package com.gcsc.guide.controller;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
|
import io.swagger.v3.oas.annotations.responses.ApiResponse;
|
||||||
|
import io.swagger.v3.oas.annotations.security.SecurityRequirement;
|
||||||
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
import org.springframework.web.bind.annotation.GetMapping;
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
import org.springframework.web.bind.annotation.RestController;
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
@RestController
|
@RestController
|
||||||
|
@Tag(name = "00. 시스템", description = "헬스체크 및 시스템 상태 확인")
|
||||||
public class HealthController {
|
public class HealthController {
|
||||||
|
|
||||||
|
@Operation(summary = "헬스체크",
|
||||||
|
description = "서버 가동 상태를 확인합니다. 인증 없이 접근 가능합니다.",
|
||||||
|
security = {})
|
||||||
|
@ApiResponse(responseCode = "200", description = "서버 정상 가동 중")
|
||||||
@GetMapping("/api/health")
|
@GetMapping("/api/health")
|
||||||
public Map<String, String> health() {
|
public Map<String, String> health() {
|
||||||
return Map.of(
|
return Map.of(
|
||||||
|
|||||||
@ -2,6 +2,14 @@ package com.gcsc.guide.controller;
|
|||||||
|
|
||||||
import com.gcsc.guide.dto.*;
|
import com.gcsc.guide.dto.*;
|
||||||
import com.gcsc.guide.service.IssueService;
|
import com.gcsc.guide.service.IssueService;
|
||||||
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
|
import io.swagger.v3.oas.annotations.Parameter;
|
||||||
|
import io.swagger.v3.oas.annotations.media.Content;
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import io.swagger.v3.oas.annotations.responses.ApiResponse;
|
||||||
|
import io.swagger.v3.oas.annotations.responses.ApiResponses;
|
||||||
|
import io.swagger.v3.oas.annotations.security.SecurityRequirement;
|
||||||
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
import jakarta.validation.Valid;
|
import jakarta.validation.Valid;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import org.springframework.data.domain.Page;
|
import org.springframework.data.domain.Page;
|
||||||
@ -15,25 +23,37 @@ import org.springframework.web.bind.annotation.*;
|
|||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
/**
|
|
||||||
* 이슈 관리 API
|
|
||||||
*/
|
|
||||||
@RestController
|
@RestController
|
||||||
@RequestMapping("/api/issues")
|
@RequestMapping("/api/issues")
|
||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
|
@Tag(name = "06. 이슈 관리", description = "이슈 등록/조회/수정 및 코멘트 관리")
|
||||||
|
@SecurityRequirement(name = "Bearer JWT")
|
||||||
public class IssueController {
|
public class IssueController {
|
||||||
|
|
||||||
private final IssueService issueService;
|
private final IssueService issueService;
|
||||||
|
|
||||||
/** 이슈 목록 (status 필터, 페이징) */
|
@Operation(summary = "이슈 목록 조회",
|
||||||
|
description = "이슈 목록을 페이징으로 조회합니다. status 파라미터로 상태별 필터링이 가능합니다.")
|
||||||
|
@ApiResponses({
|
||||||
|
@ApiResponse(responseCode = "200", description = "조회 성공"),
|
||||||
|
@ApiResponse(responseCode = "401", description = "인증 실패", content = @Content)
|
||||||
|
})
|
||||||
@GetMapping
|
@GetMapping
|
||||||
public ResponseEntity<Page<IssueResponse>> getIssues(
|
public ResponseEntity<Page<IssueResponse>> getIssues(
|
||||||
|
@Parameter(description = "이슈 상태 필터 (OPEN, IN_PROGRESS, CLOSED)", example = "OPEN")
|
||||||
@RequestParam(required = false) String status,
|
@RequestParam(required = false) String status,
|
||||||
|
@Parameter(description = "페이징 파라미터 (page, size, sort)")
|
||||||
@PageableDefault(size = 20, sort = "createdAt", direction = Sort.Direction.DESC) Pageable pageable) {
|
@PageableDefault(size = 20, sort = "createdAt", direction = Sort.Direction.DESC) Pageable pageable) {
|
||||||
return ResponseEntity.ok(issueService.getIssues(status, pageable));
|
return ResponseEntity.ok(issueService.getIssues(status, pageable));
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 이슈 생성 */
|
@Operation(summary = "이슈 생성",
|
||||||
|
description = "새로운 이슈를 생성합니다. 프로젝트명, 위치, Gitea 이슈 링크 등을 선택적으로 포함할 수 있습니다.")
|
||||||
|
@ApiResponses({
|
||||||
|
@ApiResponse(responseCode = "201", description = "이슈 생성 성공",
|
||||||
|
content = @Content(schema = @Schema(implementation = IssueResponse.class))),
|
||||||
|
@ApiResponse(responseCode = "401", description = "인증 실패", content = @Content)
|
||||||
|
})
|
||||||
@PostMapping
|
@PostMapping
|
||||||
public ResponseEntity<IssueResponse> createIssue(
|
public ResponseEntity<IssueResponse> createIssue(
|
||||||
Authentication authentication,
|
Authentication authentication,
|
||||||
@ -43,24 +63,45 @@ public class IssueController {
|
|||||||
return ResponseEntity.created(URI.create("/api/issues/" + issue.id())).body(issue);
|
return ResponseEntity.created(URI.create("/api/issues/" + issue.id())).body(issue);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 이슈 상세 (코멘트 포함) */
|
@Operation(summary = "이슈 상세 조회",
|
||||||
|
description = "이슈의 상세 정보와 코멘트 목록을 함께 반환합니다.")
|
||||||
|
@ApiResponses({
|
||||||
|
@ApiResponse(responseCode = "200", description = "조회 성공"),
|
||||||
|
@ApiResponse(responseCode = "401", description = "인증 실패", content = @Content),
|
||||||
|
@ApiResponse(responseCode = "404", description = "이슈를 찾을 수 없음", content = @Content)
|
||||||
|
})
|
||||||
@GetMapping("/{id}")
|
@GetMapping("/{id}")
|
||||||
public ResponseEntity<Map<String, Object>> getIssue(@PathVariable Long id) {
|
public ResponseEntity<Map<String, Object>> getIssue(
|
||||||
|
@Parameter(description = "이슈 ID", required = true) @PathVariable Long id) {
|
||||||
return ResponseEntity.ok(issueService.getIssueDetail(id));
|
return ResponseEntity.ok(issueService.getIssueDetail(id));
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 이슈 수정 */
|
@Operation(summary = "이슈 수정",
|
||||||
|
description = "이슈의 제목, 내용, 상태, 우선순위, 담당자 등을 수정합니다.")
|
||||||
|
@ApiResponses({
|
||||||
|
@ApiResponse(responseCode = "200", description = "수정 성공",
|
||||||
|
content = @Content(schema = @Schema(implementation = IssueResponse.class))),
|
||||||
|
@ApiResponse(responseCode = "401", description = "인증 실패", content = @Content),
|
||||||
|
@ApiResponse(responseCode = "404", description = "이슈를 찾을 수 없음", content = @Content)
|
||||||
|
})
|
||||||
@PutMapping("/{id}")
|
@PutMapping("/{id}")
|
||||||
public ResponseEntity<IssueResponse> updateIssue(
|
public ResponseEntity<IssueResponse> updateIssue(
|
||||||
@PathVariable Long id,
|
@Parameter(description = "이슈 ID", required = true) @PathVariable Long id,
|
||||||
@RequestBody UpdateIssueRequest request) {
|
@RequestBody UpdateIssueRequest request) {
|
||||||
return ResponseEntity.ok(issueService.updateIssue(id, request));
|
return ResponseEntity.ok(issueService.updateIssue(id, request));
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 코멘트 추가 */
|
@Operation(summary = "코멘트 추가",
|
||||||
|
description = "이슈에 새로운 코멘트를 추가합니다.")
|
||||||
|
@ApiResponses({
|
||||||
|
@ApiResponse(responseCode = "201", description = "코멘트 추가 성공",
|
||||||
|
content = @Content(schema = @Schema(implementation = IssueCommentResponse.class))),
|
||||||
|
@ApiResponse(responseCode = "401", description = "인증 실패", content = @Content),
|
||||||
|
@ApiResponse(responseCode = "404", description = "이슈를 찾을 수 없음", content = @Content)
|
||||||
|
})
|
||||||
@PostMapping("/{id}/comments")
|
@PostMapping("/{id}/comments")
|
||||||
public ResponseEntity<IssueCommentResponse> addComment(
|
public ResponseEntity<IssueCommentResponse> addComment(
|
||||||
@PathVariable Long id,
|
@Parameter(description = "이슈 ID", required = true) @PathVariable Long id,
|
||||||
Authentication authentication,
|
Authentication authentication,
|
||||||
@Valid @RequestBody CreateCommentRequest request) {
|
@Valid @RequestBody CreateCommentRequest request) {
|
||||||
Long authorId = (Long) authentication.getPrincipal();
|
Long authorId = (Long) authentication.getPrincipal();
|
||||||
|
|||||||
@ -31,6 +31,17 @@ app:
|
|||||||
cors:
|
cors:
|
||||||
allowed-origins: ${CORS_ORIGINS:http://localhost:5173,https://guide.gc-si.dev}
|
allowed-origins: ${CORS_ORIGINS:http://localhost:5173,https://guide.gc-si.dev}
|
||||||
|
|
||||||
|
# SpringDoc / Swagger
|
||||||
|
springdoc:
|
||||||
|
api-docs:
|
||||||
|
path: /v3/api-docs
|
||||||
|
swagger-ui:
|
||||||
|
path: /swagger-ui.html
|
||||||
|
tags-sorter: alpha
|
||||||
|
operations-sorter: method
|
||||||
|
doc-expansion: none
|
||||||
|
display-request-duration: true
|
||||||
|
|
||||||
# Actuator
|
# Actuator
|
||||||
management:
|
management:
|
||||||
endpoints:
|
endpoints:
|
||||||
|
|||||||
불러오는 중...
Reference in New Issue
Block a user