Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion src/main/java/com/kustacks/kuring/auth/AuthConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,12 @@
import com.kustacks.kuring.admin.application.service.AdminDetailsService;
import com.kustacks.kuring.auth.authorization.AuthenticationPrincipalArgumentResolver;
import com.kustacks.kuring.auth.context.SecurityContextPersistenceFilter;
import com.kustacks.kuring.auth.handler.*;
import com.kustacks.kuring.auth.handler.AuthenticationFailureHandler;
import com.kustacks.kuring.auth.handler.AuthenticationSuccessHandler;
import com.kustacks.kuring.auth.handler.LoginAuthenticationFailureHandler;
import com.kustacks.kuring.auth.handler.LoginAuthenticationSuccessHandler;
import com.kustacks.kuring.auth.handler.UserRegisterFailureHandler;
import com.kustacks.kuring.auth.handler.UserRegisterSuccessHandler;
import com.kustacks.kuring.auth.interceptor.AdminTokenAuthenticationFilter;
import com.kustacks.kuring.auth.interceptor.BearerTokenAuthenticationFilter;
import com.kustacks.kuring.auth.interceptor.FirebaseTokenAuthenticationFilter;
Expand Down Expand Up @@ -49,6 +54,7 @@ public class AuthConfig implements WebMvcConfigurer {
public FilterRegistrationBean<CorsFilter> corsFilterRegistration() {
CorsConfiguration config = new CorsConfiguration();


List<String> dynamicOrigins = new ArrayList<>(allowedOrigins);
dynamicOrigins.add("http://localhost:[*]");
dynamicOrigins.add("http://127.0.0.1:[*]");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ public enum ResponseCodeAndMessages {

/* Academic Events */
ACADEMIC_EVENT_SEARCH_SUCCESS(HttpStatus.OK.value(), "학사일정 조회에 성공했습니다."),
ACADEMIC_EVENT_NOTIFICATION_UPDATE_SUCCESS(HttpStatus.OK.value(), "학사일정 알림 설정이 변경되었습니다."),

/**
* ErrorCodes about auth
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ public enum KuringFeatures {
UPDATE_KUIS_NOTICE(new Feature("update_kuis_notice")),
UPDATE_USER(new Feature("update_user")),
UPDATE_STAFF(new Feature("update_staff")),
UPDATE_ACADEMIC_EVENT(new Feature("update_academic_event"));
UPDATE_ACADEMIC_EVENT(new Feature("update_academic_event")),
NOTIFY_ACADEMIC_EVENT(new Feature("notify_academic_event"));

private final Feature feature;

Expand Down
12 changes: 4 additions & 8 deletions src/main/java/com/kustacks/kuring/config/FeatureFlagConfig.java
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
package com.kustacks.kuring.config;

import com.kustacks.kuring.common.env.RemotePropertyResolver;
import com.kustacks.kuring.common.featureflag.FeatureFlagProperties;
import com.kustacks.kuring.common.featureflag.FeatureFlags;
import com.kustacks.kuring.common.featureflag.RemoteFeatureFlags;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Bean;
Expand Down Expand Up @@ -31,14 +32,9 @@ public AppConfigDataClient appConfigDataClient() {
.region(Region.of(properties.region()))
.build();
}
//
// @Bean
// public RemoteFeatureFlags remoteFeatureFlags(RemotePropertyResolver remotePropertyResolver) {
// return new RemoteFeatureFlags(remotePropertyResolver);
// }

@Bean
public FeatureFlags featureFlags() {
return new FeatureFlags.AlwaysDisabledFeatureFlags();
public RemoteFeatureFlags remoteFeatureFlags(RemotePropertyResolver remotePropertyResolver) {
return new RemoteFeatureFlags(remotePropertyResolver);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,11 @@
import com.kustacks.kuring.common.dto.BaseResponse;
import com.kustacks.kuring.common.exception.InvalidStateException;
import com.kustacks.kuring.common.exception.code.ErrorCode;
import com.kustacks.kuring.notice.adapter.in.web.dto.*;
import com.kustacks.kuring.notice.adapter.in.web.dto.CommentListResponse;
import com.kustacks.kuring.notice.adapter.in.web.dto.NoticeCategoryNameResponse;
import com.kustacks.kuring.notice.adapter.in.web.dto.NoticeContentSearchResponse;
import com.kustacks.kuring.notice.adapter.in.web.dto.NoticeDepartmentNameResponse;
import com.kustacks.kuring.notice.adapter.in.web.dto.NoticeRangeLookupResponse;
import com.kustacks.kuring.notice.application.port.in.NoticeCommentReadingUseCase;
import com.kustacks.kuring.notice.application.port.in.NoticeQueryUseCase;
import com.kustacks.kuring.notice.application.port.in.dto.NoticeContentSearchResult;
Expand All @@ -31,7 +35,9 @@
import java.util.Optional;

import static com.kustacks.kuring.auth.authentication.AuthorizationExtractor.extractAuthorizationValue;
import static com.kustacks.kuring.common.dto.ResponseCodeAndMessages.*;
import static com.kustacks.kuring.common.dto.ResponseCodeAndMessages.CATEGORY_SEARCH_SUCCESS;
import static com.kustacks.kuring.common.dto.ResponseCodeAndMessages.DEPARTMENTS_SEARCH_SUCCESS;
import static com.kustacks.kuring.common.dto.ResponseCodeAndMessages.NOTICE_SEARCH_SUCCESS;

@Tag(name = "Notice-Query", description = "공지 정보 조회")
@Validated
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,11 @@
import com.kustacks.kuring.notice.adapter.in.web.dto.CommentDetailResponse;
import com.kustacks.kuring.notice.application.port.in.NoticeCommentReadingUseCase;
import com.kustacks.kuring.notice.application.port.in.NoticeQueryUseCase;
import com.kustacks.kuring.notice.application.port.in.dto.*;
import com.kustacks.kuring.notice.application.port.in.dto.NoticeCategoryNameResult;
import com.kustacks.kuring.notice.application.port.in.dto.NoticeContentSearchResult;
import com.kustacks.kuring.notice.application.port.in.dto.NoticeDepartmentNameResult;
import com.kustacks.kuring.notice.application.port.in.dto.NoticeRangeLookupCommand;
import com.kustacks.kuring.notice.application.port.in.dto.NoticeRangeLookupResult;
import com.kustacks.kuring.notice.application.port.out.CommentQueryPort;
import com.kustacks.kuring.notice.application.port.out.NoticeQueryPort;
import com.kustacks.kuring.notice.application.port.out.dto.CommentReadModel;
Expand All @@ -21,7 +25,13 @@
import org.springframework.data.domain.PageRequest;
import org.springframework.transaction.annotation.Transactional;

import java.util.*;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;

import static com.kustacks.kuring.notice.domain.CategoryName.DEPARTMENT;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import com.kustacks.kuring.common.dto.BaseResponse;
import com.kustacks.kuring.common.exception.InvalidStateException;
import com.kustacks.kuring.common.exception.code.ErrorCode;
import com.kustacks.kuring.user.adapter.in.web.dto.UserAcademicEventNotificationRequest;
import com.kustacks.kuring.user.adapter.in.web.dto.UserBookmarkRequest;
import com.kustacks.kuring.user.adapter.in.web.dto.UserCategoriesSubscribeRequest;
import com.kustacks.kuring.user.adapter.in.web.dto.UserDepartmentsSubscribeRequest;
Expand All @@ -15,8 +16,8 @@
import com.kustacks.kuring.user.adapter.in.web.dto.UserLoginResponse;
import com.kustacks.kuring.user.adapter.in.web.dto.UserPasswordModifyRequest;
import com.kustacks.kuring.user.adapter.in.web.dto.UserSignupRequest;
import com.kustacks.kuring.user.application.port.in.dto.UserWithdrawCommand;
import com.kustacks.kuring.user.application.port.in.UserCommandUseCase;
import com.kustacks.kuring.user.application.port.in.dto.UserAcademicEventNotificationCommand;
import com.kustacks.kuring.user.application.port.in.dto.UserBookmarkCommand;
import com.kustacks.kuring.user.application.port.in.dto.UserCategoriesSubscribeCommand;
import com.kustacks.kuring.user.application.port.in.dto.UserDepartmentsSubscribeCommand;
Expand All @@ -26,6 +27,7 @@
import com.kustacks.kuring.user.application.port.in.dto.UserLogoutCommand;
import com.kustacks.kuring.user.application.port.in.dto.UserPasswordModifyCommand;
import com.kustacks.kuring.user.application.port.in.dto.UserSignupCommand;
import com.kustacks.kuring.user.application.port.in.dto.UserWithdrawCommand;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.security.SecurityRequirement;
import io.swagger.v3.oas.annotations.tags.Tag;
Expand All @@ -42,6 +44,7 @@
import org.springframework.web.bind.annotation.RequestHeader;

import static com.kustacks.kuring.auth.authentication.AuthorizationExtractor.extractAuthorizationValue;
import static com.kustacks.kuring.common.dto.ResponseCodeAndMessages.ACADEMIC_EVENT_NOTIFICATION_UPDATE_SUCCESS;
import static com.kustacks.kuring.common.dto.ResponseCodeAndMessages.BOOKMARK_SAVE_SUCCESS;
import static com.kustacks.kuring.common.dto.ResponseCodeAndMessages.CATEGORY_SUBSCRIBE_SUCCESS;
import static com.kustacks.kuring.common.dto.ResponseCodeAndMessages.DEPARTMENTS_SUBSCRIBE_SUCCESS;
Expand Down Expand Up @@ -87,6 +90,17 @@ public ResponseEntity<BaseResponse<Void>> editUserSubscribeDepartments(
return ResponseEntity.ok().body(new BaseResponse<>(DEPARTMENTS_SUBSCRIBE_SUCCESS, null));
}

@Operation(summary = "사용자 학사일정 알림 설정 수정", description = "사용자의 학사일정 알림 설정 상태를 수정합니다.")
@SecurityRequirement(name = FCM_TOKEN_HEADER_KEY)
@PatchMapping(value = "/notifications/academic-events", consumes = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<BaseResponse<Void>> updateAcademicEventNotification(
@Valid @RequestBody UserAcademicEventNotificationRequest request,
@RequestHeader(FCM_TOKEN_HEADER_KEY) String id
) {
userCommandUseCase.updateAcademicEventNotification(new UserAcademicEventNotificationCommand(id, request.enabled()));
return ResponseEntity.ok().body(new BaseResponse<>(ACADEMIC_EVENT_NOTIFICATION_UPDATE_SUCCESS, null));
}

@Operation(summary = "사용자 피드백 작성", description = "사용자가 피드백을 작성하여 저장합니다")
@SecurityRequirement(name = FCM_TOKEN_HEADER_KEY)
@PostMapping("/feedbacks")
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package com.kustacks.kuring.user.adapter.in.web.dto;

import jakarta.validation.constraints.NotNull;

public record UserAcademicEventNotificationRequest(
@NotNull Boolean enabled
) {
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.kustacks.kuring.user.application.port.in;

import com.kustacks.kuring.user.application.port.in.dto.UserAcademicEventNotificationCommand;
import com.kustacks.kuring.user.application.port.in.dto.UserBookmarkCommand;
import com.kustacks.kuring.user.application.port.in.dto.UserCategoriesSubscribeCommand;
import com.kustacks.kuring.user.application.port.in.dto.UserDecreaseQuestionCountCommand;
Expand All @@ -15,6 +16,8 @@
public interface UserCommandUseCase {
void editSubscribeCategories(UserCategoriesSubscribeCommand command);
void editSubscribeDepartments(UserDepartmentsSubscribeCommand command);

void updateAcademicEventNotification(UserAcademicEventNotificationCommand command);
void saveFeedback(UserFeedbackCommand command);
void saveBookmark(UserBookmarkCommand command);
void decreaseQuestionCount(UserDecreaseQuestionCountCommand command);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.kustacks.kuring.user.application.port.in.dto;

public record UserAcademicEventNotificationCommand(
String userToken,
Boolean enabled
) {
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
import com.kustacks.kuring.notice.domain.CategoryName;
import com.kustacks.kuring.notice.domain.DepartmentName;
import com.kustacks.kuring.user.application.port.in.UserCommandUseCase;
import com.kustacks.kuring.user.application.port.in.dto.UserAcademicEventNotificationCommand;
import com.kustacks.kuring.user.application.port.in.dto.UserBookmarkCommand;
import com.kustacks.kuring.user.application.port.in.dto.UserCategoriesSubscribeCommand;
import com.kustacks.kuring.user.application.port.in.dto.UserDecreaseQuestionCountCommand;
Expand Down Expand Up @@ -82,6 +83,12 @@ public void editSubscribeDepartments(UserDepartmentsSubscribeCommand command) {
);
}

@Override
public void updateAcademicEventNotification(UserAcademicEventNotificationCommand command) {
User user = findUserByToken(command.userToken());
user.updateAcademicNotificationEnabled(command.enabled());
}

@Override
public void saveFeedback(UserFeedbackCommand command) {
User findUser = findUserByToken(command.userToken());
Expand Down
8 changes: 8 additions & 0 deletions src/main/java/com/kustacks/kuring/user/domain/User.java
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,10 @@ public class User implements Serializable {
@Column(nullable = true)
private Long loginUserId;

@Getter(AccessLevel.PUBLIC)
@Column(name = "academic_event_notification_enabled", nullable = false)
private Boolean academicEventNotificationEnabled = Boolean.TRUE;

//Fcm Token User
public User(String token) {
this.fcmToken = token;
Expand Down Expand Up @@ -174,6 +178,10 @@ public boolean isLoggedIn() {
return this.loginUserId != null;
}

public void updateAcademicNotificationEnabled(Boolean enabled) {
this.academicEventNotificationEnabled = enabled;
}

@Override
public boolean equals(Object o) {
if (this == o) return true;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ public class StaffUpdater {
private final StaffScraper staffScraper;
private final List<DeptInfo> deptInfos;
private final FeatureFlags featureFlags;

@Scheduled(fixedRate = 30, timeUnit = TimeUnit.DAYS)
public void update() {
if (featureFlags.isEnabled(KuringFeatures.UPDATE_STAFF.getFeature())) {
Expand Down Expand Up @@ -65,14 +65,18 @@ private Map<String, StaffDto> scrapStaffByDepartment(DeptInfo deptInfo) {

private Map<String, StaffDto> convertStaffDtoMap(List<StaffDto> scrapedStaffDtos) {
return scrapedStaffDtos.stream()
.collect(Collectors.toMap(StaffDto::identifier, staffDto -> staffDto));
.collect(Collectors.toMap(
StaffDto::identifier,
staffDto -> staffDto,
(existing, replacement) -> existing));
}

private void mergeForMultipleDepartmentsStaff(
Map<String, StaffDto> kuStaffDTOMap,
Map<String, StaffDto> staffDtoMap
) {
staffDtoMap.forEach((key, value) -> kuStaffDTOMap.merge(key, value, (v1, v2) -> {
staffDtoMap.forEach((key, value) -> kuStaffDTOMap.merge(key, value,
(v1, v2) -> {
v1.setDeptName(v1.getDeptName() + ", " + v2.getDeptName());
return v1;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
-- 사용자 테이블에 학사일정 알림 설정 컬럼 추가
ALTER TABLE user
ADD COLUMN academic_event_notification_enabled BOOLEAN NOT NULL DEFAULT TRUE;
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@
import static com.kustacks.kuring.acceptance.UserStep.피드백_요청_응답_확인_v2;
import static com.kustacks.kuring.acceptance.UserStep.학과_구독_요청;
import static com.kustacks.kuring.acceptance.UserStep.학과_구독_응답_확인;
import static com.kustacks.kuring.acceptance.UserStep.학사일정_알림_토글_요청;
import static com.kustacks.kuring.acceptance.UserStep.학사일정_알림_토글_응답_확인;
import static com.kustacks.kuring.acceptance.UserStep.회원_가입_요청;
import static com.kustacks.kuring.acceptance.UserStep.회원_탈퇴_요청;
import static com.kustacks.kuring.acceptance.UserStep.회원_탈퇴_응답_확인;
Expand Down Expand Up @@ -502,4 +504,23 @@ void modify_password_with_wrong_email() {
// then
실패_응답_확인(비밀번호_초기화_응답, HttpStatus.NOT_FOUND);
}

@DisplayName("[v2] 사용자는 학사일정 알림을 토글할 수 있다")
@Test
void toggle_academic_event_notification() {
// given
doNothing().when(firebaseSubscribeService).validationToken(anyString());

// when - 첫 번째 토글 (true → false)
var 첫번째_토글_응답 = 학사일정_알림_토글_요청(USER_FCM_TOKEN, false);

// then
학사일정_알림_토글_응답_확인(첫번째_토글_응답, false);

// when - 두 번째 토글 (false → true)
var 두번째_토글_응답 = 학사일정_알림_토글_요청(USER_FCM_TOKEN, true);

// then
학사일정_알림_토글_응답_확인(두번째_토글_응답, true);
}
}
21 changes: 21 additions & 0 deletions src/test/java/com/kustacks/kuring/acceptance/UserStep.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.kustacks.kuring.acceptance;

import com.kustacks.kuring.auth.dto.UserRegisterRequest;
import com.kustacks.kuring.user.adapter.in.web.dto.UserAcademicEventNotificationRequest;
import com.kustacks.kuring.user.adapter.in.web.dto.UserBookmarkRequest;
import com.kustacks.kuring.user.adapter.in.web.dto.UserCategoriesSubscribeRequest;
import com.kustacks.kuring.user.adapter.in.web.dto.UserDepartmentsSubscribeRequest;
Expand Down Expand Up @@ -325,4 +326,24 @@ public class UserStep {
return response.jsonPath().getString("data.accessToken");
}

public static ExtractableResponse<Response> 학사일정_알림_토글_요청(String fcmToken, boolean enabled) {
return RestAssured
.given().log().all()
.contentType(MediaType.APPLICATION_JSON_VALUE)
.header("User-Token", fcmToken)
.body(new UserAcademicEventNotificationRequest(enabled))
.when().patch("/api/v2/users/notifications/academic-events")
.then().log().all()
.extract();
}

public static void 학사일정_알림_토글_응답_확인(ExtractableResponse<Response> response, Boolean expectedEnabled) {
assertAll(
() -> assertThat(response.statusCode()).isEqualTo(HttpStatus.OK.value()),
() -> assertThat(response.jsonPath().getInt("code")).isEqualTo(200),
() -> assertThat(response.jsonPath().getString("message")).isEqualTo("학사일정 알림 설정이 변경되었습니다."),
() -> assertThat(response.jsonPath().getList("data")).isNull()
);
}

}
25 changes: 24 additions & 1 deletion src/test/java/com/kustacks/kuring/user/domain/UserTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@

import java.util.List;

import static org.assertj.core.api.Assertions.*;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatCode;
import static org.assertj.core.api.Assertions.assertThatThrownBy;

@DisplayName("도메인 : User")
class UserTest {
Expand Down Expand Up @@ -184,6 +186,27 @@ void logout_user() {
.isNull();
}

@DisplayName("학사일정 알림 상태를 변경할 수 있다")
@Test
void update_academic_event_notification() {
// given - true
User user = createUser(1L, "token");

// when - false
user.updateAcademicNotificationEnabled(false);

// then - false
assertThat(user.getAcademicEventNotificationEnabled())
.isFalse();

// when - true
user.updateAcademicNotificationEnabled(true);

// then - true
assertThat(user.getAcademicEventNotificationEnabled())
.isTrue();
}

private RootUser createRootUser(Long id, String email, String password, String nickname) {
RootUser rootUser = new RootUser(email, password, nickname);
ReflectionTestUtils.setField(rootUser, "id", id);
Expand Down
Loading