class UserService(
private val userRepository: UserRepository,
private val userDtoFactory: UserDtoFactory,
private val tokenRepository: TokenRepository,
private val logger: Logger,
) {
private val userByIdCache: Cache<String, User> = cache {
clearAfterWrite = 1.minutes
clearAfterRead = 1.minutes
load { id: String ->
userRepository.getUser(id)
?.toUser()
?.also { userByKeyCache.store(it.key, it) }
}
}
private val userByKeyCache: Cache<String, User> = cache {
clearAfterWrite = 1.minutes
clearAfterRead = 1.minutes
load { key: String ->
userRepository.getUserByKey(key)
?.toUser()
?.also { userByIdCache.store(it.id, it) }
}
}
fun getUser(id: String): User? = userByIdCache.get(id)
fun getUserByKey(key: String): User? = userByKeyCache.get(key)
fun getToken(email: String, passwordHash: String): String =
userRepository.getUserByEmail(email)
?.takeIf { it.passwordHash == passwordHash }
?.let { tokenRepository.createToken(it.id, it.isAdmin) }
?: error("Wrong email or password")
fun updateUser(token: String, userPatch: UserPatch): User =
tokenRepository.getUserId(token)
?.let { userRepository.getUser(it) }
?.let { userDto ->
userDto.copy(
email = userPatch.email ?: userDto.email,
name = userPatch.name ?: userDto.name,
surname = userPatch.surname ?: userDto.surname,
)
}
?.also {
userRepository.updateUser(it)
userByIdCache.remove(it.id)
userByKeyCache.remove(it.key)
logger.log("User updated: $it")
}
?.toUser()
?: error("User not found")
fun addUser(token: String, addUser: AddUser): User {
if (!tokenRepository.isAdmin(token)) {
error("Only admin can add user")
}
return userDtoFactory.produceUserDto(addUser)
.also(userRepository::addUser)
.toUser()
.also { logger.log("User added: $it") }
}
fun userStatistics(token: String): UserStatistics {
if (!tokenRepository.isAdmin(token)) {
error("Only admin can get statistics")
}
return UserStatistics(
numberOfUsersCreatedEachDay = userRepository
.getAllUsers()
.groupingBy { it.creationTime.toLocalDate() }
.eachCount()
)
}
fun clearCache() {
userByIdCache.clear()
userByKeyCache.clear()
}
}
class UserDtoFactory(
private val timeProvider: TimeProvider,
private val uuidGenerator: UuidGenerator,
private val userKeyGenerator: UserKeyGenerator,
) {
fun produceUserDto(addUser: AddUser): UserDto = UserDto(
id = uuidGenerator.generate(),
key = userKeyGenerator
.findPublicKey(addUser.name, addUser.surname)
?: uuidGenerator.generate(),
email = addUser.email,
name = addUser.name,
surname = addUser.surname,
passwordHash = addUser.passwordHash,
isAdmin = false,
creationTime = timeProvider.now(),
)
}
class RealUserKeyGenerator(
private val userRepository: UserRepository,
): UserKeyGenerator {
override fun findPublicKey(
name: String,
surname: String
): String? = sequenceOf(
"$name$surname",
"$surname$name",
"$name${surname.first()}",
"${name.first()}$surname",
"$surname${name.first()}",
"${surname.first()}$name",
).map(::properKey)
.filter { it.length >= 4 }
.find(userRepository::isAvailableKey)
private fun properKey(original: String): String = original
.replace("[^0-9a-zA-Z]".toRegex(), "")
.lowercase()
.trim()
}