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

Ignore: πŸ› Modify Device Token Management Policy #209

Merged
merged 20 commits into from
Dec 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
8b028df
feat: add handle_owner method in device_token entity
psychology50 Dec 3, 2024
e71f64d
feat: device_token_regiter_service in domain-service module
psychology50 Dec 3, 2024
7502232
feat: add two type of device_token search query
psychology50 Dec 3, 2024
c475ee2
feat: add device-token-rdb-service
psychology50 Dec 3, 2024
f262cfa
fix: change read-by-token to read-by-device-id-and-token
psychology50 Dec 3, 2024
bcfbc86
fix: add activated condition in to device_token entity's is_expired
psychology50 Dec 4, 2024
d519be6
feat: add device token duplicated error code
psychology50 Dec 4, 2024
b8dd141
fix: add conflict error handling when create_device in device_rdb_ser…
psychology50 Dec 4, 2024
5b3c94c
fix: convert from by-device-id-and-token to by-token for unique
psychology50 Dec 4, 2024
fc15a5a
fix: add validation logic for token value's uniqueness and pair of de…
psychology50 Dec 4, 2024
0fabada
fix: add device_id param on the handle-owner in device-token-entity
psychology50 Dec 4, 2024
87a7929
fix: add conflict condition if different device-id but expired, it wi…
psychology50 Dec 4, 2024
ad94f60
test: device-token-register-service-unit test
psychology50 Dec 4, 2024
9ab3006
fix: update device-token-register-service path in usecase and delete …
psychology50 Dec 4, 2024
97110b9
style: divide create and update logic
psychology50 Dec 4, 2024
df72e3b
chore: add testcontainer in domain-service module
psychology50 Dec 4, 2024
61dceba
chore: add profile-resolver in domain-service module
psychology50 Dec 4, 2024
8d090eb
chore: add jpa-test-config in test package in the domain-service-module
psychology50 Dec 4, 2024
37657b5
fix: add 'test' profile in domain-service-integration-profile-resolver
psychology50 Dec 4, 2024
14db037
test: device-token-register-service integration test
psychology50 Dec 4, 2024
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

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import kr.co.pennyway.api.apis.users.service.*;
import kr.co.pennyway.api.common.storage.AwsS3Adapter;
import kr.co.pennyway.common.annotation.UseCase;
import kr.co.pennyway.domain.context.account.service.DeviceTokenRegisterService;
import kr.co.pennyway.domain.domains.device.domain.DeviceToken;
import kr.co.pennyway.domain.domains.oauth.domain.Oauth;
import kr.co.pennyway.domain.domains.user.domain.NotifySetting;
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,19 @@ public Boolean isActivated() {
return activated && lastSignedInAt.plusDays(7).isAfter(now);
}

/**
* λ””λ°”μ΄μŠ€ 토큰이 λ§Œλ£Œλ˜μ—ˆλŠ”μ§€ ν™•μΈν•œλ‹€.
*
* @return 토큰이 κ°±μ‹ λœμ§€ 7일이 μ§€λ‚¬κ±°λ‚˜ 토큰이 λΉ„ν™œμ„±ν™” λ˜μ—ˆλ‹€λ©΄ true, 그렇지 μ•ŠμœΌλ©΄ false
*/
public boolean isExpired() {
LocalDateTime now = LocalDateTime.now();

return !activated || lastSignedInAt.plusDays(7).isBefore(now);
}

public void activate() {
lastSignedInAt = LocalDateTime.now();
this.activated = Boolean.TRUE;
}

Expand All @@ -75,14 +87,22 @@ public void updateLastSignedInAt() {
}

