Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

✨ 사용자 알림 설정 API #60

Merged
merged 16 commits into from
Apr 26, 2024
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
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
import kr.co.pennyway.api.apis.users.dto.UserProfileDto;
import kr.co.pennyway.api.apis.users.dto.UserProfileUpdateDto;
import kr.co.pennyway.api.common.security.authentication.SecurityUserDetails;
import kr.co.pennyway.domain.domains.user.domain.NotifySetting;
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.validation.annotation.Validated;
Expand Down Expand Up @@ -61,10 +62,90 @@ public interface UserAccountApi {
@Operation(summary = "사용자 계정 조회", description = "사용자 본인의 계정 정보를 조회합니다.")
@ApiResponse(responseCode = "200", content = @Content(schemaProperties = @SchemaProperty(name = "user", schema = @Schema(implementation = UserProfileDto.class))))
ResponseEntity<?> getMyAccount(@AuthenticationPrincipal SecurityUserDetails user);

@Operation(summary = "사용자 이름 수정")
ResponseEntity<?> putName(@RequestBody @Validated UserProfileUpdateDto.NameReq request, @AuthenticationPrincipal SecurityUserDetails user);

@Operation(summary = "사용자 아이디 수정")
ResponseEntity<?> putUsername(@RequestBody @Validated UserProfileUpdateDto.UsernameReq request, @AuthenticationPrincipal SecurityUserDetails user);

@Operation(summary = "사용자 알림 활성화")
@Parameter(name = "type", description = "알림 타입", examples = {
@ExampleObject(name = "가계부", value = "account_book"), @ExampleObject(name = "피드", value = "feed"), @ExampleObject(name = "채팅", value = "chat")
}, required = true, in = ParameterIn.QUERY)
@ApiResponses({
@ApiResponse(responseCode = "200", content = @Content(mediaType = "application/json", examples = {
@ExampleObject(name = "가계부 알림 활성화", value = """
{
"code": "2000",
"data": {
"notifySetting": {
"accountBookNotify": true
}
}
}
"""),
@ExampleObject(name = "피드 알림 활성화", value = """
{
"code": "2000",
"data": {
"notifySetting": {
"feedNotify": true
}
}
}
"""),
@ExampleObject(name = "채팅 알림 활성화", value = """
{
"code": "2000",
"data": {
"notifySetting": {
"chatNotify": true
}
}
}
""")
}))
})
ResponseEntity<?> patchNotifySetting(@RequestParam NotifySetting.NotifyType type, @AuthenticationPrincipal SecurityUserDetails user);

@Operation(summary = "사용자 알림 비활성화")
@Parameter(name = "type", description = "알림 타입", examples = {
@ExampleObject(name = "가계부", value = "account_book"), @ExampleObject(name = "피드", value = "feed"), @ExampleObject(name = "채팅", value = "chat")
}, required = true, in = ParameterIn.QUERY)
@ApiResponses({
@ApiResponse(responseCode = "200", content = @Content(mediaType = "application/json", examples = {
@ExampleObject(name = "가계부 알림 비활성화", value = """
{
"code": "2000",
"data": {
"notifySetting": {
"accountBookNotify": false
}
}
}
"""),
@ExampleObject(name = "피드 알림 비활성화", value = """
{
"code": "2000",
"data": {
"notifySetting": {
"feedNotify": false
}
}
}
"""),
@ExampleObject(name = "채팅 알림 비활성화", value = """
{
"code": "2000",
"data": {
"notifySetting": {
"chatNotify": false
}
}
}
""")
}))
})
ResponseEntity<?> deleteNotifySetting(@RequestParam NotifySetting.NotifyType type, @AuthenticationPrincipal SecurityUserDetails user);
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import kr.co.pennyway.api.apis.users.usecase.UserAccountUseCase;
import kr.co.pennyway.api.common.response.SuccessResponse;
import kr.co.pennyway.api.common.security.authentication.SecurityUserDetails;
import kr.co.pennyway.domain.domains.user.domain.NotifySetting;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.ResponseEntity;
Expand Down Expand Up @@ -43,7 +44,7 @@ public ResponseEntity<?> deleteDevice(@RequestParam("token") @Validated @NotBlan
public ResponseEntity<?> getMyAccount(@AuthenticationPrincipal SecurityUserDetails user) {
return ResponseEntity.ok(SuccessResponse.from("user", userAccountUseCase.getMyAccount(user.getUserId())));
}

@Override
@PatchMapping("/name")
@PreAuthorize("isAuthenticated()")
Expand All @@ -59,4 +60,18 @@ public ResponseEntity<?> putUsername(UserProfileUpdateDto.UsernameReq request, S
userAccountUseCase.updateUsername(user.getUserId(), request.username());
return ResponseEntity.ok(SuccessResponse.noContent());
}

@Override
@PatchMapping("/notifications")
@PreAuthorize("isAuthenticated()")
public ResponseEntity<?> patchNotifySetting(@RequestParam NotifySetting.NotifyType type, @AuthenticationPrincipal SecurityUserDetails user) {
return ResponseEntity.ok(SuccessResponse.from("notifySetting", userAccountUseCase.activateNotification(user.getUserId(), type)));
}

@Override
@DeleteMapping("/notifications")
@PreAuthorize("isAuthenticated()")
public ResponseEntity<?> deleteNotifySetting(@RequestParam NotifySetting.NotifyType type, @AuthenticationPrincipal SecurityUserDetails user) {
return ResponseEntity.ok(SuccessResponse.from("notifySetting", userAccountUseCase.deactivateNotification(user.getUserId(), type)));
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package kr.co.pennyway.api.apis.users.dto;

import com.fasterxml.jackson.annotation.JsonInclude;
import io.swagger.v3.oas.annotations.media.Schema;
import kr.co.pennyway.domain.domains.user.domain.NotifySetting;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Pattern;

Expand All @@ -22,4 +24,25 @@ public record UsernameReq(
String username
) {
}

@Schema(title = "사용자 알림 설정 응답 DTO")
public record NotifySettingUpdateReq(
@Schema(description = "계좌 알림 설정", example = "true", requiredMode = Schema.RequiredMode.NOT_REQUIRED)
@JsonInclude(JsonInclude.Include.NON_NULL)
Boolean accountBookNotify,
@Schema(description = "피드 알림 설정", example = "true", requiredMode = Schema.RequiredMode.NOT_REQUIRED)
@JsonInclude(JsonInclude.Include.NON_NULL)
Boolean feedNotify,
@Schema(description = "채팅 알림 설정", example = "true", requiredMode = Schema.RequiredMode.NOT_REQUIRED)
@JsonInclude(JsonInclude.Include.NON_NULL)
Boolean chatNotify
) {
public static NotifySettingUpdateReq of(NotifySetting.NotifyType type, Boolean flag) {
return switch (type) {
case ACCOUNT_BOOK -> new NotifySettingUpdateReq(flag, null, null);
case FEED -> new NotifySettingUpdateReq(null, flag, null);
case CHAT -> new NotifySettingUpdateReq(null, null, flag);
};
}
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package kr.co.pennyway.api.apis.users.service;

import kr.co.pennyway.domain.domains.user.domain.NotifySetting;
import kr.co.pennyway.domain.domains.user.domain.User;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
Expand All @@ -19,4 +20,9 @@ public void updateName(User user, String newName) {
public void updateUsername(User user, String newUsername) {
user.updateUsername(newUsername);
}

@Transactional
public void updateNotifySetting(User user, NotifySetting.NotifyType type, Boolean flag) {
user.getNotifySetting().updateNotifySetting(type, flag);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,15 @@

import kr.co.pennyway.api.apis.users.dto.DeviceDto;
import kr.co.pennyway.api.apis.users.dto.UserProfileDto;
import kr.co.pennyway.api.apis.users.dto.UserProfileUpdateDto;
import kr.co.pennyway.api.apis.users.service.DeviceRegisterService;
import kr.co.pennyway.api.apis.users.service.UserProfileUpdateService;
import kr.co.pennyway.common.annotation.UseCase;
import kr.co.pennyway.domain.domains.device.domain.Device;
import kr.co.pennyway.domain.domains.device.exception.DeviceErrorCode;
import kr.co.pennyway.domain.domains.device.exception.DeviceErrorException;
import kr.co.pennyway.domain.domains.device.service.DeviceService;
import kr.co.pennyway.domain.domains.user.domain.NotifySetting;
import kr.co.pennyway.domain.domains.user.domain.User;
import kr.co.pennyway.domain.domains.user.exception.UserErrorCode;
import kr.co.pennyway.domain.domains.user.exception.UserErrorException;
Expand All @@ -25,7 +27,6 @@ public class UserAccountUseCase {
private final DeviceService deviceService;

private final UserProfileUpdateService userProfileUpdateService;

private final DeviceRegisterService deviceRegisterService;

@Transactional
Expand Down Expand Up @@ -54,7 +55,7 @@ public UserProfileDto getMyAccount(Long userId) {

return UserProfileDto.from(user);
}

@Transactional
public void updateName(Long userId, String newName) {
User user = readUserOrThrow(userId);
Expand All @@ -69,6 +70,22 @@ public void updateUsername(Long userId, String newUsername) {
userProfileUpdateService.updateUsername(user, newUsername);
}

@Transactional
public UserProfileUpdateDto.NotifySettingUpdateReq activateNotification(Long userId, NotifySetting.NotifyType type) {
User user = readUserOrThrow(userId);

userProfileUpdateService.updateNotifySetting(user, type, Boolean.TRUE);
return UserProfileUpdateDto.NotifySettingUpdateReq.of(type, Boolean.TRUE);
}

@Transactional
public UserProfileUpdateDto.NotifySettingUpdateReq deactivateNotification(Long userId, NotifySetting.NotifyType type) {
User user = readUserOrThrow(userId);

userProfileUpdateService.updateNotifySetting(user, type, Boolean.FALSE);
return UserProfileUpdateDto.NotifySettingUpdateReq.of(type, Boolean.FALSE);
}

private User readUserOrThrow(Long userId) {
return userService.readUser(userId).orElseThrow(
() -> new UserErrorException(UserErrorCode.NOT_FOUND)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package kr.co.pennyway.api.common.converter;

import kr.co.pennyway.domain.domains.user.domain.NotifySetting;
import kr.co.pennyway.domain.domains.user.exception.UserErrorCode;
import kr.co.pennyway.domain.domains.user.exception.UserErrorException;
import org.springframework.core.convert.converter.Converter;

public class NotifyTypeConverter implements Converter<String, NotifySetting.NotifyType> {
@Override
public NotifySetting.NotifyType convert(String type) {
try {
return NotifySetting.NotifyType.valueOf(type.toUpperCase());
} catch (IllegalArgumentException e) {
throw new UserErrorException(UserErrorCode.INVALID_NOTIFY_TYPE);
}
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package kr.co.pennyway.api.config;

import kr.co.pennyway.api.common.converter.NotifyTypeConverter;
import kr.co.pennyway.api.common.converter.ProviderConverter;
import kr.co.pennyway.api.common.converter.VerificationTypeConverter;
import org.springframework.context.annotation.Configuration;
Expand All @@ -13,5 +14,6 @@ public void addFormatters(FormatterRegistry registrar) {

registrar.addConverter(new ProviderConverter());
registrar.addConverter(new VerificationTypeConverter());
registrar.addConverter(new NotifyTypeConverter());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -32,4 +32,16 @@ public static NotifySetting of(Boolean accountBookNotify, Boolean feedNotify, Bo
.chatNotify(chatNotify)
.build();
}

public void updateNotifySetting(NotifyType notifyType, Boolean flag) {
switch (notifyType) {
case ACCOUNT_BOOK -> this.accountBookNotify = flag;
case FEED -> this.feedNotify = flag;
case CHAT -> this.chatNotify = flag;
}
}

public enum NotifyType {
ACCOUNT_BOOK, FEED, CHAT
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,10 @@ public enum UserErrorCode implements BaseErrorCode {
ALREADY_WITHDRAWAL(StatusCode.FORBIDDEN, ReasonCode.ACCESS_TO_THE_REQUESTED_RESOURCE_IS_FORBIDDEN, "이미 탈퇴한 유저입니다."),

/* 404 NOT_FOUND */
NOT_FOUND(StatusCode.NOT_FOUND, ReasonCode.REQUESTED_RESOURCE_NOT_FOUND, "유저를 찾을 수 없습니다.");
NOT_FOUND(StatusCode.NOT_FOUND, ReasonCode.REQUESTED_RESOURCE_NOT_FOUND, "유저를 찾을 수 없습니다."),

/* 422 UNPROCESSABLE_ENTITY */
INVALID_NOTIFY_TYPE(StatusCode.UNPROCESSABLE_CONTENT, ReasonCode.TYPE_MISMATCH_ERROR_IN_REQUEST_BODY, "유효하지 않은 알림 타입입니다.");

private final StatusCode statusCode;
private final ReasonCode reasonCode;
Expand Down
Loading