From ded80717fbde7c3ec2034d845adad8ecb90f96b7 Mon Sep 17 00:00:00 2001 From: chanhyeok oh <125783546+ochanhyeok@users.noreply.github.com> Date: Sun, 2 Nov 2025 15:15:03 +0900 Subject: [PATCH] =?UTF-8?q?[BE]=20SISC1-201=20[DOCS]=20Swagger=20=EA=B3=A0?= =?UTF-8?q?=EB=8F=84=ED=99=94=20-=20API=20=EC=9D=91=EB=8B=B5=20=ED=98=95?= =?UTF-8?q?=EC=8B=9D=20=EC=A0=95=EC=9D=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/AttendanceController.java | 28 ++++++ .../AttendanceSessionController.java | 68 ++++++++++++++ .../attendance/dto/AttendanceRequest.java | 33 +++++++ .../attendance/dto/AttendanceResponse.java | 71 +++++++++++++++ .../dto/AttendanceSessionRequest.java | 60 ++++++++++++ .../dto/AttendanceSessionResponse.java | 91 +++++++++++++++++++ 6 files changed, 351 insertions(+) diff --git a/backend/src/main/java/org/sejongisc/backend/attendance/controller/AttendanceController.java b/backend/src/main/java/org/sejongisc/backend/attendance/controller/AttendanceController.java index 42559260..3f004d50 100644 --- a/backend/src/main/java/org/sejongisc/backend/attendance/controller/AttendanceController.java +++ b/backend/src/main/java/org/sejongisc/backend/attendance/controller/AttendanceController.java @@ -1,5 +1,7 @@ package org.sejongisc.backend.attendance.controller; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -20,6 +22,10 @@ @RequiredArgsConstructor @RequestMapping("/api/attendance") @Slf4j +@Tag( + name = "출석(Attendance) API", + description = "학생 출석 체크인 및 관리자 출석 현황 조회 관련 API" +) public class AttendanceController { private final AttendanceService attendanceService; @@ -30,6 +36,12 @@ public class AttendanceController { * - 위치 범위, 시간 위도우 검증 포함 * - 중복 출석 방지 */ + @Operation( + summary = "출석 체크인", + description = "학생이 출석 코드와 GPS 위치를 제시하여 출석 체크인을 진행합니다. " + + "세션의 지정된 위치 범위 내에 있어야 하며, 시간 윈도우 내에만 체크인이 가능합니다. " + + "시작 시간 5분 이내는 PRESENT, 이후는 LATE로 자동 판별됩니다." + ) @PostMapping("/sessions/{sessionId}/check-in") public ResponseEntity checkIn( @PathVariable UUID sessionId, @@ -50,6 +62,11 @@ public ResponseEntity checkIn( * - 특정 세션의 모든 출석 기록 조회 * - 출석 시간 순으로 정렬 */ + @Operation( + summary = "세션별 출석 목록 조회", + description = "특정 세션에 참가한 모든 학생의 출석 기록을 조회합니다. (관리자 전용) " + + "출석 시간 순으로 정렬되며, 각 학생의 상태, 체크인 시간, 포인트 등이 포함됩니다." + ) @GetMapping("/sessions/{sessionId}/attendances") @PreAuthorize("hasRole('PRESIDENT') or hasRole('VICE_PRESIDENT')") public ResponseEntity> getAttendancesBySession(@PathVariable UUID sessionId) { @@ -65,6 +82,11 @@ public ResponseEntity> getAttendancesBySession(@PathVar * - 로그인한 사용자의 모든 출석 기록 조회 * - 최신 순으로 정렬 */ + @Operation( + summary = "내 출석 기록 조회", + description = "로그인한 사용자의 모든 출석 기록을 최신 순으로 조회합니다. " + + "각 출석 기록에는 세션 정보, 출석 상태, 체크인 시간, 획득 포인트 등이 포함됩니다." + ) @GetMapping("/history") public ResponseEntity> getMyAttendances( @AuthenticationPrincipal CustomUserDetails userDetails) { @@ -80,6 +102,12 @@ public ResponseEntity> getMyAttendances( * - PRESENT/LATE/ABSENT 등으로 상태 변경 * - 수정 사유 기록 가능 */ + @Operation( + summary = "출석 상태 수정", + description = "특정 학생의 출석 상태를 변경합니다. (관리자 전용) " + + "PRESENT(출석), LATE(지각), ABSENT(결석), EXCUSED(사유결석) 등의 상태로 변경 가능하며, " + + "변경 사유를 함께 기록할 수 있습니다." + ) @PostMapping("/sessions/{sessionId}/attendances/{memberId}") @PreAuthorize("hasRole('PRESIDENT') or hasRole('VICE_PRESIDENT')") public ResponseEntity updateAttendanceStatus( diff --git a/backend/src/main/java/org/sejongisc/backend/attendance/controller/AttendanceSessionController.java b/backend/src/main/java/org/sejongisc/backend/attendance/controller/AttendanceSessionController.java index 19db2323..c2d3c136 100644 --- a/backend/src/main/java/org/sejongisc/backend/attendance/controller/AttendanceSessionController.java +++ b/backend/src/main/java/org/sejongisc/backend/attendance/controller/AttendanceSessionController.java @@ -1,5 +1,7 @@ package org.sejongisc.backend.attendance.controller; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -19,6 +21,10 @@ @RequiredArgsConstructor @RequestMapping("/api/attendance/sessions") @Slf4j +@Tag( + name = "출석 세션(Attendance Session) API", + description = "출석 세션 생성, 조회, 수정, 삭제 및 상태 관리 관련 API" +) public class AttendanceSessionController { private final AttendanceSessionService attendanceSessionService; @@ -29,6 +35,12 @@ public class AttendanceSessionController { * - GPS 위치 및 반경 설정 * - 시간 윈도우 설정 */ + @Operation( + summary = "출석 세션 생성", + description = "새로운 출석 세션을 생성합니다. (관리자 전용) " + + "6자리 랜덤 코드가 자동 생성되며, GPS 위치 정보, 시간 윈도우, " + + "보상 포인트 등을 설정할 수 있습니다." + ) @PostMapping @PreAuthorize("hasRole('PRESIDENT') or hasRole('VICE_PRESIDENT')") public ResponseEntity createSession(@Valid @RequestBody AttendanceSessionRequest request) { @@ -46,6 +58,11 @@ public ResponseEntity createSession(@Valid @RequestBo * - 세션 ID로 상세 정보 조회 * - 남은 시간, 참여자 수 등 포함 */ + @Operation( + summary = "세션 상세 조회", + description = "세션 ID로 특정 세션의 상세 정보를 조회합니다. " + + "남은 체크인 시간, 현재 참여자 수, 세션 상태 등의 정보가 포함됩니다." + ) @GetMapping("/{sessionId}") public ResponseEntity getSession(@PathVariable UUID sessionId) { log.info("출석 세션 조회: 세션ID={}", sessionId); @@ -60,6 +77,11 @@ public ResponseEntity getSession(@PathVariable UUID s * - 학생이 출석 코드 입력 시 사용 * - 체크인 가능 여부 확인 */ + @Operation( + summary = "출석 코드로 세션 조회", + description = "6자리 출석 코드를 입력하여 해당 세션을 조회합니다. (학생용) " + + "체크인 가능 여부, 남은 시간 등의 정보를 확인할 수 있습니다." + ) @GetMapping("/code/{code}") public ResponseEntity getSessionByCode(@PathVariable String code) { log.info("출석 코드로 세션 조회: 코드={}", code); @@ -74,6 +96,11 @@ public ResponseEntity getSessionByCode(@PathVariable * - 최신 순으로 정렬 * - 공개/비공개 세션 모두 포함 */ + @Operation( + summary = "모든 세션 목록 조회", + description = "생성된 모든 출석 세션을 최신 순으로 조회합니다. " + + "공개/비공개 세션이 모두 포함되며, 관리자는 모든 세션을 볼 수 있습니다." + ) @GetMapping public ResponseEntity> getAllSessions() { log.info("모든 출석 세션 조회"); @@ -88,6 +115,11 @@ public ResponseEntity> getAllSessions() { * - 학생들이 볼 수 있는 공개 세션만 조회 * - 최신 순으로 정렬 */ + @Operation( + summary = "공개 세션 목록 조회", + description = "학생들이 볼 수 있는 공개 세션들을 최신 순으로 조회합니다. " + + "비공개 세션은 제외됩니다." + ) @GetMapping("/public") public ResponseEntity> getPublicSessions() { log.info("공개 출석 세션 조회"); @@ -102,6 +134,11 @@ public ResponseEntity> getPublicSessions() { * - 체크인 가능한 세션들만 조회 * - 시작시간 ~ 종료 시간 범위 내 */ + @Operation( + summary = "활성 세션 목록 조회", + description = "현재 체크인이 가능한 활성 세션들을 조회합니다. " + + "세션 시작 시간부터 시간 윈도우 종료까지 범위 내인 세션들만 조회됩니다." + ) @GetMapping("/active") public ResponseEntity> getActiveSessions() { log.info("활성 출석 세션 조회"); @@ -115,6 +152,11 @@ public ResponseEntity> getActiveSessions() { * 태그별 세션 목록 조회 * - "금융IT", "동아리 전체" 등 태그로 필터링 */ + @Operation( + summary = "태그별 세션 목록 조회", + description = "특정 태그를 가진 세션들을 조회합니다. " + + "예: '금융IT', '동아리 전체', '스터디' 등으로 세션을 분류할 수 있습니다." + ) @GetMapping("/tag/{tag}") public ResponseEntity> getSessionsByTag(@PathVariable String tag) { log.info("태그별 출석 세션 조회: 태그={}", tag); @@ -128,6 +170,11 @@ public ResponseEntity> getSessionsByTag(@PathVar * 상태별 세션 목록 조회 (관리자용) * - UPCOMING/OPEN/CLOSED 상태별 필터링 */ + @Operation( + summary = "상태별 세션 목록 조회", + description = "특정 상태의 세션들을 조회합니다. (관리자 전용) " + + "UPCOMING(예정), OPEN(진행중), CLOSED(종료) 상태별로 필터링할 수 있습니다." + ) @GetMapping("/status/{status}") @PreAuthorize("hasRole('PRESIDENT') or hasRole('VICE_PRESIDENT')") public ResponseEntity> getSessionsByStatus(@PathVariable SessionStatus status) { @@ -143,6 +190,12 @@ public ResponseEntity> getSessionsByStatus(@Path * - 제목, 시간, 위치, 반경 등 수정 가능 * - 코드는 변경 불가 */ + @Operation( + summary = "세션 정보 수정", + description = "세션의 기본 정보를 수정합니다. (관리자 전용) " + + "제목, 태그, 시간, GPS 위치, 반경, 포인트 등을 수정할 수 있으며, " + + "6자리 코드는 변경할 수 없습니다." + ) @PutMapping("/{sessionId}") @PreAuthorize("hasRole('PRESIDENT') or hasRole('VICE_PRESIDENT')") public ResponseEntity updateSession( @@ -163,6 +216,11 @@ public ResponseEntity updateSession( * - 세션 상태를 OPEN으로 변경 * - 체크인 수동 활성화 */ + @Operation( + summary = "세션 활성화", + description = "세션을 수동으로 활성화하여 즉시 체크인을 가능하게 합니다. (관리자 전용) " + + "세션 상태가 OPEN으로 변경되어 학생들이 체크인할 수 있습니다." + ) @PostMapping("/{sessionId}/activate") @PreAuthorize("hasRole('PRESIDENT') or hasRole('VICE_PRESIDENT')") public ResponseEntity activateSession(@PathVariable UUID sessionId) { @@ -180,6 +238,11 @@ public ResponseEntity activateSession(@PathVariable UUID sessionId) { * - 세션 상태를 CLOSED로 변경 * - 체크인 수동 종료 */ + @Operation( + summary = "세션 종료", + description = "세션을 종료합니다. (관리자 전용) " + + "세션 상태가 CLOSED로 변경되어 더 이상 체크인이 불가능합니다." + ) @PostMapping("/{sessionId}/close") @PreAuthorize("hasRole('PRESIDENT') or hasRole('VICE_PRESIDENT')") public ResponseEntity closeSession(@PathVariable UUID sessionId) { @@ -197,6 +260,11 @@ public ResponseEntity closeSession(@PathVariable UUID sessionId) { * - 세션 완전 삭제 (출석 기록도 함께 삭제) * - 주의: 복구 불가 */ + @Operation( + summary = "세션 삭제", + description = "세션을 완전히 삭제합니다. (관리자 전용) " + + "⚠️ 주의: 해당 세션의 모든 출석 기록이 함께 삭제되며, 복구가 불가능합니다." + ) @DeleteMapping("/{sessionId}") @PreAuthorize("hasRole('PRESIDENT') or hasRole('VICE_PRESIDENT')") public ResponseEntity deleteSession(@PathVariable UUID sessionId) { diff --git a/backend/src/main/java/org/sejongisc/backend/attendance/dto/AttendanceRequest.java b/backend/src/main/java/org/sejongisc/backend/attendance/dto/AttendanceRequest.java index 11a1f7b2..d32463ad 100644 --- a/backend/src/main/java/org/sejongisc/backend/attendance/dto/AttendanceRequest.java +++ b/backend/src/main/java/org/sejongisc/backend/attendance/dto/AttendanceRequest.java @@ -1,5 +1,6 @@ package org.sejongisc.backend.attendance.dto; +import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.*; import lombok.AllArgsConstructor; import lombok.Builder; @@ -11,26 +12,58 @@ @Builder @NoArgsConstructor @AllArgsConstructor +@Schema( + title = "출석 체크인 요청", + description = "학생이 출석 체크인 시 제출하는 요청 객체. 출석 코드, GPS 위치 정보를 포함합니다." +) public class AttendanceRequest { // === 출석 체크용 필드들 === + @Schema( + description = "출석 세션의 6자리 코드. 관리자가 생성한 코드를 입력합니다.", + example = "ABC123", + minLength = 6, + maxLength = 6 + ) @NotBlank(message = "출석 코드는 필수입니다") @Size(min = 6, max = 6, message = "출석 코드는 6자리여야 합니다") private String code; + @Schema( + description = "체크인 위치의 위도 (latitude). WGS84 좌표계 사용. 범위: -90 ~ 90", + example = "37.4979", + minimum = "-90.0", + maximum = "90.0" + ) @NotNull(message = "위도는 필수입니다") @DecimalMin(value = "-90.0", message = "위도는 -90 이상이어야 합니다") @DecimalMax(value = "90.0", message = "위도는 90 이하이어야 합니다") private Double latitude; + @Schema( + description = "체크인 위치의 경도 (longitude). WGS84 좌표계 사용. 범위: -180 ~ 180", + example = "127.0276", + minimum = "-180.0", + maximum = "180.0" + ) @NotNull(message = "경도는 필수입니다") @DecimalMin(value = "-180.0", message = "경도는 -180 이상이어야 합니다") @DecimalMax(value = "180.0", message = "경도는 180 이하이어야 합니다") private Double longitude; + @Schema( + description = "체크인 시 추가 메모. 선택 사항입니다.", + example = "교실 앞에서 체크인", + maxLength = 500 + ) @Size(max = 500, message = "메모는 500자 이하여야 합니다") private String note; + @Schema( + description = "체크인에 사용한 디바이스 정보. 예: 'iPhone 12', 'Android Pixel 6' 등", + example = "iPhone 14 Pro", + maxLength = 200 + ) @Size(max = 200, message = "디바이스 정보는 200자 이하여야 합니다") private String deviceInfo; diff --git a/backend/src/main/java/org/sejongisc/backend/attendance/dto/AttendanceResponse.java b/backend/src/main/java/org/sejongisc/backend/attendance/dto/AttendanceResponse.java index 8322052f..c76056dd 100644 --- a/backend/src/main/java/org/sejongisc/backend/attendance/dto/AttendanceResponse.java +++ b/backend/src/main/java/org/sejongisc/backend/attendance/dto/AttendanceResponse.java @@ -1,6 +1,7 @@ package org.sejongisc.backend.attendance.dto; import com.fasterxml.jackson.annotation.JsonFormat; +import io.swagger.v3.oas.annotations.media.Schema; import lombok.*; import org.sejongisc.backend.attendance.entity.AttendanceStatus; @@ -11,27 +12,97 @@ @Builder @NoArgsConstructor @AllArgsConstructor +@Schema( + title = "출석 정보 응답", + description = "학생의 출석 기록 정보. 체크인 시간, 출석 상태, 포인트, GPS 정보 등을 포함합니다." +) public class AttendanceResponse { + @Schema( + description = "출석 기록의 고유 ID", + example = "550e8400-e29b-41d4-a716-446655440000" + ) private UUID attendanceId; + + @Schema( + description = "체크인한 학생의 사용자 ID", + example = "f47ac10b-58cc-4372-a567-0e02b2c3d479" + ) private UUID userId; + + @Schema( + description = "체크인한 학생의 이름", + example = "김철수" + ) private String userName; + + @Schema( + description = "해당 출석 세션의 ID", + example = "a1b2c3d4-e5f6-7890-abcd-ef1234567890" + ) private UUID attendanceSessionId; + + @Schema( + description = "출석 상태. PRESENT(출석), LATE(지각), ABSENT(결석), EXCUSED(사유결석)", + example = "PRESENT", + implementation = AttendanceStatus.class + ) private AttendanceStatus attendanceStatus; + @Schema( + description = "실제 체크인 시간 (ISO 8601 형식)", + example = "2024-10-31 14:30:15" + ) @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") private LocalDateTime checkedAt; + @Schema( + description = "체크인으로 인해 획득한 포인트", + example = "10" + ) private Integer awardedPoints; + + @Schema( + description = "체크인 시 작성한 메모", + example = "교실 입구에서 체크인했습니다" + ) private String note; + + @Schema( + description = "체크인 시의 위도", + example = "37.4979" + ) private Double checkInLatitude; + + @Schema( + description = "체크인 시의 경도", + example = "127.0276" + ) private Double checkInLongitude; + + @Schema( + description = "체크인에 사용한 디바이스 정보", + example = "iPhone 14 Pro" + ) private String deviceInfo; + + @Schema( + description = "지각 여부. true면 지각(5분 이상 경과 후 체크인)", + example = "false" + ) private boolean isLate; + @Schema( + description = "출석 기록 생성 시간", + example = "2024-10-31 14:30:15" + ) @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") private LocalDateTime createdAt; + @Schema( + description = "출석 기록 최종 수정 시간", + example = "2024-10-31 15:00:00" + ) @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") private LocalDateTime updatedAt; } diff --git a/backend/src/main/java/org/sejongisc/backend/attendance/dto/AttendanceSessionRequest.java b/backend/src/main/java/org/sejongisc/backend/attendance/dto/AttendanceSessionRequest.java index c44126ac..a7f4e663 100644 --- a/backend/src/main/java/org/sejongisc/backend/attendance/dto/AttendanceSessionRequest.java +++ b/backend/src/main/java/org/sejongisc/backend/attendance/dto/AttendanceSessionRequest.java @@ -1,5 +1,6 @@ package org.sejongisc.backend.attendance.dto; +import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.*; import lombok.*; import org.sejongisc.backend.attendance.entity.SessionVisibility; @@ -10,38 +11,97 @@ @Builder @NoArgsConstructor @AllArgsConstructor +@Schema( + title = "출석 세션 생성/수정 요청", + description = "관리자가 출석 세션을 생성하거나 수정할 때 사용하는 요청 객체. " + + "세션의 기본 정보, 시간, 위치, 포인트 설정을 포함합니다." +) public class AttendanceSessionRequest { + @Schema( + description = "세션의 제목/이름", + example = "2024년 10월 동아리 정기 모임", + maxLength = 100 + ) @NotBlank(message = "제목은 필수입니다") @Size(max = 100, message = "제목은 100자 이하여야 합니다") private String title; + @Schema( + description = "세션의 분류 태그. 예: '금융IT', '동아리 전체', '스터디'", + example = "금융IT", + maxLength = 50 + ) @Size(max = 50, message = "태그는 50자 이하여야 합니다") private String tag; + @Schema( + description = "세션 시작 시간 (ISO 8601 형식). 현재 시간 이후여야 합니다.", + example = "2024-11-15T14:00:00", + type = "string", + format = "date-time" + ) @NotNull(message = "시작 시간은 필수입니다") @Future(message = "시작 시간은 현재 시간 이후여야 합니다") private LocalDateTime startsAt; + @Schema( + description = "출석 체크인이 가능한 시간 윈도우 (초 단위). " + + "범위: 300초(5분) ~ 14400초(4시간)", + example = "1800", + minimum = "300", + maximum = "14400" + ) @Min(value = 300, message = "최소 5분 이상이어야 합니다") @Max(value = 14400, message = "최대 4시간 설정 가능합니다") private Integer windowSeconds; + @Schema( + description = "출석 완료 시 지급할 포인트", + example = "10", + minimum = "0" + ) @Min(value = 0, message = "포인트는 0 이상이어야 합니다") private Integer rewardPoints; + @Schema( + description = "세션 개최 위치의 위도 (latitude). WGS84 좌표계. 선택 사항", + example = "37.4979", + minimum = "-90.0", + maximum = "90.0" + ) @DecimalMin(value = "-90.0", message = "위도는 -90 이상이어야 합니다") @DecimalMax(value = "90.0", message = "위도는 90 이하이어야 합니다") private Double latitude; + @Schema( + description = "세션 개최 위치의 경도 (longitude). WGS84 좌표계. 선택 사항", + example = "127.0276", + minimum = "-180.0", + maximum = "180.0" + ) @DecimalMin(value = "-180.0", message = "경도는 -180 이상이어야 합니다") @DecimalMax(value = "180.0", message = "경도는 180 이하이어야 합니다") private Double longitude; + @Schema( + description = "GPS 기반 위치 검증을 위한 반경 (미터 단위). " + + "지정된 위치에서 이 반경 내에 있어야만 체크인이 가능합니다. " + + "범위: 1m ~ 500m", + example = "100", + minimum = "1", + maximum = "500" + ) @Min(value = 1, message = "반경은 1m 이상이어야 합니다") @Max(value = 500, message = "반경은 500m 이하여야 합니다") private Integer radiusMeters; + @Schema( + description = "세션의 공개 범위. PUBLIC(공개) 또는 PRIVATE(비공개). " + + "공개 세션은 모든 사용자에게 보이고, 비공개는 관리자에게만 보입니다.", + example = "PUBLIC", + implementation = SessionVisibility.class + ) private SessionVisibility visibility; } diff --git a/backend/src/main/java/org/sejongisc/backend/attendance/dto/AttendanceSessionResponse.java b/backend/src/main/java/org/sejongisc/backend/attendance/dto/AttendanceSessionResponse.java index 7185cb4e..f3424863 100644 --- a/backend/src/main/java/org/sejongisc/backend/attendance/dto/AttendanceSessionResponse.java +++ b/backend/src/main/java/org/sejongisc/backend/attendance/dto/AttendanceSessionResponse.java @@ -1,6 +1,7 @@ package org.sejongisc.backend.attendance.dto; import com.fasterxml.jackson.annotation.JsonFormat; +import io.swagger.v3.oas.annotations.media.Schema; import lombok.*; import org.sejongisc.backend.attendance.entity.SessionStatus; import org.sejongisc.backend.attendance.entity.SessionVisibility; @@ -12,34 +13,124 @@ @Builder @NoArgsConstructor @AllArgsConstructor +@Schema( + title = "출석 세션 응답", + description = "출석 세션의 상세 정보. 세션 설정, 상태, 남은 시간, 참여자 수 등을 포함합니다." +) public class AttendanceSessionResponse { + @Schema( + description = "출석 세션의 고유 ID", + example = "a1b2c3d4-e5f6-7890-abcd-ef1234567890" + ) private UUID attendanceSessionId; + + @Schema( + description = "세션의 제목/이름", + example = "2024년 10월 동아리 정기 모임" + ) private String title; + + @Schema( + description = "세션의 분류 태그", + example = "금융IT" + ) private String tag; + @Schema( + description = "세션 시작 시간", + example = "2024-11-15 14:00:00" + ) @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") private LocalDateTime startsAt; + @Schema( + description = "출석 체크인이 가능한 시간 윈도우 (초 단위)", + example = "1800" + ) private Integer windowSeconds; + + @Schema( + description = "출석 세션의 6자리 코드. 학생들이 체크인 시 입력합니다.", + example = "ABC123" + ) private String code; + + @Schema( + description = "출석 완료 시 지급할 포인트", + example = "10" + ) private Integer rewardPoints; + + @Schema( + description = "세션 개최 위치의 위도", + example = "37.4979" + ) private Double latitude; + + @Schema( + description = "세션 개최 위치의 경도", + example = "127.0276" + ) private Double longitude; + + @Schema( + description = "GPS 기반 위치 검증 반경 (미터 단위)", + example = "100" + ) private Integer radiusMeters; + + @Schema( + description = "세션의 공개 범위. PUBLIC(공개) 또는 PRIVATE(비공개)", + example = "PUBLIC", + implementation = SessionVisibility.class + ) private SessionVisibility visibility; + + @Schema( + description = "세션의 현재 상태. UPCOMING(예정), OPEN(진행중), CLOSED(종료)", + example = "OPEN", + implementation = SessionStatus.class + ) private SessionStatus status; + @Schema( + description = "세션 레코드 생성 시간", + example = "2024-10-31 10:00:00" + ) @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") private LocalDateTime createdAt; + @Schema( + description = "세션 레코드 최종 수정 시간", + example = "2024-10-31 11:30:00" + ) @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") private LocalDateTime updatedAt; + @Schema( + description = "세션의 예상 종료 시간 (시작시간 + 윈도우)", + example = "2024-11-15 14:30:00" + ) @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") private LocalDateTime endsAt; + @Schema( + description = "현재부터 체크인 마감까지 남은 시간 (초 단위). 음수이면 마감됨", + example = "1234" + ) private Long remainingSeconds; + + @Schema( + description = "현재 체크인이 가능한 상태인지 여부. " + + "true면 지금 체크인 가능, false면 불가능", + example = "true" + ) private boolean checkInAvailable; + + @Schema( + description = "현재 세션에 참여한 학생 수", + example = "25" + ) private Integer participantCount; }