Skip to content

Commit 8d719c5

Browse files
authored
20260227 #247 출석체크 description 추가 (#258)
* [BE][FEAT] AttendanceController Operation 추가 * [BE][FEAT] AttendanceSessionController Operation 추가 * [BE][FEAT] SessionUserController Operation 추가 * [BE][FIX] 에러코드 수정 및 로그 수정
1 parent fe3d5b2 commit 8d719c5

10 files changed

Lines changed: 421 additions & 169 deletions

File tree

Lines changed: 105 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
package org.sejongisc.backend.attendance.controller;
22

3+
import static org.sejongisc.backend.attendance.util.AuthUserUtil.requireUserId;
4+
35
import io.swagger.v3.oas.annotations.Operation;
46
import io.swagger.v3.oas.annotations.tags.Tag;
57
import jakarta.validation.Valid;
@@ -14,7 +16,13 @@
1416
import org.sejongisc.backend.common.auth.dto.CustomUserDetails;
1517
import org.springframework.http.ResponseEntity;
1618
import org.springframework.security.core.annotation.AuthenticationPrincipal;
17-
import org.springframework.web.bind.annotation.*;
19+
import org.springframework.web.bind.annotation.GetMapping;
20+
import org.springframework.web.bind.annotation.PathVariable;
21+
import org.springframework.web.bind.annotation.PostMapping;
22+
import org.springframework.web.bind.annotation.PutMapping;
23+
import org.springframework.web.bind.annotation.RequestBody;
24+
import org.springframework.web.bind.annotation.RequestMapping;
25+
import org.springframework.web.bind.annotation.RestController;
1826

