Solution: UserService

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() }