Skip to content

Commit 22ccd17

Browse files
committed
Implement backend alarm feature
1 parent 44274f6 commit 22ccd17

36 files changed

Lines changed: 1727 additions & 29 deletions

ontime-back/src/main/java/devkor/ontime_back/config/SecurityConfig.java

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
import devkor.ontime_back.global.oauth.google.GoogleLoginService;
1616
import devkor.ontime_back.global.oauth.kakao.KakaoLoginFilter;
1717
import devkor.ontime_back.global.oauth.google.GoogleLoginFilter;
18+
import devkor.ontime_back.repository.UserAlarmSettingRepository;
1819
import devkor.ontime_back.repository.UserRepository;
1920
import lombok.RequiredArgsConstructor;
2021
import org.springframework.beans.factory.annotation.Value;
@@ -51,6 +52,7 @@ public class SecurityConfig {
5152
private final LoginService loginService;
5253
private final JwtTokenProvider jwtTokenProvider;
5354
private final UserRepository userRepository;
55+
private final UserAlarmSettingRepository userAlarmSettingRepository;
5456
private final ObjectMapper objectMapper;
5557
private final AppleLoginService appleLoginService;
5658
private final GoogleLoginService googleLoginService;
@@ -75,7 +77,7 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
7577
.requestMatchers("/health").permitAll() // 로드밸런서 연결 확인용 url
7678
.anyRequest().authenticated()
7779
)
78-
.addFilterBefore(new KakaoLoginFilter("/oauth2/kakao/login", jwtTokenProvider, userRepository),
80+
.addFilterBefore(new KakaoLoginFilter("/oauth2/kakao/login", jwtTokenProvider, userRepository, userAlarmSettingRepository),
7981
UsernamePasswordAuthenticationFilter.class)
8082
.addFilterBefore(new GoogleLoginFilter("/oauth2/google/login", googleLoginService, userRepository),
8183
UsernamePasswordAuthenticationFilter.class)
@@ -145,4 +147,4 @@ public CorsConfigurationSource corsConfigurationSource() {
145147
source.registerCorsConfiguration("/**", configuration);
146148
return source;
147149
}
148-
}
150+
}
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
package devkor.ontime_back.controller;
2+
3+
import devkor.ontime_back.dto.*;
4+
import devkor.ontime_back.response.ApiResponseForm;
5+
import devkor.ontime_back.service.AlarmService;
6+
import devkor.ontime_back.service.UserAuthService;
7+
import jakarta.servlet.http.HttpServletRequest;
8+
import lombok.RequiredArgsConstructor;
9+
import org.springframework.http.HttpStatus;
10+
import org.springframework.http.ResponseEntity;
11+
import org.springframework.web.bind.annotation.*;
12+
13+
import java.util.Map;
14+
15+
@RestController
16+
@RequiredArgsConstructor
17+
public class AlarmController {
18+
19+
private final UserAuthService userAuthService;
20+
private final AlarmService alarmService;
21+
22+
@GetMapping("/users/me/alarm-settings")
23+
public ResponseEntity<ApiResponseForm<AlarmSettingsResponseDto>> getAlarmSettings(HttpServletRequest request) {
24+
Long userId = userAuthService.getUserIdFromToken(request);
25+
return ResponseEntity.status(HttpStatus.OK)
26+
.body(ApiResponseForm.success(alarmService.getAlarmSettings(userId)));
27+
}
28+
29+
@PatchMapping("/users/me/alarm-settings")
30+
public ResponseEntity<ApiResponseForm<AlarmSettingsResponseDto>> patchAlarmSettings(
31+
HttpServletRequest request,
32+
@RequestBody Map<String, Object> requestBody) {
33+
Long userId = userAuthService.getUserIdFromToken(request);
34+
return ResponseEntity.status(HttpStatus.OK)
35+
.body(ApiResponseForm.success(alarmService.patchAlarmSettings(userId, requestBody)));
36+
}
37+
38+
@PutMapping("/users/me/devices/current")
39+
public ResponseEntity<ApiResponseForm<AlarmDeviceCurrentResponseDto>> registerCurrentDevice(
40+
HttpServletRequest request,
41+
@RequestBody AlarmDeviceCurrentRequestDto requestDto) {
42+
Long userId = userAuthService.getUserIdFromToken(request);
43+
return ResponseEntity.status(HttpStatus.OK)
44+
.body(ApiResponseForm.success(alarmService.registerCurrentDevice(
45+
userId,
46+
requestDto,
47+
userAuthService.getAccessTokenFromRequest(request),
48+
userAuthService.getRefreshTokenFromRequest(request))));
49+
}
50+
51+
@DeleteMapping("/users/me/devices/current")
52+
public ResponseEntity<ApiResponseForm<AlarmDeviceUnregisterResponseDto>> unregisterCurrentDevice(
53+
HttpServletRequest request,
54+
@RequestBody(required = false) AlarmDeviceUnregisterRequestDto requestDto) {
55+
Long userId = userAuthService.getUserIdFromToken(request);
56+
return ResponseEntity.status(HttpStatus.OK)
57+
.body(ApiResponseForm.success(alarmService.unregisterCurrentDevice(
58+
userId,
59+
requestDto,
60+
userAuthService.getAccessTokenFromRequest(request))));
61+
}
62+
63+
@PostMapping("/users/me/alarm-status")
64+
public ResponseEntity<ApiResponseForm<AlarmStatusReportResponseDto>> reportAlarmStatus(
65+
HttpServletRequest request,
66+
@RequestBody AlarmStatusReportRequestDto requestDto) {
67+
Long userId = userAuthService.getUserIdFromToken(request);
68+
return ResponseEntity.status(HttpStatus.OK)
69+
.body(ApiResponseForm.success(alarmService.reportAlarmStatus(
70+
userId,
71+
requestDto,
72+
userAuthService.getAccessTokenFromRequest(request))));
73+
}
74+
75+
@GetMapping("/users/me/alarm-status")
76+
public ResponseEntity<ApiResponseForm<AlarmStatusCurrentResponseDto>> getCurrentAlarmStatus(HttpServletRequest request) {
77+
Long userId = userAuthService.getUserIdFromToken(request);
78+
return ResponseEntity.status(HttpStatus.OK)
79+
.body(ApiResponseForm.success(alarmService.getCurrentAlarmStatus(
80+
userId,
81+
userAuthService.getAccessTokenFromRequest(request))));
82+
}
83+
}

