diff --git a/ontime-back/src/main/java/devkor/ontime_back/controller/AlarmController.java b/ontime-back/src/main/java/devkor/ontime_back/controller/AlarmController.java index c4e498b..a656df3 100644 --- a/ontime-back/src/main/java/devkor/ontime_back/controller/AlarmController.java +++ b/ontime-back/src/main/java/devkor/ontime_back/controller/AlarmController.java @@ -4,6 +4,11 @@ import devkor.ontime_back.response.ApiResponseForm; import devkor.ontime_back.service.AlarmService; import devkor.ontime_back.service.UserAuthService; +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 jakarta.servlet.http.HttpServletRequest; import lombok.RequiredArgsConstructor; import org.springframework.http.HttpStatus; @@ -19,6 +24,14 @@ public class AlarmController { private final UserAuthService userAuthService; private final AlarmService alarmService; + @Operation(summary = "Get current user's native alarm settings") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "Alarm settings lookup succeeded", content = @Content( + mediaType = "application/json", + schema = @Schema(example = "{\n \"status\": \"success\",\n \"code\": 200,\n \"message\": \"OK\",\n \"data\": {\n \"alarmsEnabled\": true,\n \"defaultAlarmOffsetMinutes\": 10,\n \"updatedAt\": \"2026-05-05T00:00:00Z\"\n }\n}") + )), + @ApiResponse(responseCode = "4XX", description = "Alarm settings lookup failed", content = @Content(mediaType = "application/json", schema = @Schema(example = "Failure message"))) + }) @GetMapping("/users/me/alarm-settings") public ResponseEntity> getAlarmSettings(HttpServletRequest request) { Long userId = userAuthService.getUserIdFromToken(request); @@ -26,6 +39,24 @@ public ResponseEntity> getAlarmSetting .body(ApiResponseForm.success(alarmService.getAlarmSettings(userId))); } + @Operation( + summary = "Update current user's native alarm settings", + requestBody = @io.swagger.v3.oas.annotations.parameters.RequestBody( + description = "Partial alarm settings update. Omit fields that should not change.", + required = true, + content = @Content(schema = @Schema( + type = "object", + example = "{\"alarmsEnabled\": true, \"defaultAlarmOffsetMinutes\": 10}" + )) + ) + ) + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "Alarm settings update succeeded", content = @Content( + mediaType = "application/json", + schema = @Schema(example = "{\n \"status\": \"success\",\n \"code\": 200,\n \"message\": \"OK\",\n \"data\": {\n \"alarmsEnabled\": true,\n \"defaultAlarmOffsetMinutes\": 10,\n \"updatedAt\": \"2026-05-05T00:00:00Z\"\n }\n}") + )), + @ApiResponse(responseCode = "4XX", description = "Alarm settings update failed", content = @Content(mediaType = "application/json", schema = @Schema(example = "Failure message"))) + }) @PatchMapping("/users/me/alarm-settings") public ResponseEntity> patchAlarmSettings( HttpServletRequest request, @@ -35,6 +66,24 @@ public ResponseEntity> patchAlarmSetti .body(ApiResponseForm.success(alarmService.patchAlarmSettings(userId, requestBody))); } + @Operation( + summary = "Register current device for native alarm ownership", + requestBody = @io.swagger.v3.oas.annotations.parameters.RequestBody( + description = "Current device metadata used to bind the logged-in access token to native alarm status.", + required = true, + content = @Content(schema = @Schema( + type = "object", + example = "{\"deviceId\": \"ios-device-000001\", \"platform\": \"ios\", \"appVersion\": \"1.2.3\", \"osVersion\": \"iOS 18.0\", \"supportsNativeAlarm\": true, \"nativeAlarmProvider\": \"iosAlarmKit\", \"fallbackProvider\": \"localNotification\"}" + )) + ) + ) + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "Current device registration succeeded", content = @Content( + mediaType = "application/json", + schema = @Schema(example = "{\n \"status\": \"success\",\n \"code\": 200,\n \"message\": \"OK\",\n \"data\": {\n \"deviceId\": \"ios-device-000001\",\n \"active\": true,\n \"lastSeenAt\": \"2026-05-05T00:00:00Z\"\n }\n}") + )), + @ApiResponse(responseCode = "4XX", description = "Current device registration failed", content = @Content(mediaType = "application/json", schema = @Schema(example = "Failure message"))) + }) @PutMapping("/users/me/devices/current") public ResponseEntity> registerCurrentDevice( HttpServletRequest request, @@ -48,6 +97,23 @@ public ResponseEntity> registerCu userAuthService.getRefreshTokenFromRequest(request)))); } + @Operation( + summary = "Unregister current device from native alarm ownership", + requestBody = @io.swagger.v3.oas.annotations.parameters.RequestBody( + description = "Optional device ID. If omitted, the device bound to the access token is unregistered.", + content = @Content(schema = @Schema( + type = "object", + example = "{\"deviceId\": \"ios-device-000001\"}" + )) + ) + ) + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "Current device unregister succeeded", content = @Content( + mediaType = "application/json", + schema = @Schema(example = "{\n \"status\": \"success\",\n \"code\": 200,\n \"message\": \"OK\",\n \"data\": {\n \"active\": false\n }\n}") + )), + @ApiResponse(responseCode = "4XX", description = "Current device unregister failed", content = @Content(mediaType = "application/json", schema = @Schema(example = "Failure message"))) + }) @DeleteMapping("/users/me/devices/current") public ResponseEntity> unregisterCurrentDevice( HttpServletRequest request, @@ -60,6 +126,24 @@ public ResponseEntity> unregis userAuthService.getAccessTokenFromRequest(request)))); } + @Operation( + summary = "Report current native alarm reconciliation status", + requestBody = @io.swagger.v3.oas.annotations.parameters.RequestBody( + description = "Native alarm reconciliation status for the current device and schedule window.", + required = true, + content = @Content(schema = @Schema( + type = "object", + example = "{\"deviceId\": \"ios-device-000001\", \"reconciledAt\": \"2026-05-05T09:00:00+09:00\", \"scheduleWindowStart\": \"2026-05-05T00:00:00\", \"scheduleWindowEnd\": \"2026-05-06T00:00:00\", \"alarmCoverageStart\": \"2026-05-05T00:00:00\", \"alarmCoverageEnd\": \"2026-05-06T00:00:00\", \"status\": \"armed\", \"permissionIssue\": null, \"nativeAlarmProvider\": \"iosAlarmKit\", \"fallbackProvider\": \"localNotification\", \"armedScheduleCount\": 1, \"armedScheduleIds\": [\"3fa85f64-5717-4562-b3fc-2c963f66afe5\"], \"skippedScheduleCount\": 0, \"failures\": []}" + )) + ) + ) + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "Alarm status report succeeded", content = @Content( + mediaType = "application/json", + schema = @Schema(example = "{\n \"status\": \"success\",\n \"code\": 200,\n \"message\": \"OK\",\n \"data\": {\n \"received\": true\n }\n}") + )), + @ApiResponse(responseCode = "4XX", description = "Alarm status report failed", content = @Content(mediaType = "application/json", schema = @Schema(example = "Failure message"))) + }) @PostMapping("/users/me/alarm-status") public ResponseEntity> reportAlarmStatus( HttpServletRequest request, @@ -72,6 +156,14 @@ public ResponseEntity> reportAlarm userAuthService.getAccessTokenFromRequest(request)))); } + @Operation(summary = "Get current native alarm reconciliation status") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "Current alarm status lookup succeeded", content = @Content( + mediaType = "application/json", + schema = @Schema(example = "{\n \"status\": \"success\",\n \"code\": 200,\n \"message\": \"OK\",\n \"data\": {\n \"deviceId\": \"ios-device-000001\",\n \"active\": true,\n \"platform\": \"ios\",\n \"appVersion\": \"1.2.3\",\n \"osVersion\": \"iOS 18.0\",\n \"supportsNativeAlarm\": true,\n \"nativeAlarmProvider\": \"iosAlarmKit\",\n \"fallbackProvider\": \"localNotification\",\n \"lastSeenAt\": \"2026-05-05T00:00:00Z\",\n \"reconciledAt\": \"2026-05-05T00:00:00Z\",\n \"scheduleWindowStart\": \"2026-05-05T00:00:00\",\n \"scheduleWindowEnd\": \"2026-05-06T00:00:00\",\n \"alarmCoverageStart\": \"2026-05-05T00:00:00\",\n \"alarmCoverageEnd\": \"2026-05-06T00:00:00\",\n \"status\": \"armed\",\n \"permissionIssue\": null,\n \"armedScheduleCount\": 1,\n \"armedScheduleIds\": [\"3fa85f64-5717-4562-b3fc-2c963f66afe5\"],\n \"skippedScheduleCount\": 0,\n \"failures\": [],\n \"updatedAt\": \"2026-05-05T00:00:00Z\"\n }\n}") + )), + @ApiResponse(responseCode = "4XX", description = "Current alarm status lookup failed", content = @Content(mediaType = "application/json", schema = @Schema(example = "Failure message"))) + }) @GetMapping("/users/me/alarm-status") public ResponseEntity> getCurrentAlarmStatus(HttpServletRequest request) { Long userId = userAuthService.getUserIdFromToken(request); diff --git a/ontime-back/src/main/java/devkor/ontime_back/controller/FirebaseTokenController.java b/ontime-back/src/main/java/devkor/ontime_back/controller/FirebaseTokenController.java index 8350e01..1e931f1 100644 --- a/ontime-back/src/main/java/devkor/ontime_back/controller/FirebaseTokenController.java +++ b/ontime-back/src/main/java/devkor/ontime_back/controller/FirebaseTokenController.java @@ -32,7 +32,7 @@ public class FirebaseTokenController { content = @Content( schema = @Schema( type = "object", - example = "{\"firebaseToken\": \"token1234abcd(실제로는 firebase에서 받은 토큰을 기입해야 함)\"}" + example = "{\"firebaseToken\": \"token1234abcd(실제로는 firebase에서 받은 토큰을 기입해야 함)\", \"deviceId\": \"ios-device-000001\"}" ) ) ) diff --git a/ontime-back/src/main/java/devkor/ontime_back/controller/ScheduleController.java b/ontime-back/src/main/java/devkor/ontime_back/controller/ScheduleController.java index 4fe6983..6cc271a 100644 --- a/ontime-back/src/main/java/devkor/ontime_back/controller/ScheduleController.java +++ b/ontime-back/src/main/java/devkor/ontime_back/controller/ScheduleController.java @@ -70,10 +70,36 @@ public ResponseEntity>> getPeriodSchedule(Http return ResponseEntity.status(HttpStatus.OK).body(ApiResponseForm.success(schedules)); } + @Operation(summary = "Native alarm schedule window lookup", + requestBody = @io.swagger.v3.oas.annotations.parameters.RequestBody( + description = "No JSON body is required. Send access token in the header and use startDate/endDate query parameters." + ) + ) + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "Alarm schedule window lookup succeeded", + content = @Content( + mediaType = "application/json", + schema = @Schema( + example = "{\n \"status\": \"success\",\n \"code\": 200,\n \"message\": \"OK\",\n \"data\": [\n {\n \"scheduleId\": \"3fa85f64-5717-4562-b3fc-2c963f66afe5\",\n \"scheduleName\": \"Morning meeting\",\n \"place\": {\n \"placeId\": \"3fa85f64-5717-4562-b3fc-2c963f66afe6\",\n \"placeName\": \"Office\"\n },\n \"scheduleTime\": \"2026-05-05T09:30:00\",\n \"moveTime\": 20,\n \"scheduleSpareTime\": 10,\n \"doneStatus\": \"NOT_ENDED\",\n \"preparationStartTime\": \"2026-05-05T08:40:00\",\n \"defaultAlarmTime\": \"2026-05-05T08:30:00\",\n \"preparations\": [],\n \"alarmSettings\": null\n }\n ]\n}" + ) + )), + @ApiResponse(responseCode = "4XX", description = "Alarm schedule window lookup failed", + content = @Content( + mediaType = "application/json", + schema = @Schema(example = "Failure message") + ) + ) + }) @GetMapping("/alarm-window") public ResponseEntity>> getAlarmWindowSchedules( HttpServletRequest request, + @Parameter(description = "Alarm window start date-time. Supports ISO local date-time or ISO offset date-time.", + required = true, + example = "2026-05-05T00:00:00") @RequestParam String startDate, + @Parameter(description = "Alarm window end date-time. Supports ISO local date-time or ISO offset date-time.", + required = true, + example = "2026-05-06T00:00:00") @RequestParam String endDate) { Long userId = userAuthService.getUserIdFromToken(request); List schedules = scheduleService.getAlarmWindowSchedules(