2929@ RestController
3030@ RequestMapping ("/api/v1/core-attendance/meetings" )
3131@ RequiredArgsConstructor
32- @ PreAuthorize (CoreAttendanceController .LEAD_OR_HIGHER_RULE )
3332public class CoreAttendanceController {
3433
35- public static final String LEAD_OR_HIGHER_RULE =
34+ public static final String CORE_OR_HIGHER_RULE =
3635 "@accessGuard.check(authentication,"
3736 + " T(inha.gdgoc.global.security.AccessGuard$AccessCondition).atLeast("
38- + "T(inha.gdgoc.domain.user.enums.UserRole).LEAD ))" ;
39- public static final String ORGANIZER_OR_HIGHER_RULE =
37+ + "T(inha.gdgoc.domain.user.enums.UserRole).CORE ))" ;
38+ public static final String LEAD_OR_HIGHER_RULE =
4039 "@accessGuard.check(authentication,"
4140 + " T(inha.gdgoc.global.security.AccessGuard$AccessCondition).atLeast("
42- + "T(inha.gdgoc.domain.user.enums.UserRole).ORGANIZER))" ;
43-
41+ + "T(inha.gdgoc.domain.user.enums.UserRole).LEAD))" ;
4442 private final CoreAttendanceService service ;
4543
4644 private static ResponseEntity <ApiResponse <Map <String , Object >, Void >> okUpdated (long updated , List <Long > ignored ) {
4745 return ResponseEntity .ok (ApiResponse .ok (CoreAttendanceMessage .ATTENDANCE_ALL_SET_SUCCESS , Map .of ("updated" , updated , "ignoredUserIds" , ignored )));
4846 }
4947
5048 /* ===== Meetings(날짜) 목록 ===== */
49+ @ PreAuthorize (CORE_OR_HIGHER_RULE )
5150 @ GetMapping
5251 public ResponseEntity <ApiResponse <DateListResponse , Void >> listDates () {
5352 return ResponseEntity .ok (ApiResponse .ok (CoreAttendanceMessage .DATE_LIST_RETRIEVED_SUCCESS , new DateListResponse (service .getDates ())));
5453 }
5554
56- @ PreAuthorize (ORGANIZER_OR_HIGHER_RULE )
55+ @ PreAuthorize (LEAD_OR_HIGHER_RULE )
5756 @ PostMapping
5857 public ResponseEntity <ApiResponse <DateListResponse , Void >> createDate (@ Valid @ RequestBody CreateDateRequest request ) {
5958 service .addDate (request .getDate ());
6059 return ResponseEntity .ok (ApiResponse .ok (CoreAttendanceMessage .DATE_CREATED_SUCCESS , new DateListResponse (service .getDates ())));
6160 }
6261
63- @ PreAuthorize (ORGANIZER_OR_HIGHER_RULE )
62+ @ PreAuthorize (LEAD_OR_HIGHER_RULE )
6463 @ DeleteMapping ("/{date}" )
6564 public ResponseEntity <ApiResponse <DateListResponse , Void >> deleteDate (@ PathVariable @ DateTimeFormat (iso = DateTimeFormat .ISO .DATE ) LocalDate date ) {
6665 service .deleteDate (date .toString ());
6766 return ResponseEntity .ok (ApiResponse .ok (CoreAttendanceMessage .DATE_DELETED_SUCCESS , new DateListResponse (service .getDates ())));
6867 }
6968
7069 /* ===== 팀 목록 (리드=본인 팀만 / 관리자=전체) ===== */
70+ @ PreAuthorize (CORE_OR_HIGHER_RULE )
7171 @ GetMapping ("/teams" )
7272 public ResponseEntity <ApiResponse <List <TeamResponse >, PageMeta >> getTeams (@ AuthenticationPrincipal CustomUserDetails me ) {
73- List <TeamResponse > list = service .isLeadScoped (me .getRole (), me .getTeam ())
73+ List <TeamResponse > list = service .isTeamScoped (me .getRole (), me .getTeam ())
7474 ? service .getTeamsForLead (service .resolveEffectiveTeam (me .getRole (), me .getTeam (), null ))
7575 : service .getTeamsForOrganizerOrAdmin ();
7676
@@ -80,31 +80,34 @@ public ResponseEntity<ApiResponse<List<TeamResponse>, PageMeta>> getTeams(@Authe
8080
8181 /* ===== 특정 날짜의 팀원+현재 출석 상태 조회 (리드=본인 팀만) ===== */
8282 // 프론트가 체크박스 채우기 전에 필요한 목록/상태
83+ @ PreAuthorize (CORE_OR_HIGHER_RULE )
8384 @ GetMapping ("/{date}/members" )
8485 public ResponseEntity <ApiResponse <List <Map <String , Object >>, Void >> membersOfMeeting (@ AuthenticationPrincipal CustomUserDetails me , @ PathVariable @ DateTimeFormat (iso = DateTimeFormat .ISO .DATE ) LocalDate date , @ RequestParam (required = false ) TeamType team // 관리자만 사용, 리드는 무시
8586 ) {
8687 TeamType effectiveTeam = service .resolveEffectiveTeam (me .getRole (), me .getTeam (), team );
8788 var list = service .getMembersWithPresence (date .toString (), effectiveTeam );
88- // list 원소 예시: { "userId": "123", "name": "홍길동", "present ": true , "lastModifiedAt": "..." }
89+ // list 원소 예시: { "userId": "123", "name": "홍길동", "status ": "PRESENT" , "lastModifiedAt": "..." }
8990 return ResponseEntity .ok (ApiResponse .ok (CoreAttendanceMessage .TEAM_LIST_RETRIEVED_SUCCESS , list ));
9091 }
9192
9293 /* ===== 특정 날짜 출석 일괄 저장 (멱등 스냅샷) ===== */
93- // Body: { "userIds": ["1","2",...], "present": true } → presentUserIds만 보내는 구조로도 쉽게 변환 가능
94+ // Body: { "userIds": ["1","2",...], "status": "PRESENT" }
95+ @ PreAuthorize (LEAD_OR_HIGHER_RULE )
9496 @ PutMapping ("/{date}/attendance" )
9597 public ResponseEntity <ApiResponse <Map <String , Object >, Void >> saveAttendanceSnapshot (@ AuthenticationPrincipal CustomUserDetails me , @ PathVariable @ DateTimeFormat (iso = DateTimeFormat .ISO .DATE ) LocalDate date , @ RequestBody @ Valid SetAttendanceRequest req ) {
9698 var userIds = req .safeUserIds ();
9799 CoreAttendanceService .AttendanceUpdateResult result = service .saveAttendanceSnapshot (
98100 date .toString (),
99101 userIds ,
100- req .presentValue (),
102+ req .statusValue (),
101103 me .getRole (),
102104 me .getTeam ()
103105 );
104106 return okUpdated (result .updatedCount (), result .ignoredUserIds ());
105107 }
106108
107109 /* ===== 날짜 요약(JSON) ===== */
110+ @ PreAuthorize (CORE_OR_HIGHER_RULE )
108111 @ GetMapping ("/{date}/summary" )
109112 public ResponseEntity <ApiResponse <DaySummaryResponse , Void >> summary (@ AuthenticationPrincipal CustomUserDetails me , @ PathVariable @ DateTimeFormat (iso = DateTimeFormat .ISO .DATE ) LocalDate date , @ RequestParam (required = false ) TeamType team ) {
110113 TeamType effectiveTeam = service .resolveEffectiveTeam (me .getRole (), me .getTeam (), team );
@@ -113,6 +116,7 @@ public ResponseEntity<ApiResponse<DaySummaryResponse, Void>> summary(@Authentica
113116 }
114117
115118 /* ===== 날짜 요약(CSV) ===== */
119+ @ PreAuthorize (CORE_OR_HIGHER_RULE )
116120 @ GetMapping (value = "/{date}/summary.csv" , produces = "text/csv; charset=UTF-8" )
117121 public ResponseEntity <String > summaryCsv (@ AuthenticationPrincipal CustomUserDetails me , @ PathVariable @ DateTimeFormat (iso = DateTimeFormat .ISO .DATE ) LocalDate date , @ RequestParam (required = false ) TeamType team ) {
118122 TeamType effective = service .resolveEffectiveTeam (me .getRole (), me .getTeam (), team );
@@ -122,6 +126,7 @@ public ResponseEntity<String> summaryCsv(@AuthenticationPrincipal CustomUserDeta
122126 .body (csv );
123127 }
124128
129+ @ PreAuthorize (CORE_OR_HIGHER_RULE )
125130 @ GetMapping (value = "/summary.csv" , produces = "text/csv; charset=UTF-8" )
126131 public ResponseEntity <String > summaryCsvAll (
127132 @ AuthenticationPrincipal CustomUserDetails me ,
0 commit comments