ontime-back/src/main/java/devkor/ontime_back/controller/FirebaseTokenController.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,10 @@ public class FirebaseTokenController {
5050
public ResponseEntity<ApiResponseForm<String>> registerFirebaseToken(HttpServletRequest request, @RequestBody FirebaseTokenAddDto firebaseTokenAddDto) {
5151
Long userId = userAuthService.getUserIdFromToken(request);
5252

53-
firebaseTokenService.registerFirebaseToken(userId, firebaseTokenAddDto);
53+
firebaseTokenService.registerFirebaseToken(
54+
userId,
55+
firebaseTokenAddDto,
56+
userAuthService.getAccessTokenFromRequest(request));
5457

5558
String message = "FCM 토큰이 성공적으로 User테이블에 저장되었습니다!";
5659
return ResponseEntity.ok(ApiResponseForm.success(null, message));

ontime-back/src/main/java/devkor/ontime_back/controller/ScheduleController.java

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package devkor.ontime_back.controller;
22

33
import devkor.ontime_back.dto.*;
4+
import devkor.ontime_back.response.GeneralException;
45
import devkor.ontime_back.response.ApiResponseForm;
56
import devkor.ontime_back.service.ScheduleService;
67
import devkor.ontime_back.service.UserAuthService;
@@ -17,10 +18,14 @@
1718
import org.springframework.http.ResponseEntity;
1819
import org.springframework.web.bind.annotation.*;
1920

21+
import java.time.DateTimeException;
2022
import java.time.LocalDateTime;
23+
import java.time.format.DateTimeFormatter;
2124
import java.util.List;
2225
import java.util.UUID;
2326

27+
import static devkor.ontime_back.response.ErrorCode.INVALID_INPUT;
28+
2429
@RestController
2530
@RequestMapping("/schedules")
2631
@RequiredArgsConstructor
@@ -65,6 +70,20 @@ public ResponseEntity<ApiResponseForm<List<ScheduleDto>>> getPeriodSchedule(Http
6570
return ResponseEntity.status(HttpStatus.OK).body(ApiResponseForm.success(schedules));
6671
}
6772

73+
@GetMapping("/alarm-window")
74+
public ResponseEntity<ApiResponseForm<List<AlarmWindowScheduleDto>>> getAlarmWindowSchedules(
75+
HttpServletRequest request,
76+
@RequestParam String startDate,
77+
@RequestParam String endDate) {
78+
Long userId = userAuthService.getUserIdFromToken(request);
79+
List<AlarmWindowScheduleDto> schedules = scheduleService.getAlarmWindowSchedules(
80+
userId,
81+
parseLocalWallClockDateTime(startDate),
82+
parseLocalWallClockDateTime(endDate)
83+
);
84+
return ResponseEntity.status(HttpStatus.OK).body(ApiResponseForm.success(schedules));
85+
}
86+
6887
// id로 스케줄 조회
6988
@Operation(summary = "일정 id로 일정 조회",
7089
requestBody = @io.swagger.v3.oas.annotations.parameters.RequestBody(
@@ -262,6 +281,16 @@ public ResponseEntity<ApiResponseForm<?>> finishSchedule(
262281
return ResponseEntity.ok(ApiResponseForm.success(null, message));
263282
}
264283

284+
private LocalDateTime parseLocalWallClockDateTime(String value) {
285+
if (value == null || value.isBlank()) {
286+
throw new GeneralException(INVALID_INPUT);
287+
}
288+
try {
289+
return LocalDateTime.from(DateTimeFormatter.ISO_DATE_TIME.parse(value));
290+
} catch (DateTimeException e) {
291+
throw new GeneralException(INVALID_INPUT);
292+
}
293+
}
294+
265295

266296
}
267-
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
package devkor.ontime_back.dto;
2+
3+
import lombok.AllArgsConstructor;
4+
import lombok.Builder;
5+
import lombok.Getter;
6+
import lombok.NoArgsConstructor;
7+
import lombok.ToString;
8+
9+
@Getter
10+
@Builder
11+
@ToString
12+
@NoArgsConstructor
13+
@AllArgsConstructor
14+
public class AlarmDeviceCurrentRequestDto {
15+
private String deviceId;
16+
private String platform;
17+
private String appVersion;
18+
private String osVersion;
19+
private Boolean supportsNativeAlarm;
20+
private String nativeAlarmProvider;
21+
private String fallbackProvider;
22+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package devkor.ontime_back.dto;
2+
3+
import lombok.AllArgsConstructor;
4+
import lombok.Builder;
5+
import lombok.Getter;
6+
7+
import java.time.Instant;
8+
9+
@Getter
10+
@Builder
11+
@AllArgsConstructor
12+
public class AlarmDeviceCurrentResponseDto {
13+
private String deviceId;
14+
private Boolean active;
15+
private Instant lastSeenAt;
16+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package devkor.ontime_back.dto;
2+
3+
import lombok.AllArgsConstructor;
4+
import lombok.Builder;
5+
import lombok.Getter;
6+
import lombok.NoArgsConstructor;
7+
import lombok.ToString;
8+
9+
@Getter
10+
@Builder
11+
@ToString
12+
@NoArgsConstructor
13+
@AllArgsConstructor
14+
public class AlarmDeviceUnregisterRequestDto {
15+
private String deviceId;
16+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package devkor.ontime_back.dto;
2+
3+
import lombok.AllArgsConstructor;
4+
import lombok.Builder;
5+
import lombok.Getter;
6+
7+
@Getter
8+
@Builder
9+
@AllArgsConstructor
10+
public class AlarmDeviceUnregisterResponseDto {
11+
private Boolean active;
12+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package devkor.ontime_back.dto;
2+
3+
import lombok.AllArgsConstructor;
4+
import lombok.Builder;
5+
import lombok.Getter;
6+
7+
import java.time.Instant;
8+
9+
@Getter
10+
@Builder
11+
@AllArgsConstructor
12+
public class AlarmSettingsResponseDto {
13+
private Boolean alarmsEnabled;
14+
private Integer defaultAlarmOffsetMinutes;
15+
private Instant updatedAt;
16+
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
package devkor.ontime_back.dto;
2+
3+
import lombok.AllArgsConstructor;
4+
import lombok.Builder;
5+
import lombok.Getter;
6+
7+
import java.time.Instant;
8+
import java.time.LocalDateTime;
9+
import java.util.List;
10+
11+
@Getter
12+
@Builder
13+
@AllArgsConstructor
14+
public class AlarmStatusCurrentResponseDto {
15+
private String deviceId;
16+
private Boolean active;
17+
private String platform;
18+
private String appVersion;
19+
private String osVersion;
20+
private Boolean supportsNativeAlarm;
21+
private String nativeAlarmProvider;
22+
private String fallbackProvider;
23+
private Instant lastSeenAt;
24+
private Instant reconciledAt;
25+
private LocalDateTime scheduleWindowStart;
26+
private LocalDateTime scheduleWindowEnd;
27+
private LocalDateTime alarmCoverageStart;
28+
private LocalDateTime alarmCoverageEnd;
29+
private String status;
30+
private String permissionIssue;
31+
private Integer armedScheduleCount;
32+
private List<String> armedScheduleIds;
33+
private Integer skippedScheduleCount;
34+
private List<AlarmStatusFailureDto> failures;
35+
private Instant updatedAt;
36+
}

0 commit comments

Comments
 (0)