/**
* λ””λ°”μ΄μŠ€ 토큰이 λ§Œλ£Œλ˜μ—ˆλŠ”μ§€ ν™•μΈν•œλ‹€.
*
* @return 토큰이 κ°±μ‹ λœμ§€ 7일이 μ§€λ‚¬μœΌλ©΄ true, 그렇지 μ•ŠμœΌλ©΄ false
* ν† ν°μ˜ μ†Œμœ μžλ₯Ό ν™•μΈν•˜κ³  ν•„μš”ν•œ μƒνƒœ 변경을 μˆ˜ν–‰ν•©λ‹ˆλ‹€.
* λ‹€λ₯Έ μ†Œμœ μžμΈ 경우 μ†Œμœ μžλ₯Ό κ°±μ‹ ν•˜κ³ , 같은 μ†Œμœ μžμΈ 경우 ν™œμ„±ν™”λ§Œ μˆ˜ν–‰ν•©λ‹ˆλ‹€.
*/
public boolean isExpired() {
LocalDateTime now = LocalDateTime.now();
public void handleOwner(User newUser, String newDeviceId) {
Objects.requireNonNull(newUser, "userλŠ” null이 될 수 μ—†μŠ΅λ‹ˆλ‹€.");
Objects.requireNonNull(newDeviceId, "deviceIdλŠ” null이 될 수 μ—†μŠ΅λ‹ˆλ‹€.");

if (!this.user.equals(newUser)) {
this.user = newUser;
}

if (!this.deviceId.equals(newDeviceId)) {
this.deviceId = newDeviceId;
}

return lastSignedInAt.plusDays(7).isBefore(now);
this.activate();
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,11 @@
@RequiredArgsConstructor
public enum DeviceTokenErrorCode implements BaseErrorCode {
/* 404 NOT_FOUND */
NOT_FOUND_DEVICE(StatusCode.NOT_FOUND, ReasonCode.REQUESTED_RESOURCE_NOT_FOUND, "λ””λ°”μ΄μŠ€λ₯Ό 찾을 수 μ—†μŠ΅λ‹ˆλ‹€.");
NOT_FOUND_DEVICE(StatusCode.NOT_FOUND, ReasonCode.REQUESTED_RESOURCE_NOT_FOUND, "λ””λ°”μ΄μŠ€λ₯Ό 찾을 수 μ—†μŠ΅λ‹ˆλ‹€."),

/* 409 CONFLICT */
DUPLICATED_DEVICE_TOKEN(StatusCode.CONFLICT, ReasonCode.RESOURCE_ALREADY_EXISTS, "이미 λ“±λ‘λœ λ””λ°”μ΄μŠ€ ν† ν°μž…λ‹ˆλ‹€."),
;

private final StatusCode statusCode;
private final ReasonCode reasonCode;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@ public interface DeviceTokenRepository extends JpaRepository<DeviceToken, Long>

List<DeviceToken> findAllByUser_Id(Long userId);

Optional<DeviceToken> findByToken(String token);

List<DeviceToken> findAllByUser_IdAndDeviceId(Long userId, String deviceId);

@Modifying(clearAutomatically = true)
@Transactional
@Query("UPDATE DeviceToken d SET d.activated = false WHERE d.user.id = :userId")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,12 @@

import kr.co.pennyway.common.annotation.DomainService;
import kr.co.pennyway.domain.domains.device.domain.DeviceToken;
import kr.co.pennyway.domain.domains.device.exception.DeviceTokenErrorCode;
import kr.co.pennyway.domain.domains.device.exception.DeviceTokenErrorException;
import kr.co.pennyway.domain.domains.device.repository.DeviceTokenRepository;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.dao.DataIntegrityViolationException;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;
Expand All @@ -16,9 +19,18 @@
public class DeviceTokenRdbService {
private final DeviceTokenRepository deviceTokenRepository;

/**
* @throws DeviceTokenErrorException μ€‘λ³΅λœ λ””λ°”μ΄μŠ€ 토큰이 이미 μ‘΄μž¬ν•˜λŠ” 경우
*/
@Transactional
public DeviceToken createDevice(DeviceToken deviceToken) {
return deviceTokenRepository.save(deviceToken);
try {
return deviceTokenRepository.save(deviceToken);
} catch (DataIntegrityViolationException e) {
log.error("DeviceToken 등둝 쀑 쀑볡 μ—λŸ¬κ°€ λ°œμƒν–ˆμŠ΅λ‹ˆλ‹€. deviceToken: {}", deviceToken);

throw new DeviceTokenErrorException(DeviceTokenErrorCode.DUPLICATED_DEVICE_TOKEN);
}
}

/**
Expand All @@ -29,6 +41,16 @@ public Optional<DeviceToken> readDeviceByUserIdAndToken(Long userId, String toke
return deviceTokenRepository.findByUser_IdAndToken(userId, token);
}

@Transactional(readOnly = true)
public Optional<DeviceToken> readDeviceByToken(String token) {
return deviceTokenRepository.findByToken(token);
}

@Transactional(readOnly = true)
public List<DeviceToken> readByUserIdAndDeviceId(Long userId, String deviceId) {
return deviceTokenRepository.findAllByUser_IdAndDeviceId(userId, deviceId);
}

/**
* @return λΉ„ν™œμ„±ν™”λœ λ””λ°”μ΄μŠ€ 토큰 정보λ₯Ό ν¬ν•¨ν•©λ‹ˆλ‹€.
*/
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
package kr.co.pennyway.domain.context.account.service;

import kr.co.pennyway.common.annotation.DomainService;
import kr.co.pennyway.domain.domains.device.domain.DeviceToken;
import kr.co.pennyway.domain.domains.device.exception.DeviceTokenErrorCode;
import kr.co.pennyway.domain.domains.device.exception.DeviceTokenErrorException;
import kr.co.pennyway.domain.domains.device.service.DeviceTokenRdbService;
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;
import kr.co.pennyway.domain.domains.user.service.UserRdbService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;

@Slf4j
@DomainService
@RequiredArgsConstructor
public class DeviceTokenRegisterService {
private final UserRdbService userRdbService;
private final DeviceTokenRdbService deviceTokenRdbService;

/**
* μ‚¬μš©μžμ˜ λ””λ°”μ΄μŠ€ 토큰을 μƒμ„±ν•˜κ±°λ‚˜ κ°±μ‹ ν•œλ‹€.
*
* <pre>
* [λΉ„μ¦ˆλ‹ˆμŠ€ κ·œμΉ™]
* - 같은 {userId, deviceId}에 λŒ€ν•΄ μƒˆλ‘œμš΄ 토큰이 λ°œκΈ‰λ  수 μžˆμ§€λ§Œ, ν™œμ„±ν™”λœ 토큰은 ν•˜λ‚˜μ—¬μ•Ό ν•©λ‹ˆλ‹€.
* - {deviceId, token} 쑰합은 μ‹œμŠ€ν…œ μ „μ²΄μ—μ„œ μœ μΌν•΄μ•Ό ν•©λ‹ˆλ‹€.
* - device token이 이미 λ“±λ‘λœ 경우, μ†Œμœ μž 정보λ₯Ό κ°±μ‹ ν•˜κ³  λ§ˆμ§€λ§‰ 둜그인 μ‹œκ°„μ„ κ°±μ‹ ν•œλ‹€.
* - device token이 λ“±λ‘λ˜μ§€ μ•Šμ€ 경우, μƒˆλ‘œμš΄ device token을 μƒμ„±ν•œλ‹€.
* </pre>
*
* @param userId μ‚¬μš©μž μ‹λ³„μž
* @param deviceId λ””λ°”μ΄μŠ€ μ‹λ³„μž
* @param deviceName λ””λ°”μ΄μŠ€ 이름
* @param deviceToken λ””λ°”μ΄μŠ€ 토큰
* @return {@link DeviceToken} μ‚¬μš©μžμ˜ 기기둜 λ“±λ‘λœ Device 정보
*/
@Transactional
public DeviceToken execute(Long userId, String deviceId, String deviceName, String deviceToken) {
User user = userRdbService.readUser(userId).orElseThrow(() -> new UserErrorException(UserErrorCode.NOT_FOUND));

return getOrCreateDevice(user, deviceId, deviceName, deviceToken);
}

/**
* μ‚¬μš©μžμ˜ λ””λ°”μ΄μŠ€ 토큰을 μƒμ„±ν•©λ‹ˆλ‹€.
* λ§Œμ•½, 이미 λ“±λ‘λœ λ””λ°”μ΄μŠ€ 토큰이 μ‘΄μž¬ν•œλ‹€λ©΄, ν•΄λ‹Ή 토큰을 κ°±μ‹ ν•˜κ³  λ°˜ν™˜ν•©λ‹ˆλ‹€.
*/
private DeviceToken getOrCreateDevice(User user, String deviceId, String deviceName, String deviceToken) {
return deviceTokenRdbService.readDeviceByToken(deviceToken)
.map(originalDeviceToken -> updateDevice(user, deviceId, originalDeviceToken))
.orElseGet(() -> createDevice(user, deviceId, deviceName, deviceToken));
}

private DeviceToken updateDevice(User user, String deviceId, DeviceToken originalDeviceToken) {
if (!originalDeviceToken.getDeviceId().equals(deviceId) && originalDeviceToken.isActivated()) {
throw new DeviceTokenErrorException(DeviceTokenErrorCode.DUPLICATED_DEVICE_TOKEN);
}

originalDeviceToken.handleOwner(user, deviceId);
return originalDeviceToken;
}

private DeviceToken createDevice(User user, String deviceId, String deviceName, String deviceToken) {
deactivateExistingTokens(user.getId(), deviceId);

DeviceToken newDeviceToken = DeviceToken.of(deviceToken, deviceId, deviceName, user);

return deviceTokenRdbService.createDevice(newDeviceToken);
}

/**
* νŠΉμ • μ‚¬μš©μžμ˜ λ””λ°”μ΄μŠ€μ— λŒ€ν•œ κΈ°μ‘΄ ν™œμ„± 토큰듀을 λΉ„ν™œμ„±ν™”ν•©λ‹ˆλ‹€.
* μƒˆλ‘œμš΄ 토큰 등둝 μ‹œ ν˜ΈμΆœλ˜μ–΄ ν•˜λ‚˜μ˜ λ””λ°”μ΄μŠ€μ— ν•˜λ‚˜μ˜ ν™œμ„± ν† ν°λ§Œ μ‘΄μž¬ν•˜λ„λ‘ 보μž₯ν•©λ‹ˆλ‹€.
*/
private void deactivateExistingTokens(Long userId, String deviceId) {
List<DeviceToken> userDeviceTokens = deviceTokenRdbService.readByUserIdAndDeviceId(userId, deviceId);

userDeviceTokens.stream()
.filter(DeviceToken::isActivated)
.forEach(DeviceToken::deactivate);
}
}
Loading
Loading