1927
@RestController
2028
@RequestMapping("/api/attendance")
@@ -30,21 +38,68 @@ public class AttendanceController {
3038
* POST /api/attendance/check-in
3139
* body: { "qrToken": "..." }
3240
*/
33-
@Operation(summary = "체크인", description = "qrToken으로 출석 체크인합니다. (세션 멤버)")
41+
@Operation(
42+
summary = "체크인",
43+
description = """
44+
## 인증(JWT): **필요**
45+
46+
## 요청 바디 ( `AttendanceRoundQrTokenRequest` )
47+
- **`qrToken`**: QR 토큰
48+
49+
## 동작 설명
50+
- qrToken이 유효한지 검증
51+
- 출석 라운드가 ACTIVE 상태인지 검증
52+
- 해당 멤버가 출석 세션의 멤버인지 검증
53+
- 해당 세션의 allowedMinutes 내에 출석체크하면 AttendanceStatus가 PRESENT
54+
- allowedMinutes가 지난 후에 출석체크하면 LATE
55+
56+
## 응답
57+
- **`200 OK`**
58+
59+
## 에러코드
60+
- **`QR_TOKEN_MALFORMED`** : QR 토큰 형식이 올바르지 않습니다.
61+
- **`ROUND_NOT_FOUND`** : 해당 출석 라운드가 존재하지 않습니다.
62+
- **`ROUND_NOT_ACTIVE`** : 출석 라운드가 진행 중이 아닙니다.
63+
- **`NOT_SESSION_MEMBER`** : 출석 세션의 멤버가 아닙니다.
64+
- **`ALREADY_CHECKED_IN`** : 이미 출석 체크되었습니다.
65+
66+
""")
3467
@PostMapping("/check-in")
3568
public ResponseEntity<Void> checkIn(
3669
@AuthenticationPrincipal CustomUserDetails userDetails,
3770
@RequestBody AttendanceRoundQrTokenRequest request
3871
) {
3972
UUID userId = requireUserId(userDetails);
40-
attendanceService.checkIn(userId, userDetails.getName(), request);
73+
attendanceService.checkIn(userId, userDetails.getUsername(), request);
4174
return ResponseEntity.ok().build();
4275
}
4376

4477
/**
4578
* 라운드별 출석 명단 조회(관리자/OWNER)
4679
*/
47-
@Operation(summary = "라운드 출석 명단 조회", description = "특정 라운드의 출석 기록을 조회합니다. (관리자/OWNER)")
80+
@Operation(
81+
summary = "라운드 출석 명단 조회",
82+
description = """
83+
## 인증(JWT)
84+
- **필요**
85+
86+
## 권한
87+
- 세션 **MANAGER** 또는 **OWNER**
88+
89+
## 동작 설명
90+
- 특정 출석 라운드(`roundId`)에 기록된 모든 출석 데이터를 리스트로 반환
91+
92+
## 응답 바디 ( `List<AttendanceResponse>` )
93+
- **유저 정보**: `userId`, `userName`(이름)
94+
- **세션/라운드 정보**: 세션 제목, 라운드 이름, 장소, 시작 시간 등
95+
- **출석 상태**: `attendanceStatus` (PENDING, PRESENT, LATE, ABSENT, EXCUSED)
96+
- **상세 기록**: `checkedAt`(체크인 시각), `note`(비고), `checkInLatitude/Longitude`(위치 정보)
97+
98+
## 에러 코드
99+
- **`ROUND_NOT_FOUND`**: 해당 출석 라운드가 존재하지 않습니다.
100+
- **`NOT_SESSION_ADMIN`**: 세션 관리자 권한이 없습니다.
101+
102+
""")
48103
@GetMapping("/rounds/{roundId}/records")
49104
public ResponseEntity<List<AttendanceResponse>> getAttendancesByRound(
50105
@PathVariable UUID roundId,
@@ -55,38 +110,36 @@ public ResponseEntity<List<AttendanceResponse>> getAttendancesByRound(
55110
}
56111

57112
/**
58-
* 라운드 내 특정 유저 출석 상태 수정(관리자/OWNER)
59-
* PUT /api/attendance/rounds/{roundId}/users/{userId}
113+
* 라운드 내 특정 유저 출석 상태 수정(관리자/OWNER) PUT /api/attendance/rounds/{roundId}/users/{userId}
60114
*/
61115
@Operation(
62116
summary = "출석 상태 수정",
63117
description = """
64-
65-
## 인증(JWT): **필요**
66-
67-
68-
## 권한
69-
- **세션 관리자 / OWNER**
70-
71-
## 경로 파라미터
72-
- **`roundId`**: 출석 상태를 수정할 라운드 ID (`UUID`)
73-
- **`userId`**: 출석 상태를 수정할 대상 사용자 ID (`UUID`)
74-
75-
## 요청 바디 ( `AttendanceStatusUpdateRequest` )
76-
- **`status`**: 출석 상태 (필수)
77-
- 허용값 예시: `PRESENT`, `LATE`, `ABSENT`, `EXCUSED`
78-
- **`reason`**: 상태 수정 사유 (선택)
79-
- 예: 지각 사유, 공결 사유 등
80-
81-
## 동작 설명
82-
- 특정 라운드에서 특정 사용자의 출석 상태를 수정합니다.
83-
- 요청한 사용자가 해당 세션의 관리자/OWNER인지 검증합니다.
84-
- `status` 값과 `reason` 값을 기반으로 출석 상태를 반영합니다.
85-
86-
## 응답
87-
- **200 OK**
88-
- 수정된 출석 정보 (`AttendanceResponse`)
89-
""")
118+
119+
## 인증(JWT): **필요**
120+
121+
## 권한
122+
- 세션 **MANAGER** 또는 **OWNER**
123+
124+
## 경로 파라미터
125+
- **`roundId`**: 출석 상태를 수정할 라운드 ID (`UUID`)
126+
- **`userId`**: 출석 상태를 수정할 대상 사용자 ID (`UUID`)
127+
128+
## 요청 바디 ( `AttendanceStatusUpdateRequest` )
129+
- **`status`**: 출석 상태 (필수)
130+
- 허용값 예시: `PRESENT`, `LATE`, `ABSENT`, `EXCUSED`
131+
- **`reason`**: 상태 수정 사유 (선택)
132+
- 예: 지각 사유, 공결 사유 등
133+
134+
## 동작 설명
135+
- 특정 라운드에서 특정 사용자의 출석 상태를 수정
136+
- 요청한 사용자가 해당 세션의 관리자/OWNER인지 검증
137+
- `status` 값과 `reason` 값을 기반으로 출석 상태 반영
138+
139+
## 응답
140+
- **200 OK**
141+
- 수정된 출석 정보 (`AttendanceResponse`)
142+
""")
90143
@PutMapping("/rounds/{roundId}/users/{userId}")
91144
public ResponseEntity<AttendanceResponse> updateAttendanceStatus(
92145
@PathVariable UUID roundId,
@@ -95,35 +148,38 @@ public ResponseEntity<AttendanceResponse> updateAttendanceStatus(
95148
@Valid @RequestBody AttendanceStatusUpdateRequest request
96149
) {
97150
UUID adminUserId = requireUserId(userDetails);
98-
99-
// status가 enum이든 string이든 안전하게 문자열로 변환
100-
String statusStr = String.valueOf(request.getStatus());
101-
String reason = request.getReason();
102-
103151
AttendanceResponse response =
104-
attendanceService.updateAttendanceStatusByRound(adminUserId, roundId, userId, statusStr, reason);
152+
attendanceService.updateAttendanceStatusByRound(adminUserId, roundId, userId, request);
105153

106154
return ResponseEntity.ok(response);
107155
}
108156

109157
/**
110-
* (옵션) 내 출석 이력 조회
111-
* GET /api/attendance/me
158+
* (옵션) 내 출석 이력 조회 GET /api/attendance/me
112159
*/
113-
@Operation(summary = "내 출석 이력 조회", description = "로그인한 사용자의 출석 이력을 조회합니다.")
160+
@Operation(
161+
summary = "내 출석 이력 조회",
162+
description = """
163+
## 인증(JWT): **필요**
164+
165+
## 동작 설명
166+
- 현재 로그인한 사용자가 참여한 모든 세션 및 라운드의 출석 기록을 최신순으로 조회
167+
168+
## 응답 바디 ( `List<AttendanceResponse>` )
169+
- **유저 정보**: `userId`, `userName`(이름)
170+
- **세션/라운드 정보**: 세션 제목, 라운드 이름, 장소, 시작 시간 등
171+
- **출석 상태**: `attendanceStatus` (PENDING, PRESENT, LATE, ABSENT, EXCUSED)
172+
- **상세 기록**: `checkedAt`(체크인 시각), `note`(비고), `checkInLatitude/Longitude`(위치 정보)
173+
174+
## 에러 코드
175+
- **`USER_NOT_FOUND`**: 유저 정보를 찾을 수 없습니다.
176+
177+
""")
114178
@GetMapping("/me")
115179
public ResponseEntity<List<AttendanceResponse>> getMyAttendances(
116180
@AuthenticationPrincipal CustomUserDetails userDetails
117181
) {
118182
UUID userId = requireUserId(userDetails);
119183
return ResponseEntity.ok(attendanceService.getAttendancesByUser(userId));
120184
}
121-
122-
123-
124-
// ------- helper -------
125-
private UUID requireUserId(CustomUserDetails userDetails) {
126-
if (userDetails == null) throw new IllegalStateException("UNAUTHENTICATED");
127-
return userDetails.getUserId();
128-
}
129185
}

0 commit comments

Comments
 (0)