Skip to content

Commit 3c3b232

Browse files
authored
Merge pull request #14 from Hm-source/dev
dev to main
2 parents 0da75c3 + 3e91928 commit 3c3b232

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

56 files changed

+1627
-43
lines changed

build.gradle

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ dependencies {
6363
annotationProcessor 'com.querydsl:querydsl-apt:5.0.0:jakarta'
6464
annotationProcessor 'jakarta.persistence:jakarta.persistence-api'
6565
annotationProcessor 'jakarta.annotation:jakarta.annotation-api'
66+
implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.7.0'
6667
}
6768

6869
tasks.named('test') {

src/main/java/org/example/gridgestagram/config/SecurityConfig.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,13 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
7373
.authenticationProvider(daoAuthenticationProvider())
7474
.authorizeHttpRequests(authz -> authz
7575
.requestMatchers(HttpMethod.OPTIONS, "/**").permitAll()
76+
.requestMatchers(
77+
"/swagger-ui.html",
78+
"/swagger-ui/**",
79+
"/api-docs/**",
80+
"/v3/api-docs/**",
81+
"/v3/api-docs.yaml"
82+
).permitAll()
7683
.requestMatchers("/", "/login/**", "/oauth2/**", "/login/oauth2/**").permitAll()
7784
.requestMatchers(HttpMethod.POST, "/api/auth/signup").permitAll()
7885
.requestMatchers("/api/auth/**").permitAll()
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
package org.example.gridgestagram.config;
2+
3+
import io.swagger.v3.oas.models.Components;
4+
import io.swagger.v3.oas.models.OpenAPI;
5+
import io.swagger.v3.oas.models.info.Info;
6+
import io.swagger.v3.oas.models.security.SecurityRequirement;
7+
import io.swagger.v3.oas.models.security.SecurityScheme;
8+
import io.swagger.v3.oas.models.servers.Server;
9+
import java.util.List;
10+
import org.springdoc.core.models.GroupedOpenApi;
11+
import org.springframework.context.annotation.Bean;
12+
import org.springframework.context.annotation.Configuration;
13+
14+
@Configuration
15+
public class SwaggerConfig {
16+
17+
@Bean
18+
public OpenAPI openAPI() {
19+
return new OpenAPI()
20+
.info(apiInfo())
21+
.servers(List.of(
22+
new Server().url("http://localhost:8080").description("로컬 서버"),
23+
new Server().url("http://3.39.0.54").description("운영 서버")
24+
))
25+
.components(new Components()
26+
.addSecuritySchemes("bearerAuth",
27+
new SecurityScheme()
28+
.type(SecurityScheme.Type.HTTP)
29+
.scheme("bearer")
30+
.bearerFormat("JWT")))
31+
.addSecurityItem(new SecurityRequirement().addList("bearerAuth"));
32+
}
33+
34+
private Info apiInfo() {
35+
return new Info()
36+
.title("Gridgestagram API")
37+
.description("Gridgestagram 프로젝트의 REST API 문서입니다.")
38+
.version("v1");
39+
}
40+
41+
@Bean
42+
public GroupedOpenApi grouping() {
43+
String[] paths = {"/**"};
44+
return GroupedOpenApi.builder()
45+
.group("spec")
46+
.pathsToMatch(paths)
47+
.build();
48+
}
49+
}

src/main/java/org/example/gridgestagram/controller/admin/AccountManagementController.java

Lines changed: 97 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,13 @@
11
package org.example.gridgestagram.controller.admin;
22

3+
import io.swagger.v3.oas.annotations.Operation;
4+
import io.swagger.v3.oas.annotations.Parameter;
5+
import io.swagger.v3.oas.annotations.media.Content;
6+
import io.swagger.v3.oas.annotations.media.ExampleObject;
7+
import io.swagger.v3.oas.annotations.media.Schema;
8+
import io.swagger.v3.oas.annotations.responses.ApiResponse;
9+
import io.swagger.v3.oas.annotations.responses.ApiResponses;
10+
import io.swagger.v3.oas.annotations.tags.Tag;
311
import java.time.LocalDateTime;
412
import lombok.RequiredArgsConstructor;
513
import org.example.gridgestagram.annotation.LogAction;
@@ -13,6 +21,7 @@
1321
import org.springframework.web.bind.annotation.RequestMapping;
1422
import org.springframework.web.bind.annotation.RestController;
1523

24+
@Tag(name = "관리자 - 계정 관리", description = "관리자용 계정 상태 관리 API (일시정지, 해제, 휴면 활성화)")
1625
@RestController
1726
@RequestMapping("/api/admin/accounts")
1827
@RequiredArgsConstructor
@@ -22,9 +31,36 @@ public class AccountManagementController {
2231

2332
private final AccountManagementService accountManagementService;
2433

25-
34+
@Operation(
35+
summary = "계정 일시정지",
36+
description = "관리자가 문제가 있는 사용자의 계정을 일시적으로 정지시킵니다."
37+
)
38+
@ApiResponses({
39+
@ApiResponse(
40+
responseCode = "200",
41+
description = "계정 일시정지 성공",
42+
content = @Content(
43+
mediaType = "application/json",
44+
schema = @Schema(implementation = AccountActionResponse.class),
45+
examples = @ExampleObject(
46+
value = "{\"success\": true, \"message\": \"계정이 일시정지되었습니다.\", \"action\": \"SUSPEND\", \"timestamp\": \"2024-01-01 10:00:00\"}"
47+
)
48+
)
49+
),
50+
@ApiResponse(
51+
responseCode = "403",
52+
description = "권한 부족 (관리자 권한 필요)",
53+
content = @Content(mediaType = "application/json")
54+
),
55+
@ApiResponse(
56+
responseCode = "404",
57+
description = "사용자를 찾을 수 없음",
58+
content = @Content(mediaType = "application/json")
59+
)
60+
})
2661
@PostMapping("/{userId}/suspend")
2762
public ResponseEntity<AccountActionResponse> suspendAccount(
63+
@Parameter(description = "일시정지할 사용자 ID", example = "1")
2864
@PathVariable Long userId) {
2965

3066
accountManagementService.suspendAccount(userId);
@@ -37,8 +73,37 @@ public ResponseEntity<AccountActionResponse> suspendAccount(
3773
.build());
3874
}
3975

76+
@Operation(
77+
summary = "계정 일시정지 해제",
78+
description = "관리자가 일시정지된 사용자의 계정을 다시 활성화시킵니다."
79+
)
80+
@ApiResponses({
81+
@ApiResponse(
82+
responseCode = "200",
83+
description = "계정 일시정지 해제 성공",
84+
content = @Content(
85+
mediaType = "application/json",
86+
schema = @Schema(implementation = AccountActionResponse.class),
87+
examples = @ExampleObject(
88+
value = "{\"success\": true, \"message\": \"계정 일시정지가 해제되었습니다.\", \"action\": \"UNSUSPEND\", \"timestamp\": \"2024-01-01 10:00:00\"}"
89+
)
90+
)
91+
),
92+
@ApiResponse(
93+
responseCode = "403",
94+
description = "권한 부족 (관리자 권한 필요)",
95+
content = @Content(mediaType = "application/json")
96+
),
97+
@ApiResponse(
98+
responseCode = "404",
99+
description = "사용자를 찾을 수 없음",
100+
content = @Content(mediaType = "application/json")
101+
)
102+
})
40103
@PostMapping("/{userId}/unsuspend")
41-
public ResponseEntity<AccountActionResponse> unsuspendAccount(@PathVariable Long userId) {
104+
public ResponseEntity<AccountActionResponse> unsuspendAccount(
105+
@Parameter(description = "일시정지 해제할 사용자 ID", example = "1")
106+
@PathVariable Long userId) {
42107

43108
accountManagementService.unsuspendAccount(userId);
44109

@@ -50,8 +115,37 @@ public ResponseEntity<AccountActionResponse> unsuspendAccount(@PathVariable Long
50115
.build());
51116
}
52117

118+
@Operation(
119+
summary = "휴면 계정 활성화",
120+
description = "관리자가 휴면 상태인 사용자의 계정을 다시 활성화시킵니다."
121+
)
122+
@ApiResponses({
123+
@ApiResponse(
124+
responseCode = "200",
125+
description = "휴면 계정 활성화 성공",
126+
content = @Content(
127+
mediaType = "application/json",
128+
schema = @Schema(implementation = AccountActionResponse.class),
129+
examples = @ExampleObject(
130+
value = "{\"success\": true, \"message\": \"휴면 계정이 활성화되었습니다.\", \"action\": \"ACTIVATE_DORMANT\", \"timestamp\": \"2024-01-01 10:00:00\"}"
131+
)
132+
)
133+
),
134+
@ApiResponse(
135+
responseCode = "403",
136+
description = "권한 부족 (관리자 권한 필요)",
137+
content = @Content(mediaType = "application/json")
138+
),
139+
@ApiResponse(
140+
responseCode = "404",
141+
description = "사용자를 찾을 수 없음",
142+
content = @Content(mediaType = "application/json")
143+
)
144+
})
53145
@PostMapping("/{userId}/activate-dormant")
54-
public ResponseEntity<AccountActionResponse> activateDormantAccount(@PathVariable Long userId) {
146+
public ResponseEntity<AccountActionResponse> activateDormantAccount(
147+
@Parameter(description = "활성화할 휴면 계정 사용자 ID", example = "1")
148+
@PathVariable Long userId) {
55149

56150
accountManagementService.activateDormantAccount(userId);
57151

src/main/java/org/example/gridgestagram/controller/admin/AdminFeedController.java

Lines changed: 87 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,13 @@
11
package org.example.gridgestagram.controller.admin;
22

3+
import io.swagger.v3.oas.annotations.Operation;
4+
import io.swagger.v3.oas.annotations.Parameter;
5+
import io.swagger.v3.oas.annotations.media.Content;
6+
import io.swagger.v3.oas.annotations.media.ExampleObject;
7+
import io.swagger.v3.oas.annotations.media.Schema;
8+
import io.swagger.v3.oas.annotations.responses.ApiResponse;
9+
import io.swagger.v3.oas.annotations.responses.ApiResponses;
10+
import io.swagger.v3.oas.annotations.tags.Tag;
311
import lombok.RequiredArgsConstructor;
412
import org.example.gridgestagram.annotation.LogAction;
513
import org.example.gridgestagram.controller.admin.dto.AdminFeedDetailResponse;
@@ -21,35 +29,112 @@
2129
import org.springframework.web.bind.annotation.RequestParam;
2230
import org.springframework.web.bind.annotation.RestController;
2331

32+
@Tag(name = "관리자 - 피드 관리", description = "관리자용 피드 검색, 상세 조회, 삭제 관리 API")
2433
@RestController
2534
@RequestMapping("/api/admin/feeds")
2635
@RequiredArgsConstructor
27-
@Secured("ROLE_ADMIN")
2836
public class AdminFeedController {
2937

3038
private final AdminFeedService adminFeedService;
3139

40+
@Operation(
41+
summary = "피드 검색 및 목록 조회",
42+
description = "관리자가 다양한 조건으로 피드를 검색하고 목록을 조회합니다. 사용자명, 상태, 기간 등으로 필터링할 수 있습니다."
43+
)
44+
@ApiResponses({
45+
@ApiResponse(
46+
responseCode = "200",
47+
description = "피드 검색 성공",
48+
content = @Content(
49+
mediaType = "application/json",
50+
examples = @ExampleObject(
51+
value = "{\"content\": [{\"id\": 1, \"content\": \"피드 내용\", \"status\": \"ACTIVE\", \"likeCount\": 10, \"commentCount\": 5, \"user\": {\"username\": \"user123\"}, \"createdAt\": \"2024-01-01 10:00:00\"}], \"pageable\": {\"pageNumber\": 0, \"pageSize\": 20}, \"totalElements\": 100}"
52+
)
53+
)
54+
),
55+
@ApiResponse(
56+
responseCode = "403",
57+
description = "권한 부족 (관리자 권한 필요)",
58+
content = @Content(mediaType = "application/json")
59+
)
60+
})
61+
@Secured("ROLE_ADMIN")
3262
@LogAction(value = LogType.ADMIN_FEED_VIEW, targetType = "FEED")
3363
@GetMapping
3464
public ResponseEntity<Page<AdminFeedResponse>> searchFeeds(
65+
@Parameter(description = "검색 조건 (사용자명, 상태, 기간 등)")
3566
@ModelAttribute AdminFeedSearchCondition condition,
67+
@Parameter(description = "페이징 정보 (기본 20개씩, 최신순)")
3668
@PageableDefault(size = 20, sort = "createdAt", direction = Sort.Direction.DESC) Pageable pageable) {
3769

3870
Page<AdminFeedResponse> result = adminFeedService.searchFeeds(condition, pageable);
3971
return ResponseEntity.ok(result);
4072
}
4173

74+
@Operation(
75+
summary = "피드 상세 조회",
76+
description = "관리자가 특정 피드의 상세 정보를 조회합니다. 작성자 정보, 첨부파일, 신고 내역 등을 포함합니다."
77+
)
78+
@ApiResponses({
79+
@ApiResponse(
80+
responseCode = "200",
81+
description = "피드 상세 조회 성공",
82+
content = @Content(
83+
mediaType = "application/json",
84+
schema = @Schema(implementation = AdminFeedDetailResponse.class),
85+
examples = @ExampleObject(
86+
value = "{\"id\": 1, \"content\": \"상세 피드 내용\", \"status\": \"ACTIVE\", \"likeCount\": 15, \"commentCount\": 8, \"user\": {\"id\": 1, \"username\": \"user123\", \"email\": \"[email protected]\"}, \"files\": [], \"reports\": [], \"createdAt\": \"2024-01-01 10:00:00\"}"
87+
)
88+
)
89+
),
90+
@ApiResponse(
91+
responseCode = "403",
92+
description = "권한 부족 (관리자 권한 필요)",
93+
content = @Content(mediaType = "application/json")
94+
),
95+
@ApiResponse(
96+
responseCode = "404",
97+
description = "피드를 찾을 수 없음",
98+
content = @Content(mediaType = "application/json")
99+
)
100+
})
101+
@Secured("ROLE_ADMIN")
42102
@LogAction(value = LogType.ADMIN_FEED_VIEW, targetType = "FEED")
43103
@GetMapping("/{feedId}")
44-
public ResponseEntity<AdminFeedDetailResponse> getFeedDetail(@PathVariable Long feedId) {
104+
public ResponseEntity<AdminFeedDetailResponse> getFeedDetail(
105+
@Parameter(description = "상세 조회할 피드 ID", example = "1")
106+
@PathVariable Long feedId) {
45107
AdminFeedDetailResponse response = adminFeedService.getFeedDetail(feedId);
46108
return ResponseEntity.ok(response);
47109
}
48110

111+
@Operation(
112+
summary = "피드 삭제",
113+
description = "관리자가 문제가 있는 피드를 삭제합니다. 삭제 사유를 명시할 수 있습니다."
114+
)
115+
@ApiResponses({
116+
@ApiResponse(
117+
responseCode = "204",
118+
description = "피드 삭제 성공 (내용 없음)"
119+
),
120+
@ApiResponse(
121+
responseCode = "403",
122+
description = "권한 부족 (관리자 권한 필요)",
123+
content = @Content(mediaType = "application/json")
124+
),
125+
@ApiResponse(
126+
responseCode = "404",
127+
description = "피드를 찾을 수 없음",
128+
content = @Content(mediaType = "application/json")
129+
)
130+
})
131+
@Secured("ROLE_ADMIN")
49132
@LogAction(value = LogType.ADMIN_FEED_DELETE, targetType = "FEED")
50133
@DeleteMapping("/{feedId}")
51134
public ResponseEntity<Void> deleteFeed(
135+
@Parameter(description = "삭제할 피드 ID", example = "1")
52136
@PathVariable Long feedId,
137+
@Parameter(description = "삭제 사유 (선택사항)", example = "스팸 콘텐츠로 인한 삭제")
53138
@RequestParam(required = false) String reason) {
54139
adminFeedService.deleteFeed(feedId, reason);
55140
return ResponseEntity.noContent().build();

0 commit comments

Comments
 (0)