Skip to content

Commit

Permalink
feat: add userService and its unit tests
Browse files Browse the repository at this point in the history
  • Loading branch information
ShiinaKin committed Sep 25, 2024
1 parent ea4eef3 commit 005c63f
Show file tree
Hide file tree
Showing 7 changed files with 663 additions and 9 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -43,5 +43,4 @@ data class UserManageInsertRequest(
val password: String,
val email: String,
val isDefaultImagePrivate: Boolean,
val defaultAlbumId: Long
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package io.sakurasou.exception.service.user

import io.sakurasou.exception.ServiceThrowable

/**
* @author Shiina Kin
* 2024/9/25 12:44
*/
class UserDeleteFailedException(
cause: ServiceThrowable? = null
) : ServiceThrowable() {
override val code: Int
get() = 4000
override var message: String = "User Delete Failed"

init {
message = (message + (cause?.let { ", " + it.message } ?: ""))
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package io.sakurasou.exception.service.user

import io.sakurasou.exception.ServiceThrowable

/**
* @author Shiina Kin
* 2024/9/25 12:44
*/
class UserInsertFailedException(
cause: ServiceThrowable? = null,
reason: String? = null
) : ServiceThrowable() {
override val code: Int
get() = 4000
override var message: String = "User Insert Failed"

init {
message += if (cause != null) ", ${cause.message}" else if (reason != null) ", $reason" else ""
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package io.sakurasou.exception.service.user

import io.sakurasou.exception.ServiceThrowable

/**
* @author Shiina Kin
* 2024/9/25 12:44
*/
class UserUpdateFailedException(
cause: ServiceThrowable? = null
) : ServiceThrowable() {
override val code: Int
get() = 4000
override var message: String = "User Update Failed"

init {
message = (message + (cause?.let { ", " + it.message } ?: ""))
}
}
13 changes: 12 additions & 1 deletion app/src/main/kotlin/io/sakurasou/service/user/UserService.kt
Original file line number Diff line number Diff line change
@@ -1,11 +1,22 @@
package io.sakurasou.service.user

import io.sakurasou.controller.request.UserInsertRequest
import io.sakurasou.controller.request.*
import io.sakurasou.controller.vo.PageResult
import io.sakurasou.controller.vo.UserPageVO
import io.sakurasou.controller.vo.UserVO

/**
* @author ShiinaKin
* 2024/9/5 15:30
*/
interface UserService {
suspend fun saveUser(userInsertRequest: UserInsertRequest)
suspend fun saveUserManually(userManageInsertRequest: UserManageInsertRequest)
suspend fun deleteUser(id: Long)
suspend fun patchSelf(id: Long, patchRequest: UserSelfPatchRequest)
suspend fun patchUser(id: Long, patchRequest: UserManagePatchRequest)
suspend fun banUser(id: Long)
suspend fun unbanUser(id: Long)
suspend fun fetchUser(id: Long): UserVO
suspend fun pageUsers(pageRequest: PageRequest): PageResult<UserPageVO>
}
158 changes: 157 additions & 1 deletion app/src/main/kotlin/io/sakurasou/service/user/UserServiceImpl.kt
Original file line number Diff line number Diff line change
@@ -1,14 +1,23 @@
package io.sakurasou.service.user

import at.favre.lib.crypto.bcrypt.BCrypt
import io.sakurasou.controller.request.UserInsertRequest
import io.sakurasou.controller.request.*
import io.sakurasou.controller.vo.PageResult
import io.sakurasou.controller.vo.UserPageVO
import io.sakurasou.controller.vo.UserVO
import io.sakurasou.exception.controller.access.SignupNotAllowedException
import io.sakurasou.exception.service.user.UserDeleteFailedException
import io.sakurasou.exception.service.user.UserInsertFailedException
import io.sakurasou.exception.service.user.UserNotFoundException
import io.sakurasou.exception.service.user.UserUpdateFailedException
import io.sakurasou.model.DatabaseSingleton.dbQuery
import io.sakurasou.model.dao.album.AlbumDao
import io.sakurasou.model.dao.group.GroupDao
import io.sakurasou.model.dao.image.ImageDao
import io.sakurasou.model.dao.user.UserDao
import io.sakurasou.model.dto.UserInsertDTO
import io.sakurasou.model.dto.UserManageUpdateDTO
import io.sakurasou.model.dto.UserSelfUpdateDTO
import io.sakurasou.service.setting.SettingService
import kotlinx.datetime.Clock
import kotlinx.datetime.TimeZone
Expand Down Expand Up @@ -51,4 +60,151 @@ class UserServiceImpl(
}
}

override suspend fun saveUserManually(userManageInsertRequest: UserManageInsertRequest) {
val rawPassword = userManageInsertRequest.password
val encodePassword = BCrypt.withDefaults().hashToString(12, rawPassword.toCharArray())

val now = Clock.System.now().toLocalDateTime(TimeZone.currentSystemDefault())

val userInsertDTO = UserInsertDTO(
groupId = userManageInsertRequest.groupId,
username = userManageInsertRequest.username,
password = encodePassword,
email = userManageInsertRequest.email,
isDefaultImagePrivate = userManageInsertRequest.isDefaultImagePrivate,
defaultAlbumId = null,
isBanned = false,
updateTime = now,
createTime = now
)
runCatching {
dbQuery {
val userId = userDao.saveUser(userInsertDTO)
val defaultAlbumId = albumDao.initAlbumForUser(userId)
userDao.updateUserDefaultAlbumId(userId, defaultAlbumId)
Unit
}
}.onFailure {
throw UserInsertFailedException(null, "Possibly due to duplicate Username")
}
}

override suspend fun deleteUser(id: Long) {
runCatching {
dbQuery {
val influenceRowCnt = userDao.deleteUserById(id)
if (influenceRowCnt < 1) throw UserNotFoundException()
}
}.onFailure {
if (it is UserNotFoundException) throw UserDeleteFailedException(it)
else throw it
}
}

override suspend fun patchSelf(id: Long, patchRequest: UserSelfPatchRequest) {
dbQuery {
val oldUserInfo = userDao.findUserById(id) ?: throw UserNotFoundException()

val encodePassword = patchRequest.password?.let {
BCrypt.withDefaults().hashToString(12, it.toCharArray())
} ?: oldUserInfo.password
val now = Clock.System.now().toLocalDateTime(TimeZone.currentSystemDefault())

val selfUpdateDTO = UserSelfUpdateDTO(
id = id,
password = encodePassword,
email = patchRequest.email ?: oldUserInfo.email,
isDefaultImagePrivate = patchRequest.isDefaultImagePrivate ?: oldUserInfo.isDefaultImagePrivate,
defaultAlbumId = patchRequest.defaultAlbumId ?: oldUserInfo.defaultAlbumId,
updateTime = now
)

runCatching {
val influenceRowCnt = userDao.updateSelfById(selfUpdateDTO)
if (influenceRowCnt < 1) throw UserNotFoundException()
}.onFailure {
if (it is UserNotFoundException) throw UserUpdateFailedException(it)
else throw it
}
}
}

override suspend fun patchUser(id: Long, patchRequest: UserManagePatchRequest) {
dbQuery {
val oldUserInfo = userDao.findUserById(id) ?: throw UserNotFoundException()

val encodePassword = patchRequest.password?.let {
BCrypt.withDefaults().hashToString(12, it.toCharArray())
} ?: oldUserInfo.password
val now = Clock.System.now().toLocalDateTime(TimeZone.currentSystemDefault())

val userUpdateDTO = UserManageUpdateDTO(
id = id,
groupId = patchRequest.groupId ?: oldUserInfo.groupId,
password = encodePassword,
email = patchRequest.email ?: oldUserInfo.email,
isDefaultImagePrivate = patchRequest.isDefaultImagePrivate ?: oldUserInfo.isDefaultImagePrivate,
defaultAlbumId = patchRequest.defaultAlbumId ?: oldUserInfo.defaultAlbumId,
updateTime = now
)

val isModifyGroup = patchRequest.groupId != null

runCatching {
val influenceRowCnt = userDao.updateUserById(userUpdateDTO)
if (influenceRowCnt < 1) throw UserNotFoundException()
if (isModifyGroup) imageDao.updateImageGroupIdByUserId(id, userUpdateDTO.groupId)
}.onFailure {
if (it is UserNotFoundException) throw UserUpdateFailedException(it)
else throw it
}
}
}

override suspend fun banUser(id: Long) {
runCatching {
dbQuery {
val influenceRowCnt = userDao.updateUserBanStatusById(id, true)
if (influenceRowCnt < 1) throw UserNotFoundException()
}
}.onFailure {
if (it is UserNotFoundException) throw UserUpdateFailedException(it)
else throw it
}
}

override suspend fun unbanUser(id: Long) {
runCatching {
dbQuery {
val influenceRowCnt = userDao.updateUserBanStatusById(id, false)
if (influenceRowCnt < 1) throw UserNotFoundException()
}
}.onFailure {
if (it is UserNotFoundException) throw UserUpdateFailedException(it)
else throw it
}
}

override suspend fun fetchUser(id: Long): UserVO {
return dbQuery {
val user = userDao.findUserById(id) ?: throw UserNotFoundException()
val group = groupDao.findGroupById(user.groupId)!!
val (count, totalSize) = imageDao.getImageCountAndTotalSizeOfUser(id)
UserVO(
id = user.id,
username = user.name,
groupName = group.name,
email = user.email,
isDefaultImagePrivate = user.isDefaultImagePrivate,
isBanned = user.isBanned,
createTime = user.createTime,
imageCount = count,
totalImageSize = totalSize
)
}
}

override suspend fun pageUsers(pageRequest: PageRequest): PageResult<UserPageVO> {
return dbQuery { userDao.pagination(pageRequest) }
}
}
Loading

0 comments on commit 005c63f

Please sign in to comment.