diff --git a/src/main/kotlin/andreas311/miso/domain/notification/adapter/input/NotificationAdapter.kt b/src/main/kotlin/andreas311/miso/domain/notification/adapter/input/NotificationAdapter.kt index b7963494..9d9ca350 100644 --- a/src/main/kotlin/andreas311/miso/domain/notification/adapter/input/NotificationAdapter.kt +++ b/src/main/kotlin/andreas311/miso/domain/notification/adapter/input/NotificationAdapter.kt @@ -4,16 +4,24 @@ import andreas311.miso.common.annotation.RequestController import andreas311.miso.domain.notification.adapter.input.data.DetailNotificationResponse import andreas311.miso.domain.notification.adapter.input.mapper.NotificationDataMapper import andreas311.miso.domain.notification.application.port.input.DetailNotificationUseCase +import andreas311.miso.domain.notification.application.port.input.SaveDeviceTokenUseCase import org.springframework.http.HttpStatus import org.springframework.http.ResponseEntity import org.springframework.web.bind.annotation.GetMapping import org.springframework.web.bind.annotation.PathVariable +import org.springframework.web.bind.annotation.PostMapping @RequestController("/notification") class NotificationAdapter( private val notificationDataMapper: NotificationDataMapper, - private val detailNotificationUseCase: DetailNotificationUseCase + private val saveDeviceTokenUseCase: SaveDeviceTokenUseCase, + private val detailNotificationUseCase: DetailNotificationUseCase, ) { + @PostMapping("/save/{token}") + fun save(@PathVariable(name = "token") deviceToken: String): ResponseEntity = + saveDeviceTokenUseCase.execute(deviceToken) + .let { ResponseEntity.status(HttpStatus.CREATED).build() } + @GetMapping("/{id}") fun detail(@PathVariable id: Long): ResponseEntity = detailNotificationUseCase.execute(id) diff --git a/src/main/kotlin/andreas311/miso/domain/notification/adapter/output/persistence/CommandDeviceTokenPersistenceAdapter.kt b/src/main/kotlin/andreas311/miso/domain/notification/adapter/output/persistence/CommandDeviceTokenPersistenceAdapter.kt new file mode 100644 index 00000000..36456280 --- /dev/null +++ b/src/main/kotlin/andreas311/miso/domain/notification/adapter/output/persistence/CommandDeviceTokenPersistenceAdapter.kt @@ -0,0 +1,23 @@ +package andreas311.miso.domain.notification.adapter.output.persistence + +import andreas311.miso.domain.notification.adapter.output.persistence.mapper.DeviceTokenMapper +import andreas311.miso.domain.notification.adapter.output.persistence.repository.DeviceTokenRepository +import andreas311.miso.domain.notification.application.port.output.CommandDeviceTokenPort +import andreas311.miso.domain.notification.domain.DeviceToken +import org.springframework.stereotype.Component + +@Component +class CommandDeviceTokenPersistenceAdapter( + private val deviceTokenMapper: DeviceTokenMapper, + private val deviceTokenRepository: DeviceTokenRepository +) : CommandDeviceTokenPort { + override fun saveDeviceToken(deviceToken: DeviceToken): DeviceToken { + val deviceTokenEntity = deviceTokenRepository.save(deviceTokenMapper toEntity deviceToken) + return deviceTokenMapper.toDomain(deviceTokenEntity)!! + } + + override fun deleteDeviceToken(deviceToken: DeviceToken) { + val deviceTokenEntity = deviceTokenMapper toEntity deviceToken + return deviceTokenRepository.delete(deviceTokenEntity) + } +} \ No newline at end of file diff --git a/src/main/kotlin/andreas311/miso/domain/notification/adapter/output/persistence/QueryDeviceTokenPersistenceAdapter.kt b/src/main/kotlin/andreas311/miso/domain/notification/adapter/output/persistence/QueryDeviceTokenPersistenceAdapter.kt new file mode 100644 index 00000000..4682a97c --- /dev/null +++ b/src/main/kotlin/andreas311/miso/domain/notification/adapter/output/persistence/QueryDeviceTokenPersistenceAdapter.kt @@ -0,0 +1,19 @@ +package andreas311.miso.domain.notification.adapter.output.persistence + +import andreas311.miso.domain.notification.adapter.output.persistence.mapper.DeviceTokenMapper +import andreas311.miso.domain.notification.adapter.output.persistence.repository.DeviceTokenRepository +import andreas311.miso.domain.notification.application.port.output.QueryDeviceTokenPort +import andreas311.miso.domain.notification.domain.DeviceToken +import org.springframework.stereotype.Component +import java.util.* + +@Component +class QueryDeviceTokenPersistenceAdapter( + private val deviceTokenMapper: DeviceTokenMapper, + private val deviceTokenRepository: DeviceTokenRepository +) : QueryDeviceTokenPort { + override fun findByUserIdOrNull(id: UUID): DeviceToken? { + val deviceTokenEntity = deviceTokenRepository.findByUserId(id) + return deviceTokenMapper toDomain deviceTokenEntity + } +} \ No newline at end of file diff --git a/src/main/kotlin/andreas311/miso/domain/notification/adapter/output/persistence/entity/DeviceTokenEntity.kt b/src/main/kotlin/andreas311/miso/domain/notification/adapter/output/persistence/entity/DeviceTokenEntity.kt new file mode 100644 index 00000000..8dbed0e5 --- /dev/null +++ b/src/main/kotlin/andreas311/miso/domain/notification/adapter/output/persistence/entity/DeviceTokenEntity.kt @@ -0,0 +1,16 @@ +package andreas311.miso.domain.notification.adapter.output.persistence.entity + +import org.springframework.data.annotation.Id +import org.springframework.data.redis.core.RedisHash +import org.springframework.data.redis.core.index.Indexed +import java.util.* + +@RedisHash(value = "deviceToken") +data class DeviceTokenEntity( + @Indexed + val userId: UUID, + + @Id + @Indexed + val deviceToken: String +) diff --git a/src/main/kotlin/andreas311/miso/domain/notification/adapter/output/persistence/mapper/DeviceTokenMapper.kt b/src/main/kotlin/andreas311/miso/domain/notification/adapter/output/persistence/mapper/DeviceTokenMapper.kt new file mode 100644 index 00000000..9c4b2c21 --- /dev/null +++ b/src/main/kotlin/andreas311/miso/domain/notification/adapter/output/persistence/mapper/DeviceTokenMapper.kt @@ -0,0 +1,22 @@ +package andreas311.miso.domain.notification.adapter.output.persistence.mapper + +import andreas311.miso.domain.notification.adapter.output.persistence.entity.DeviceTokenEntity +import andreas311.miso.domain.notification.domain.DeviceToken +import org.springframework.stereotype.Component + +@Component +class DeviceTokenMapper { + infix fun toEntity(domain: DeviceToken): DeviceTokenEntity = + DeviceTokenEntity( + userId = domain.userId, + deviceToken = domain.deviceToken + ) + + infix fun toDomain(entity: DeviceTokenEntity?): DeviceToken? = + entity?.let { + DeviceToken( + userId = entity.userId, + deviceToken = entity.deviceToken + ) + } +} \ No newline at end of file diff --git a/src/main/kotlin/andreas311/miso/domain/notification/adapter/output/persistence/repository/DeviceTokenRepository.kt b/src/main/kotlin/andreas311/miso/domain/notification/adapter/output/persistence/repository/DeviceTokenRepository.kt new file mode 100644 index 00000000..91c0de6c --- /dev/null +++ b/src/main/kotlin/andreas311/miso/domain/notification/adapter/output/persistence/repository/DeviceTokenRepository.kt @@ -0,0 +1,9 @@ +package andreas311.miso.domain.notification.adapter.output.persistence.repository + +import andreas311.miso.domain.notification.adapter.output.persistence.entity.DeviceTokenEntity +import org.springframework.data.repository.CrudRepository +import java.util.UUID + +interface DeviceTokenRepository : CrudRepository { + fun findByUserId(userId: UUID): DeviceTokenEntity? +} \ No newline at end of file diff --git a/src/main/kotlin/andreas311/miso/domain/notification/application/port/input/SaveDeviceTokenUseCase.kt b/src/main/kotlin/andreas311/miso/domain/notification/application/port/input/SaveDeviceTokenUseCase.kt new file mode 100644 index 00000000..9a915562 --- /dev/null +++ b/src/main/kotlin/andreas311/miso/domain/notification/application/port/input/SaveDeviceTokenUseCase.kt @@ -0,0 +1,5 @@ +package andreas311.miso.domain.notification.application.port.input + +interface SaveDeviceTokenUseCase { + fun execute(token: String) +} \ No newline at end of file diff --git a/src/main/kotlin/andreas311/miso/domain/notification/application/port/output/CommandDeviceTokenPort.kt b/src/main/kotlin/andreas311/miso/domain/notification/application/port/output/CommandDeviceTokenPort.kt new file mode 100644 index 00000000..3235581d --- /dev/null +++ b/src/main/kotlin/andreas311/miso/domain/notification/application/port/output/CommandDeviceTokenPort.kt @@ -0,0 +1,8 @@ +package andreas311.miso.domain.notification.application.port.output + +import andreas311.miso.domain.notification.domain.DeviceToken + +interface CommandDeviceTokenPort { + fun saveDeviceToken(deviceToken: DeviceToken): DeviceToken + fun deleteDeviceToken(deviceToken: DeviceToken) +} \ No newline at end of file diff --git a/src/main/kotlin/andreas311/miso/domain/notification/application/port/output/QueryDeviceTokenPort.kt b/src/main/kotlin/andreas311/miso/domain/notification/application/port/output/QueryDeviceTokenPort.kt new file mode 100644 index 00000000..bfda6efd --- /dev/null +++ b/src/main/kotlin/andreas311/miso/domain/notification/application/port/output/QueryDeviceTokenPort.kt @@ -0,0 +1,8 @@ +package andreas311.miso.domain.notification.application.port.output + +import andreas311.miso.domain.notification.domain.DeviceToken +import java.util.UUID + +interface QueryDeviceTokenPort { + fun findByUserIdOrNull(id: UUID): DeviceToken? +} \ No newline at end of file diff --git a/src/main/kotlin/andreas311/miso/domain/notification/application/service/SaveDeviceTokenService.kt b/src/main/kotlin/andreas311/miso/domain/notification/application/service/SaveDeviceTokenService.kt new file mode 100644 index 00000000..34c2aeec --- /dev/null +++ b/src/main/kotlin/andreas311/miso/domain/notification/application/service/SaveDeviceTokenService.kt @@ -0,0 +1,32 @@ +package andreas311.miso.domain.notification.application.service + +import andreas311.miso.common.annotation.RollbackService +import andreas311.miso.domain.auth.application.port.output.UserSecurityPort +import andreas311.miso.domain.notification.application.port.input.SaveDeviceTokenUseCase +import andreas311.miso.domain.notification.application.port.output.CommandDeviceTokenPort +import andreas311.miso.domain.notification.application.port.output.QueryDeviceTokenPort +import andreas311.miso.domain.notification.domain.DeviceToken + +@RollbackService +class SaveDeviceTokenService( + private val userSecurityPort: UserSecurityPort, + private val queryDeviceTokenPort: QueryDeviceTokenPort, + private val commandDeviceTokenPort: CommandDeviceTokenPort +) : SaveDeviceTokenUseCase { + override fun execute(token: String) { + val user = userSecurityPort.currentUser() + + val deviceToken = queryDeviceTokenPort.findByUserIdOrNull(user.id) + + deviceToken?.let { + commandDeviceTokenPort.deleteDeviceToken(deviceToken) + } + + commandDeviceTokenPort.saveDeviceToken( + DeviceToken( + userId = user.id, + deviceToken = token + ) + ) + } +} \ No newline at end of file diff --git a/src/main/kotlin/andreas311/miso/domain/notification/domain/DeviceToken.kt b/src/main/kotlin/andreas311/miso/domain/notification/domain/DeviceToken.kt new file mode 100644 index 00000000..856aa4fe --- /dev/null +++ b/src/main/kotlin/andreas311/miso/domain/notification/domain/DeviceToken.kt @@ -0,0 +1,8 @@ +package andreas311.miso.domain.notification.domain + +import java.util.UUID + +data class DeviceToken( + val userId: UUID, + val deviceToken: String +) diff --git a/src/main/kotlin/andreas311/miso/global/security/config/SecurityConfig.kt b/src/main/kotlin/andreas311/miso/global/security/config/SecurityConfig.kt index 751708ee..9312065a 100644 --- a/src/main/kotlin/andreas311/miso/global/security/config/SecurityConfig.kt +++ b/src/main/kotlin/andreas311/miso/global/security/config/SecurityConfig.kt @@ -73,7 +73,7 @@ class SecurityConfig( .antMatchers(HttpMethod.PATCH, "/recyclables/{id}").hasAuthority("ROLE_ADMIN") .antMatchers(HttpMethod.DELETE, "/recyclables/{id}").hasAuthority("ROLE_ADMIN") - .antMatchers(HttpMethod.POST, "/notification/save/{deviceToken}").authenticated() + .antMatchers(HttpMethod.POST, "/notification/save/{token}").authenticated() .antMatchers(HttpMethod.GET, "/notification/{id}").authenticated() .antMatchers(HttpMethod.GET, "/environment").authenticated()