Merge pull request #28 from usbharu/feature/refactor-query

Feature/refactor query
This commit is contained in:
usbharu 2023-08-11 16:09:45 +09:00 committed by GitHub
commit f61c6c1713
79 changed files with 1340 additions and 1395 deletions

View File

@ -12,22 +12,19 @@ import dev.usbharu.hideout.domain.model.job.DeliverReactionJob
import dev.usbharu.hideout.domain.model.job.DeliverRemoveReactionJob
import dev.usbharu.hideout.domain.model.job.ReceiveFollowJob
import dev.usbharu.hideout.plugins.*
import dev.usbharu.hideout.repository.IUserRepository
import dev.usbharu.hideout.query.FollowerQueryService
import dev.usbharu.hideout.query.UserQueryService
import dev.usbharu.hideout.routing.register
import dev.usbharu.hideout.service.activitypub.ActivityPubService
import dev.usbharu.hideout.service.activitypub.ActivityPubUserService
import dev.usbharu.hideout.service.api.IPostApiService
import dev.usbharu.hideout.service.api.IUserApiService
import dev.usbharu.hideout.service.api.UserAuthApiService
import dev.usbharu.hideout.service.api.WebFingerApiService
import dev.usbharu.hideout.service.auth.HttpSignatureVerifyService
import dev.usbharu.hideout.service.auth.IJwtService
import dev.usbharu.hideout.service.core.IMetaService
import dev.usbharu.hideout.service.core.IServerInitialiseService
import dev.usbharu.hideout.service.core.IdGenerateService
import dev.usbharu.hideout.service.core.TwitterSnowflakeIdGenerateService
import dev.usbharu.hideout.service.core.*
import dev.usbharu.hideout.service.job.JobQueueParentService
import dev.usbharu.hideout.service.job.KJobJobQueueParentService
import dev.usbharu.hideout.service.reaction.IReactionService
import dev.usbharu.hideout.service.user.IUserAuthService
import dev.usbharu.hideout.service.user.IUserService
import dev.usbharu.kjob.exposed.ExposedKJob
import io.ktor.client.*
@ -79,7 +76,7 @@ fun Application.parent() {
level = LogLevel.INFO
}
install(httpSignaturePlugin) {
keyMap = KtorKeyMap(get())
keyMap = KtorKeyMap(get(), get())
}
expectSuccess = true
}
@ -95,6 +92,7 @@ fun Application.parent() {
}
}
configureKoin(module, HideoutModule().module)
configureStatusPages()
runBlocking {
inject<IServerInitialiseService>().value.init()
}
@ -103,7 +101,7 @@ fun Application.parent() {
configureStaticRouting()
configureMonitoring()
configureSerialization()
register(inject<IUserService>().value)
register(inject<IUserApiService>().value)
configureSecurity(
inject<JwkProvider>().value,
@ -116,11 +114,11 @@ fun Application.parent() {
activityPubUserService = inject<ActivityPubUserService>().value,
postService = inject<IPostApiService>().value,
userApiService = inject<IUserApiService>().value,
reactionService = inject<IReactionService>().value,
userAuthService = inject<IUserAuthService>().value,
userRepository = inject<IUserRepository>().value,
jwtService = inject<IJwtService>().value,
metaService = inject<IMetaService>().value
userQueryService = inject<UserQueryService>().value,
followerQueryService = inject<FollowerQueryService>().value,
userAuthApiService = inject<UserAuthApiService>().value,
webFingerApiService = inject<WebFingerApiService>().value,
transaction = inject<Transaction>().value
)
}

View File

@ -0,0 +1,8 @@
package dev.usbharu.hideout.exception
class InvalidUsernameOrPasswordException : IllegalArgumentException {
constructor() : super()
constructor(s: String?) : super(s)
constructor(message: String?, cause: Throwable?) : super(message, cause)
constructor(cause: Throwable?) : super(cause)
}

View File

@ -1,14 +1,8 @@
package dev.usbharu.hideout.exception
class NotInitException : Exception {
class NotInitException : IllegalStateException {
constructor() : super()
constructor(message: String?) : super(message)
constructor(s: String?) : super(s)
constructor(message: String?, cause: Throwable?) : super(message, cause)
constructor(cause: Throwable?) : super(cause)
constructor(message: String?, cause: Throwable?, enableSuppression: Boolean, writableStackTrace: Boolean) : super(
message,
cause,
enableSuppression,
writableStackTrace
)
}

View File

@ -0,0 +1,8 @@
package dev.usbharu.hideout.exception
class UsernameAlreadyExistException : IllegalArgumentException {
constructor() : super()
constructor(s: String?) : super(s)
constructor(message: String?, cause: Throwable?) : super(message, cause)
constructor(cause: Throwable?) : super(cause)
}

View File

@ -2,7 +2,8 @@ package dev.usbharu.hideout.plugins
import dev.usbharu.hideout.config.Config
import dev.usbharu.hideout.domain.model.ap.JsonLd
import dev.usbharu.hideout.repository.IUserRepository
import dev.usbharu.hideout.query.UserQueryService
import dev.usbharu.hideout.service.core.Transaction
import dev.usbharu.hideout.service.user.UserAuthService
import dev.usbharu.hideout.util.HttpUtil.Activity
import io.ktor.client.*
@ -164,19 +165,21 @@ val httpSignaturePlugin = createClientPlugin("HttpSign", ::HttpSignaturePluginCo
}
}
class KtorKeyMap(private val userAuthRepository: IUserRepository) : KeyMap {
class KtorKeyMap(private val userQueryService: UserQueryService, private val transaction: Transaction) : KeyMap {
override fun getPublicKey(keyId: String?): PublicKey = runBlocking {
val username = (keyId ?: throw IllegalArgumentException("keyId is null")).substringBeforeLast("#pubkey")
.substringAfterLast("/")
val publicBytes = Base64.getDecoder().decode(
userAuthRepository.findByNameAndDomain(
username,
Config.configData.domain
)?.run {
publicKey
.replace("-----BEGIN PUBLIC KEY-----", "")
.replace("-----END PUBLIC KEY-----", "")
.replace("\n", "")
transaction.transaction {
userQueryService.findByNameAndDomain(
username,
Config.configData.domain
).run {
publicKey
.replace("-----BEGIN PUBLIC KEY-----", "")
.replace("-----END PUBLIC KEY-----", "")
.replace("\n", "")
}
}
)
val x509EncodedKeySpec = X509EncodedKeySpec(publicBytes)
@ -187,13 +190,15 @@ class KtorKeyMap(private val userAuthRepository: IUserRepository) : KeyMap {
val username = (keyId ?: throw IllegalArgumentException("keyId is null")).substringBeforeLast("#pubkey")
.substringAfterLast("/")
val publicBytes = Base64.getDecoder().decode(
userAuthRepository.findByNameAndDomain(
username,
Config.configData.domain
)?.privateKey?.run {
replace("-----BEGIN PRIVATE KEY-----", "")
.replace("-----END PRIVATE KEY-----", "")
.replace("\n", "")
transaction.transaction {
userQueryService.findByNameAndDomain(
username,
Config.configData.domain
).privateKey?.run {
replace("-----BEGIN PRIVATE KEY-----", "")
.replace("-----END PRIVATE KEY-----", "")
.replace("\n", "")
}
}
)
val x509EncodedKeySpec = PKCS8EncodedKeySpec(publicBytes)

View File

@ -1,6 +1,7 @@
package dev.usbharu.hideout.plugins
import dev.usbharu.hideout.repository.IUserRepository
import dev.usbharu.hideout.query.FollowerQueryService
import dev.usbharu.hideout.query.UserQueryService
import dev.usbharu.hideout.routing.activitypub.inbox
import dev.usbharu.hideout.routing.activitypub.outbox
import dev.usbharu.hideout.routing.activitypub.usersAP
@ -12,11 +13,10 @@ import dev.usbharu.hideout.service.activitypub.ActivityPubService
import dev.usbharu.hideout.service.activitypub.ActivityPubUserService
import dev.usbharu.hideout.service.api.IPostApiService
import dev.usbharu.hideout.service.api.IUserApiService
import dev.usbharu.hideout.service.api.UserAuthApiService
import dev.usbharu.hideout.service.api.WebFingerApiService
import dev.usbharu.hideout.service.auth.HttpSignatureVerifyService
import dev.usbharu.hideout.service.auth.IJwtService
import dev.usbharu.hideout.service.core.IMetaService
import dev.usbharu.hideout.service.reaction.IReactionService
import dev.usbharu.hideout.service.user.IUserAuthService
import dev.usbharu.hideout.service.core.Transaction
import dev.usbharu.hideout.service.user.IUserService
import io.ktor.server.application.*
import io.ktor.server.plugins.autohead.*
@ -30,22 +30,22 @@ fun Application.configureRouting(
activityPubUserService: ActivityPubUserService,
postService: IPostApiService,
userApiService: IUserApiService,
reactionService: IReactionService,
userAuthService: IUserAuthService,
userRepository: IUserRepository,
jwtService: IJwtService,
metaService: IMetaService
userQueryService: UserQueryService,
followerQueryService: FollowerQueryService,
userAuthApiService: UserAuthApiService,
webFingerApiService: WebFingerApiService,
transaction: Transaction
) {
install(AutoHeadResponse)
routing {
inbox(httpSignatureVerifyService, activityPubService)
outbox()
usersAP(activityPubUserService, userService)
webfinger(userService)
usersAP(activityPubUserService, userQueryService, followerQueryService, transaction)
webfinger(webFingerApiService)
route("/api/internal/v1") {
posts(postService, reactionService)
posts(postService)
users(userService, userApiService)
auth(userAuthService, userRepository, jwtService)
auth(userAuthApiService)
}
}
}

View File

@ -1,5 +1,6 @@
package dev.usbharu.hideout.plugins
import dev.usbharu.hideout.exception.InvalidUsernameOrPasswordException
import io.ktor.http.*
import io.ktor.server.application.*
import io.ktor.server.plugins.statuspages.*
@ -10,8 +11,12 @@ fun Application.configureStatusPages() {
exception<IllegalArgumentException> { call, cause ->
call.respondText(text = "400: $cause", status = HttpStatusCode.BadRequest)
}
exception<InvalidUsernameOrPasswordException> { call, _ ->
call.respond(HttpStatusCode.Unauthorized)
}
exception<Throwable> { call, cause ->
call.respondText(text = "500: $cause", status = HttpStatusCode.InternalServerError)
call.respondText(text = "500: ${cause.stackTraceToString()}", status = HttpStatusCode.InternalServerError)
cause.printStackTrace()
}
}
}

View File

@ -0,0 +1,12 @@
package dev.usbharu.hideout.query
import dev.usbharu.hideout.domain.model.hideout.entity.User
interface FollowerQueryService {
suspend fun findFollowersById(id: Long): List<User>
suspend fun findFollowersByNameAndDomain(name: String, domain: String): List<User>
suspend fun findFollowingById(id: Long): List<User>
suspend fun findFollowingByNameAndDomain(name: String, domain: String): List<User>
suspend fun appendFollower(user: Long, follower: Long)
suspend fun removeFollower(user: Long, follower: Long)
}

View File

@ -0,0 +1,203 @@
package dev.usbharu.hideout.query
import dev.usbharu.hideout.domain.model.hideout.entity.User
import dev.usbharu.hideout.repository.Users
import dev.usbharu.hideout.repository.UsersFollowers
import org.jetbrains.exposed.sql.*
import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq
import org.koin.core.annotation.Single
import java.time.Instant
@Single
class FollowerQueryServiceImpl : FollowerQueryService {
override suspend fun findFollowersById(id: Long): List<User> {
val followers = Users.alias("FOLLOWERS")
return Users.innerJoin(
otherTable = UsersFollowers,
onColumn = { Users.id },
otherColumn = { userId }
)
.innerJoin(
otherTable = followers,
onColumn = { UsersFollowers.followerId },
otherColumn = { followers[Users.id] }
)
.slice(
followers[Users.id],
followers[Users.name],
followers[Users.domain],
followers[Users.screenName],
followers[Users.description],
followers[Users.password],
followers[Users.inbox],
followers[Users.outbox],
followers[Users.url],
followers[Users.publicKey],
followers[Users.privateKey],
followers[Users.createdAt]
)
.select { Users.id eq id }
.map {
User(
id = it[followers[Users.id]],
name = it[followers[Users.name]],
domain = it[followers[Users.domain]],
screenName = it[followers[Users.screenName]],
description = it[followers[Users.description]],
password = it[followers[Users.password]],
inbox = it[followers[Users.inbox]],
outbox = it[followers[Users.outbox]],
url = it[followers[Users.url]],
publicKey = it[followers[Users.publicKey]],
privateKey = it[followers[Users.privateKey]],
createdAt = Instant.ofEpochMilli(it[followers[Users.createdAt]])
)
}
}
override suspend fun findFollowersByNameAndDomain(name: String, domain: String): List<User> {
val followers = Users.alias("FOLLOWERS")
return Users.innerJoin(
otherTable = UsersFollowers,
onColumn = { Users.id },
otherColumn = { userId }
)
.innerJoin(
otherTable = followers,
onColumn = { UsersFollowers.followerId },
otherColumn = { followers[Users.id] }
)
.slice(
followers[Users.id],
followers[Users.name],
followers[Users.domain],
followers[Users.screenName],
followers[Users.description],
followers[Users.password],
followers[Users.inbox],
followers[Users.outbox],
followers[Users.url],
followers[Users.publicKey],
followers[Users.privateKey],
followers[Users.createdAt]
)
.select { Users.name eq name and (Users.domain eq domain) }
.map {
User(
id = it[followers[Users.id]],
name = it[followers[Users.name]],
domain = it[followers[Users.domain]],
screenName = it[followers[Users.screenName]],
description = it[followers[Users.description]],
password = it[followers[Users.password]],
inbox = it[followers[Users.inbox]],
outbox = it[followers[Users.outbox]],
url = it[followers[Users.url]],
publicKey = it[followers[Users.publicKey]],
privateKey = it[followers[Users.privateKey]],
createdAt = Instant.ofEpochMilli(it[followers[Users.createdAt]])
)
}
}
override suspend fun findFollowingById(id: Long): List<User> {
val followers = Users.alias("FOLLOWERS")
return Users.innerJoin(
otherTable = UsersFollowers,
onColumn = { Users.id },
otherColumn = { userId }
)
.innerJoin(
otherTable = followers,
onColumn = { UsersFollowers.followerId },
otherColumn = { followers[Users.id] }
)
.slice(
followers[Users.id],
followers[Users.name],
followers[Users.domain],
followers[Users.screenName],
followers[Users.description],
followers[Users.password],
followers[Users.inbox],
followers[Users.outbox],
followers[Users.url],
followers[Users.publicKey],
followers[Users.privateKey],
followers[Users.createdAt]
)
.select { followers[Users.id] eq id }
.map {
User(
id = it[followers[Users.id]],
name = it[followers[Users.name]],
domain = it[followers[Users.domain]],
screenName = it[followers[Users.screenName]],
description = it[followers[Users.description]],
password = it[followers[Users.password]],
inbox = it[followers[Users.inbox]],
outbox = it[followers[Users.outbox]],
url = it[followers[Users.url]],
publicKey = it[followers[Users.publicKey]],
privateKey = it[followers[Users.privateKey]],
createdAt = Instant.ofEpochMilli(it[followers[Users.createdAt]])
)
}
}
override suspend fun findFollowingByNameAndDomain(name: String, domain: String): List<User> {
val followers = Users.alias("FOLLOWERS")
return Users.innerJoin(
otherTable = UsersFollowers,
onColumn = { Users.id },
otherColumn = { userId }
)
.innerJoin(
otherTable = followers,
onColumn = { UsersFollowers.followerId },
otherColumn = { followers[Users.id] }
)
.slice(
followers[Users.id],
followers[Users.name],
followers[Users.domain],
followers[Users.screenName],
followers[Users.description],
followers[Users.password],
followers[Users.inbox],
followers[Users.outbox],
followers[Users.url],
followers[Users.publicKey],
followers[Users.privateKey],
followers[Users.createdAt]
)
.select { followers[Users.name] eq name and (followers[Users.domain] eq domain) }
.map {
User(
id = it[followers[Users.id]],
name = it[followers[Users.name]],
domain = it[followers[Users.domain]],
screenName = it[followers[Users.screenName]],
description = it[followers[Users.description]],
password = it[followers[Users.password]],
inbox = it[followers[Users.inbox]],
outbox = it[followers[Users.outbox]],
url = it[followers[Users.url]],
publicKey = it[followers[Users.publicKey]],
privateKey = it[followers[Users.privateKey]],
createdAt = Instant.ofEpochMilli(it[followers[Users.createdAt]])
)
}
}
override suspend fun appendFollower(user: Long, follower: Long) {
UsersFollowers.insert {
it[userId] = user
it[followerId] = follower
}
}
override suspend fun removeFollower(user: Long, follower: Long) {
UsersFollowers.deleteWhere { Users.id eq user and (followerId eq follower) }
}
}

View File

@ -0,0 +1,13 @@
package dev.usbharu.hideout.query
import dev.usbharu.hideout.domain.model.hideout.entity.JwtRefreshToken
interface JwtRefreshTokenQueryService {
suspend fun findById(id: Long): JwtRefreshToken
suspend fun findByToken(token: String): JwtRefreshToken
suspend fun findByUserId(userId: Long): JwtRefreshToken
suspend fun deleteById(id: Long)
suspend fun deleteByToken(token: String)
suspend fun deleteByUserId(userId: Long)
suspend fun deleteAll()
}

View File

@ -0,0 +1,38 @@
package dev.usbharu.hideout.query
import dev.usbharu.hideout.domain.model.hideout.entity.JwtRefreshToken
import dev.usbharu.hideout.repository.JwtRefreshTokens
import dev.usbharu.hideout.repository.toJwtRefreshToken
import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq
import org.jetbrains.exposed.sql.deleteAll
import org.jetbrains.exposed.sql.deleteWhere
import org.jetbrains.exposed.sql.select
import org.koin.core.annotation.Single
@Single
class JwtRefreshTokenQueryServiceImpl : JwtRefreshTokenQueryService {
override suspend fun findById(id: Long): JwtRefreshToken =
JwtRefreshTokens.select { JwtRefreshTokens.id.eq(id) }.single().toJwtRefreshToken()
override suspend fun findByToken(token: String): JwtRefreshToken =
JwtRefreshTokens.select { JwtRefreshTokens.refreshToken.eq(token) }.single().toJwtRefreshToken()
override suspend fun findByUserId(userId: Long): JwtRefreshToken =
JwtRefreshTokens.select { JwtRefreshTokens.userId.eq(userId) }.single().toJwtRefreshToken()
override suspend fun deleteById(id: Long) {
JwtRefreshTokens.deleteWhere { JwtRefreshTokens.id eq id }
}
override suspend fun deleteByToken(token: String) {
JwtRefreshTokens.deleteWhere { refreshToken eq token }
}
override suspend fun deleteByUserId(userId: Long) {
JwtRefreshTokens.deleteWhere { JwtRefreshTokens.userId eq userId }
}
override suspend fun deleteAll() {
JwtRefreshTokens.deleteAll()
}
}

View File

@ -0,0 +1,9 @@
package dev.usbharu.hideout.query
import dev.usbharu.hideout.domain.model.hideout.entity.Post
interface PostQueryService {
suspend fun findById(id: Long): Post
suspend fun findByUrl(url: String): Post
suspend fun findByApId(string: String): Post
}

View File

@ -0,0 +1,16 @@
package dev.usbharu.hideout.query
import dev.usbharu.hideout.domain.model.hideout.entity.Post
import dev.usbharu.hideout.repository.Posts
import dev.usbharu.hideout.repository.toPost
import org.jetbrains.exposed.sql.select
import org.koin.core.annotation.Single
@Single
class PostQueryServiceImpl : PostQueryService {
override suspend fun findById(id: Long): Post = Posts.select { Posts.id eq id }.single().toPost()
override suspend fun findByUrl(url: String): Post = Posts.select { Posts.url eq url }.single().toPost()
override suspend fun findByApId(string: String): Post = Posts.select { Posts.apId eq string }.single().toPost()
}

View File

@ -0,0 +1,37 @@
package dev.usbharu.hideout.query
import dev.usbharu.hideout.domain.model.hideout.dto.PostResponse
@Suppress("LongParameterList")
interface PostResponseQueryService {
suspend fun findById(id: Long, userId: Long?): PostResponse
suspend fun findAll(
since: Long? = null,
until: Long? = null,
minId: Long? = null,
maxId: Long? = null,
limit: Int? = null,
userId: Long? = null
): List<PostResponse>
suspend fun findByUserId(
userId: Long,
since: Long? = null,
until: Long? = null,
minId: Long? = null,
maxId: Long? = null,
limit: Int? = null,
userId2: Long? = null
): List<PostResponse>
suspend fun findByUserNameAndUserDomain(
name: String,
domain: String,
since: Long? = null,
until: Long? = null,
minId: Long? = null,
maxId: Long? = null,
limit: Int? = null,
userId: Long? = null
): List<PostResponse>
}

View File

@ -0,0 +1,68 @@
package dev.usbharu.hideout.query
import dev.usbharu.hideout.domain.model.hideout.dto.PostResponse
import dev.usbharu.hideout.repository.Posts
import dev.usbharu.hideout.repository.Users
import dev.usbharu.hideout.repository.toPost
import dev.usbharu.hideout.repository.toUser
import org.jetbrains.exposed.sql.and
import org.jetbrains.exposed.sql.innerJoin
import org.jetbrains.exposed.sql.select
import org.jetbrains.exposed.sql.selectAll
import org.koin.core.annotation.Single
@Single
class PostResponseQueryServiceImpl : PostResponseQueryService {
override suspend fun findById(id: Long, userId: Long?): PostResponse {
return Posts
.innerJoin(Users, onColumn = { Posts.userId }, otherColumn = { Users.id })
.select { Posts.id eq id }
.single()
.let { PostResponse.from(it.toPost(), it.toUser()) }
}
override suspend fun findAll(
since: Long?,
until: Long?,
minId: Long?,
maxId: Long?,
limit: Int?,
userId: Long?
): List<PostResponse> {
return Posts
.innerJoin(Users, onColumn = { Posts.userId }, otherColumn = { id })
.selectAll()
.map { PostResponse.from(it.toPost(), it.toUser()) }
}
override suspend fun findByUserId(
userId: Long,
since: Long?,
until: Long?,
minId: Long?,
maxId: Long?,
limit: Int?,
userId2: Long?
): List<PostResponse> {
return Posts
.innerJoin(Users, onColumn = { Posts.userId }, otherColumn = { id })
.select { Posts.userId eq userId }
.map { PostResponse.from(it.toPost(), it.toUser()) }
}
override suspend fun findByUserNameAndUserDomain(
name: String,
domain: String,
since: Long?,
until: Long?,
minId: Long?,
maxId: Long?,
limit: Int?,
userId: Long?
): List<PostResponse> {
return Posts
.innerJoin(Users, onColumn = { Posts.userId }, otherColumn = { id })
.select { Users.name eq name and (Users.domain eq domain) }
.map { PostResponse.from(it.toPost(), it.toUser()) }
}
}

View File

@ -0,0 +1,17 @@
package dev.usbharu.hideout.query
import dev.usbharu.hideout.domain.model.hideout.dto.ReactionResponse
import dev.usbharu.hideout.domain.model.hideout.entity.Reaction
interface ReactionQueryService {
suspend fun findByPostId(postId: Long, userId: Long? = null): List<Reaction>
@Suppress("FunctionMaxLength")
suspend fun findByPostIdAndUserIdAndEmojiId(postId: Long, userId: Long, emojiId: Long): Reaction
suspend fun reactionAlreadyExist(postId: Long, userId: Long, emojiId: Long): Boolean
suspend fun deleteByPostIdAndUserId(postId: Long, userId: Long)
suspend fun findByPostIdWithUsers(postId: Long, userId: Long? = null): List<ReactionResponse>
}

View File

@ -0,0 +1,54 @@
package dev.usbharu.hideout.query
import dev.usbharu.hideout.domain.model.hideout.dto.Account
import dev.usbharu.hideout.domain.model.hideout.dto.ReactionResponse
import dev.usbharu.hideout.domain.model.hideout.entity.Reaction
import dev.usbharu.hideout.repository.Reactions
import dev.usbharu.hideout.repository.Users
import dev.usbharu.hideout.repository.toReaction
import org.jetbrains.exposed.sql.*
import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq
import org.koin.core.annotation.Single
@Single
class ReactionQueryServiceImpl : ReactionQueryService {
override suspend fun findByPostId(postId: Long, userId: Long?): List<Reaction> {
return Reactions.select {
Reactions.postId.eq(postId)
}.map { it.toReaction() }
}
@Suppress("FunctionMaxLength")
override suspend fun findByPostIdAndUserIdAndEmojiId(postId: Long, userId: Long, emojiId: Long): Reaction {
return Reactions
.select {
Reactions.postId.eq(postId).and(Reactions.userId.eq(userId)).and(
Reactions.emojiId.eq(emojiId)
)
}
.single()
.toReaction()
}
override suspend fun reactionAlreadyExist(postId: Long, userId: Long, emojiId: Long): Boolean {
return Reactions.select {
Reactions.postId.eq(postId).and(Reactions.userId.eq(userId)).and(
Reactions.emojiId.eq(emojiId)
)
}.empty().not()
}
override suspend fun deleteByPostIdAndUserId(postId: Long, userId: Long) {
Reactions.deleteWhere { Reactions.postId.eq(postId).and(Reactions.userId.eq(userId)) }
}
override suspend fun findByPostIdWithUsers(postId: Long, userId: Long?): List<ReactionResponse> {
return Reactions
.leftJoin(Users, onColumn = { Reactions.userId }, otherColumn = { id })
.select { Reactions.postId.eq(postId) }
.groupBy { _: ResultRow -> ReactionResponse("", true, "", listOf()) }
.map { entry: Map.Entry<ReactionResponse, List<ResultRow>> ->
entry.key.copy(accounts = entry.value.map { Account(it[Users.screenName], "", it[Users.url]) })
}
}
}

View File

@ -0,0 +1,13 @@
package dev.usbharu.hideout.query
import dev.usbharu.hideout.domain.model.hideout.entity.User
interface UserQueryService {
suspend fun findAll(limit: Int, offset: Long): List<User>
suspend fun findById(id: Long): User
suspend fun findByName(name: String): List<User>
suspend fun findByNameAndDomain(name: String, domain: String): User
suspend fun findByUrl(url: String): User
suspend fun findByIds(ids: List<Long>): List<User>
suspend fun existByNameAndDomain(name: String, domain: String): Boolean
}

View File

@ -0,0 +1,30 @@
package dev.usbharu.hideout.query
import dev.usbharu.hideout.domain.model.hideout.entity.User
import dev.usbharu.hideout.repository.Users
import dev.usbharu.hideout.repository.toUser
import org.jetbrains.exposed.sql.and
import org.jetbrains.exposed.sql.select
import org.jetbrains.exposed.sql.selectAll
import org.koin.core.annotation.Single
@Single
class UserQueryServiceImpl : UserQueryService {
override suspend fun findAll(limit: Int, offset: Long): List<User> =
Users.selectAll().limit(limit, offset).map { it.toUser() }
override suspend fun findById(id: Long): User = Users.select { Users.id eq id }.single().toUser()
override suspend fun findByName(name: String): List<User> = Users.select { Users.name eq name }.map { it.toUser() }
override suspend fun findByNameAndDomain(name: String, domain: String): User =
Users.select { Users.name eq name and (Users.domain eq domain) }.single().toUser()
override suspend fun findByUrl(url: String): User = Users.select { Users.url eq url }.single().toUser()
override suspend fun findByIds(ids: List<Long>): List<User> =
Users.select { Users.id inList ids }.map { it.toUser() }
override suspend fun existByNameAndDomain(name: String, domain: String): Boolean =
Users.select { Users.name eq name and (Users.domain eq domain) }.empty().not()
}

View File

@ -8,13 +8,6 @@ interface IJwtRefreshTokenRepository {
suspend fun save(token: JwtRefreshToken)
suspend fun findById(id: Long): JwtRefreshToken?
suspend fun findByToken(token: String): JwtRefreshToken?
suspend fun findByUserId(userId: Long): JwtRefreshToken?
suspend fun delete(token: JwtRefreshToken)
suspend fun deleteById(id: Long)
suspend fun deleteByToken(token: String)
suspend fun deleteByUserId(userId: Long)
suspend fun deleteAll()
}

View File

@ -1,44 +1,11 @@
package dev.usbharu.hideout.repository
import dev.usbharu.hideout.domain.model.hideout.entity.Post
import java.time.Instant
@Suppress("LongParameterList")
interface IPostRepository {
suspend fun generateId(): Long
suspend fun save(post: Post): Post
suspend fun findOneById(id: Long, userId: Long? = null): Post?
suspend fun findByUrl(url: String): Post?
suspend fun delete(id: Long)
suspend fun findAll(
since: Instant?,
until: Instant?,
minId: Long?,
maxId: Long?,
limit: Int?,
userId: Long?
): List<Post>
suspend fun findByUserNameAndDomain(
username: String,
s: String,
since: Instant?,
until: Instant?,
minId: Long?,
maxId: Long?,
limit: Int?,
userId: Long?
): List<Post>
suspend fun findByUserId(
idOrNull: Long,
since: Instant?,
until: Instant?,
minId: Long?,
maxId: Long?,
limit: Int?,
userId: Long?
): List<Post>
suspend fun findByApId(id: String): Post?
suspend fun findById(id: Long): Post
}

View File

@ -8,35 +8,10 @@ interface IUserRepository {
suspend fun findById(id: Long): User?
suspend fun findByIds(ids: List<Long>): List<User>
suspend fun findByName(name: String): List<User>
suspend fun findByNameAndDomain(name: String, domain: String): User?
suspend fun findByDomain(domain: String): List<User>
suspend fun findByNameAndDomains(names: List<Pair<String, String>>): List<User>
suspend fun findByUrl(url: String): User?
suspend fun findByUrls(urls: List<String>): List<User>
@Deprecated("", ReplaceWith("save(userEntity)"))
suspend fun update(userEntity: User) = save(userEntity)
suspend fun delete(id: Long)
suspend fun findAll(): List<User>
suspend fun findAllByLimitAndByOffset(limit: Int, offset: Long = 0): List<User>
suspend fun createFollower(id: Long, follower: Long)
suspend fun deleteFollower(id: Long, follower: Long)
suspend fun findFollowersById(id: Long): List<User>
suspend fun addFollowRequest(id: Long, follower: Long)
suspend fun deleteFollowRequest(id: Long, follower: Long)
suspend fun findFollowRequestsById(id: Long, follower: Long): Boolean
suspend fun nextId(): Long

View File

@ -2,10 +2,8 @@ package dev.usbharu.hideout.repository
import dev.usbharu.hideout.domain.model.hideout.entity.JwtRefreshToken
import dev.usbharu.hideout.service.core.IdGenerateService
import kotlinx.coroutines.Dispatchers
import org.jetbrains.exposed.sql.*
import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq
import org.jetbrains.exposed.sql.transactions.experimental.newSuspendedTransaction
import org.jetbrains.exposed.sql.transactions.transaction
import org.koin.core.annotation.Single
import java.time.Instant
@ -24,79 +22,32 @@ class JwtRefreshTokenRepositoryImpl(
}
}
@Suppress("InjectDispatcher")
suspend fun <T> query(block: suspend () -> T): T =
newSuspendedTransaction(Dispatchers.IO) { block() }
override suspend fun generateId(): Long = idGenerateService.generateId()
override suspend fun save(token: JwtRefreshToken) {
query {
if (JwtRefreshTokens.select { JwtRefreshTokens.id.eq(token.id) }.empty()) {
JwtRefreshTokens.insert {
it[id] = token.id
it[userId] = token.userId
it[refreshToken] = token.refreshToken
it[createdAt] = token.createdAt.toEpochMilli()
it[expiresAt] = token.expiresAt.toEpochMilli()
}
} else {
JwtRefreshTokens.update({ JwtRefreshTokens.id eq token.id }) {
it[userId] = token.userId
it[refreshToken] = token.refreshToken
it[createdAt] = token.createdAt.toEpochMilli()
it[expiresAt] = token.expiresAt.toEpochMilli()
}
if (JwtRefreshTokens.select { JwtRefreshTokens.id.eq(token.id) }.empty()) {
JwtRefreshTokens.insert {
it[id] = token.id
it[userId] = token.userId
it[refreshToken] = token.refreshToken
it[createdAt] = token.createdAt.toEpochMilli()
it[expiresAt] = token.expiresAt.toEpochMilli()
}
} else {
JwtRefreshTokens.update({ JwtRefreshTokens.id eq token.id }) {
it[userId] = token.userId
it[refreshToken] = token.refreshToken
it[createdAt] = token.createdAt.toEpochMilli()
it[expiresAt] = token.expiresAt.toEpochMilli()
}
}
}
override suspend fun findById(id: Long): JwtRefreshToken? {
return query {
JwtRefreshTokens.select { JwtRefreshTokens.id.eq(id) }.singleOrNull()?.toJwtRefreshToken()
}
}
override suspend fun findByToken(token: String): JwtRefreshToken? {
return query {
JwtRefreshTokens.select { JwtRefreshTokens.refreshToken.eq(token) }.singleOrNull()?.toJwtRefreshToken()
}
}
override suspend fun findByUserId(userId: Long): JwtRefreshToken? {
return query {
JwtRefreshTokens.select { JwtRefreshTokens.userId.eq(userId) }.singleOrNull()?.toJwtRefreshToken()
}
}
override suspend fun findById(id: Long): JwtRefreshToken? =
JwtRefreshTokens.select { JwtRefreshTokens.id.eq(id) }.singleOrNull()?.toJwtRefreshToken()
override suspend fun delete(token: JwtRefreshToken) {
return query {
JwtRefreshTokens.deleteWhere { id eq token.id }
}
}
override suspend fun deleteById(id: Long) {
return query {
JwtRefreshTokens.deleteWhere { JwtRefreshTokens.id eq id }
}
}
override suspend fun deleteByToken(token: String) {
return query {
JwtRefreshTokens.deleteWhere { refreshToken eq token }
}
}
override suspend fun deleteByUserId(userId: Long) {
return query {
JwtRefreshTokens.deleteWhere { JwtRefreshTokens.userId eq userId }
}
}
override suspend fun deleteAll() {
return query {
JwtRefreshTokens.deleteAll()
}
JwtRefreshTokens.deleteWhere { id eq token.id }
}
}

View File

@ -1,9 +1,7 @@
package dev.usbharu.hideout.repository
import dev.usbharu.hideout.domain.model.hideout.entity.Jwt
import kotlinx.coroutines.Dispatchers
import org.jetbrains.exposed.sql.*
import org.jetbrains.exposed.sql.transactions.experimental.newSuspendedTransaction
import org.jetbrains.exposed.sql.transactions.transaction
import org.koin.core.annotation.Single
import java.util.*
@ -18,39 +16,31 @@ class MetaRepositoryImpl(private val database: Database) : IMetaRepository {
}
}
@Suppress("InjectDispatcher")
suspend fun <T> query(block: suspend () -> T): T =
newSuspendedTransaction(Dispatchers.IO) { block() }
override suspend fun save(meta: dev.usbharu.hideout.domain.model.hideout.entity.Meta) {
return query {
if (Meta.select { Meta.id eq 1 }.empty()) {
Meta.insert {
it[id] = 1
it[this.version] = meta.version
it[kid] = UUID.randomUUID().toString()
it[this.jwtPrivateKey] = meta.jwt.privateKey
it[this.jwtPublicKey] = meta.jwt.publicKey
}
} else {
Meta.update({ Meta.id eq 1 }) {
it[this.version] = meta.version
it[kid] = UUID.randomUUID().toString()
it[this.jwtPrivateKey] = meta.jwt.privateKey
it[this.jwtPublicKey] = meta.jwt.publicKey
}
if (Meta.select { Meta.id eq 1 }.empty()) {
Meta.insert {
it[id] = 1
it[this.version] = meta.version
it[kid] = UUID.randomUUID().toString()
it[this.jwtPrivateKey] = meta.jwt.privateKey
it[this.jwtPublicKey] = meta.jwt.publicKey
}
} else {
Meta.update({ Meta.id eq 1 }) {
it[this.version] = meta.version
it[kid] = UUID.randomUUID().toString()
it[this.jwtPrivateKey] = meta.jwt.privateKey
it[this.jwtPublicKey] = meta.jwt.publicKey
}
}
}
override suspend fun get(): dev.usbharu.hideout.domain.model.hideout.entity.Meta? {
return query {
Meta.select { Meta.id eq 1 }.singleOrNull()?.let {
dev.usbharu.hideout.domain.model.hideout.entity.Meta(
it[Meta.version],
Jwt(UUID.fromString(it[Meta.kid]), it[Meta.jwtPrivateKey], it[Meta.jwtPublicKey])
)
}
return Meta.select { Meta.id eq 1 }.singleOrNull()?.let {
dev.usbharu.hideout.domain.model.hideout.entity.Meta(
it[Meta.version],
Jwt(UUID.fromString(it[Meta.kid]), it[Meta.jwtPrivateKey], it[Meta.jwtPublicKey])
)
}
}
}

View File

@ -3,13 +3,10 @@ package dev.usbharu.hideout.repository
import dev.usbharu.hideout.domain.model.hideout.entity.Post
import dev.usbharu.hideout.domain.model.hideout.entity.Visibility
import dev.usbharu.hideout.service.core.IdGenerateService
import kotlinx.coroutines.Dispatchers
import org.jetbrains.exposed.sql.*
import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq
import org.jetbrains.exposed.sql.transactions.experimental.newSuspendedTransaction
import org.jetbrains.exposed.sql.transactions.transaction
import org.koin.core.annotation.Single
import java.time.Instant
@Single
class PostRepositoryImpl(database: Database, private val idGenerateService: IdGenerateService) : IPostRepository {
@ -23,105 +20,43 @@ class PostRepositoryImpl(database: Database, private val idGenerateService: IdGe
override suspend fun generateId(): Long = idGenerateService.generateId()
@Suppress("InjectDispatcher")
suspend fun <T> query(block: suspend () -> T): T =
newSuspendedTransaction(Dispatchers.IO) { block() }
override suspend fun save(post: Post): Post {
return query {
val singleOrNull = Posts.select { Posts.id eq post.id }.singleOrNull()
if (singleOrNull == null) {
Posts.insert {
it[id] = post.id
it[userId] = post.userId
it[overview] = post.overview
it[text] = post.text
it[createdAt] = post.createdAt
it[visibility] = post.visibility.ordinal
it[url] = post.url
it[repostId] = post.repostId
it[replyId] = post.replyId
it[sensitive] = post.sensitive
it[apId] = post.apId
}
} else {
Posts.update({ Posts.id eq post.id }) {
it[userId] = post.userId
it[overview] = post.overview
it[text] = post.text
it[createdAt] = post.createdAt
it[visibility] = post.visibility.ordinal
it[url] = post.url
it[repostId] = post.repostId
it[replyId] = post.replyId
it[sensitive] = post.sensitive
it[apId] = post.apId
}
val singleOrNull = Posts.select { Posts.id eq post.id }.singleOrNull()
if (singleOrNull == null) {
Posts.insert {
it[id] = post.id
it[userId] = post.userId
it[overview] = post.overview
it[text] = post.text
it[createdAt] = post.createdAt
it[visibility] = post.visibility.ordinal
it[url] = post.url
it[repostId] = post.repostId
it[replyId] = post.replyId
it[sensitive] = post.sensitive
it[apId] = post.apId
}
} else {
Posts.update({ Posts.id eq post.id }) {
it[userId] = post.userId
it[overview] = post.overview
it[text] = post.text
it[createdAt] = post.createdAt
it[visibility] = post.visibility.ordinal
it[url] = post.url
it[repostId] = post.repostId
it[replyId] = post.replyId
it[sensitive] = post.sensitive
it[apId] = post.apId
}
return@query post
}
return post
}
override suspend fun findOneById(id: Long, userId: Long?): Post? {
return query {
Posts.select { Posts.id eq id }.singleOrNull()?.toPost()
}
}
override suspend fun findByUrl(url: String): Post? {
return query {
Posts.select { Posts.url eq url }.singleOrNull()?.toPost()
}
}
override suspend fun findById(id: Long): Post = Posts.select { Posts.id eq id }.single().toPost()
override suspend fun delete(id: Long) {
return query {
Posts.deleteWhere { Posts.id eq id }
}
}
override suspend fun findAll(
since: Instant?,
until: Instant?,
minId: Long?,
maxId: Long?,
limit: Int?,
userId: Long?
): List<Post> {
return query {
Posts.select { Posts.visibility eq Visibility.PUBLIC.ordinal }.map { it.toPost() }
}
}
override suspend fun findByUserNameAndDomain(
username: String,
s: String,
since: Instant?,
until: Instant?,
minId: Long?,
maxId: Long?,
limit: Int?,
userId: Long?
): List<Post> {
TODO("Not yet implemented")
}
override suspend fun findByUserId(
idOrNull: Long,
since: Instant?,
until: Instant?,
minId: Long?,
maxId: Long?,
limit: Int?,
userId: Long?
): List<Post> {
TODO("Not yet implemented")
}
override suspend fun findByApId(id: String): Post? {
return query {
Posts.select { Posts.apId eq id }.singleOrNull()?.toPost()
}
Posts.deleteWhere { Posts.id eq id }
}
}

View File

@ -5,11 +5,5 @@ import dev.usbharu.hideout.domain.model.hideout.entity.Reaction
interface ReactionRepository {
suspend fun generateId(): Long
suspend fun save(reaction: Reaction): Reaction
suspend fun reactionAlreadyExist(postId: Long, userId: Long, emojiId: Long): Boolean
suspend fun findByPostId(postId: Long): List<Reaction>
suspend fun delete(reaction: Reaction): Reaction
suspend fun deleteById(id: Long)
suspend fun deleteByPostId(postId: Long)
suspend fun deleteByUserId(userId: Long)
suspend fun deleteByPostIdAndUserId(postId: Long, userId: Long)
}

View File

@ -2,11 +2,9 @@ package dev.usbharu.hideout.repository
import dev.usbharu.hideout.domain.model.hideout.entity.Reaction
import dev.usbharu.hideout.service.core.IdGenerateService
import kotlinx.coroutines.Dispatchers
import org.jetbrains.exposed.dao.id.LongIdTable
import org.jetbrains.exposed.sql.*
import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq
import org.jetbrains.exposed.sql.transactions.experimental.newSuspendedTransaction
import org.jetbrains.exposed.sql.transactions.transaction
import org.koin.core.annotation.Single
@ -23,85 +21,35 @@ class ReactionRepositoryImpl(
}
}
@Suppress("InjectDispatcher")
suspend fun <T> query(block: suspend () -> T): T =
newSuspendedTransaction(Dispatchers.IO) { block() }
override suspend fun generateId(): Long = idGenerateService.generateId()
override suspend fun save(reaction: Reaction): Reaction {
query {
if (Reactions.select { Reactions.id eq reaction.id }.empty()) {
Reactions.insert {
it[id] = reaction.id
it[emojiId] = reaction.emojiId
it[postId] = reaction.postId
it[userId] = reaction.userId
}
} else {
Reactions.update({ Reactions.id eq reaction.id }) {
it[emojiId] = reaction.emojiId
it[postId] = reaction.postId
it[userId] = reaction.userId
}
if (Reactions.select { Reactions.id eq reaction.id }.empty()) {
Reactions.insert {
it[id] = reaction.id
it[emojiId] = reaction.emojiId
it[postId] = reaction.postId
it[userId] = reaction.userId
}
} else {
Reactions.update({ Reactions.id eq reaction.id }) {
it[emojiId] = reaction.emojiId
it[postId] = reaction.postId
it[userId] = reaction.userId
}
}
return reaction
}
override suspend fun reactionAlreadyExist(postId: Long, userId: Long, emojiId: Long): Boolean {
return query {
Reactions.select {
Reactions.postId.eq(postId).and(Reactions.userId.eq(userId)).and(
Reactions.emojiId.eq(emojiId)
)
}.empty().not()
}
}
override suspend fun findByPostId(postId: Long): List<Reaction> {
return query {
Reactions.select {
Reactions.postId.eq(postId)
}.map { it.toReaction() }
}
}
override suspend fun delete(reaction: Reaction): Reaction {
query {
Reactions.deleteWhere {
id.eq(reaction.id)
.and(postId.eq(reaction.postId))
.and(userId.eq(reaction.postId))
.and(emojiId.eq(reaction.emojiId))
}
Reactions.deleteWhere {
id.eq(reaction.id)
.and(postId.eq(reaction.postId))
.and(userId.eq(reaction.postId))
.and(emojiId.eq(reaction.emojiId))
}
return reaction
}
override suspend fun deleteById(id: Long) {
query {
Reactions.deleteWhere { Reactions.id.eq(id) }
}
}
override suspend fun deleteByPostId(postId: Long) {
query {
Reactions.deleteWhere { Reactions.postId.eq(postId) }
}
}
override suspend fun deleteByUserId(userId: Long) {
query {
Reactions.deleteWhere { Reactions.userId.eq(userId) }
}
}
override suspend fun deleteByPostIdAndUserId(postId: Long, userId: Long) {
query {
Reactions.deleteWhere { Reactions.postId.eq(postId).and(Reactions.userId.eq(userId)) }
}
}
}
fun ResultRow.toReaction(): Reaction {

View File

@ -2,11 +2,9 @@ package dev.usbharu.hideout.repository
import dev.usbharu.hideout.domain.model.hideout.entity.User
import dev.usbharu.hideout.service.core.IdGenerateService
import kotlinx.coroutines.Dispatchers
import org.jetbrains.exposed.dao.id.LongIdTable
import org.jetbrains.exposed.sql.*
import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq
import org.jetbrains.exposed.sql.transactions.experimental.newSuspendedTransaction
import org.jetbrains.exposed.sql.transactions.transaction
import org.koin.core.annotation.Single
import java.time.Instant
@ -25,207 +23,58 @@ class UserRepository(private val database: Database, private val idGenerateServi
}
}
@Suppress("InjectDispatcher")
suspend fun <T> query(block: suspend () -> T): T =
newSuspendedTransaction(Dispatchers.IO) { block() }
override suspend fun save(user: User): User {
return query {
val singleOrNull = Users.select { Users.id eq user.id }.singleOrNull()
if (singleOrNull == null) {
Users.insert {
it[id] = user.id
it[name] = user.name
it[domain] = user.domain
it[screenName] = user.screenName
it[description] = user.description
it[password] = user.password
it[inbox] = user.inbox
it[outbox] = user.outbox
it[url] = user.url
it[createdAt] = user.createdAt.toEpochMilli()
it[publicKey] = user.publicKey
it[privateKey] = user.privateKey
}
} else {
Users.update({ Users.id eq user.id }) {
it[name] = user.name
it[domain] = user.domain
it[screenName] = user.screenName
it[description] = user.description
it[password] = user.password
it[inbox] = user.inbox
it[outbox] = user.outbox
it[url] = user.url
it[createdAt] = user.createdAt.toEpochMilli()
it[publicKey] = user.publicKey
it[privateKey] = user.privateKey
}
val singleOrNull = Users.select { Users.id eq user.id }.singleOrNull()
if (singleOrNull == null) {
Users.insert {
it[id] = user.id
it[name] = user.name
it[domain] = user.domain
it[screenName] = user.screenName
it[description] = user.description
it[password] = user.password
it[inbox] = user.inbox
it[outbox] = user.outbox
it[url] = user.url
it[createdAt] = user.createdAt.toEpochMilli()
it[publicKey] = user.publicKey
it[privateKey] = user.privateKey
}
return@query user
}
}
override suspend fun createFollower(id: Long, follower: Long) {
return query {
UsersFollowers.insert {
it[userId] = id
it[followerId] = follower
} else {
Users.update({ Users.id eq user.id }) {
it[name] = user.name
it[domain] = user.domain
it[screenName] = user.screenName
it[description] = user.description
it[password] = user.password
it[inbox] = user.inbox
it[outbox] = user.outbox
it[url] = user.url
it[createdAt] = user.createdAt.toEpochMilli()
it[publicKey] = user.publicKey
it[privateKey] = user.privateKey
}
}
return user
}
override suspend fun findById(id: Long): User? {
return query {
Users.select { Users.id eq id }.map {
it.toUser()
}.singleOrNull()
}
}
override suspend fun findByIds(ids: List<Long>): List<User> {
return query {
Users.select { Users.id inList ids }.map {
it.toUser()
}
}
}
override suspend fun findByName(name: String): List<User> {
return query {
Users.select { Users.name eq name }.map {
it.toUser()
}
}
}
override suspend fun findByNameAndDomain(name: String, domain: String): User? {
return query {
Users.select { Users.name eq name and (Users.domain eq domain) }.singleOrNull()?.toUser()
}
}
override suspend fun findByDomain(domain: String): List<User> {
return query {
Users.select { Users.domain eq domain }.map {
it.toUser()
}
}
}
override suspend fun findByNameAndDomains(names: List<Pair<String, String>>): List<User> {
return query {
val selectAll = Users.selectAll()
names.forEach { (name, domain) ->
selectAll.orWhere { Users.name eq name and (Users.domain eq domain) }
}
selectAll.map { it.toUser() }
}
}
override suspend fun findByUrl(url: String): User? {
return query {
Users.select { Users.url eq url }.singleOrNull()?.toUser()
}
}
override suspend fun findByUrls(urls: List<String>): List<User> {
return query {
Users.select { Users.url inList urls }.map { it.toUser() }
}
}
override suspend fun findFollowersById(id: Long): List<User> {
return query {
val followers = Users.alias("FOLLOWERS")
Users.innerJoin(
otherTable = UsersFollowers,
onColumn = { Users.id },
otherColumn = { userId }
)
.innerJoin(
otherTable = followers,
onColumn = { UsersFollowers.followerId },
otherColumn = { followers[Users.id] }
)
.slice(
followers.get(Users.id),
followers.get(Users.name),
followers.get(Users.domain),
followers.get(Users.screenName),
followers.get(Users.description),
followers.get(Users.password),
followers.get(Users.inbox),
followers.get(Users.outbox),
followers.get(Users.url),
followers.get(Users.publicKey),
followers.get(Users.privateKey),
followers.get(Users.createdAt)
)
.select { Users.id eq id }
.map {
User(
id = it[followers[Users.id]],
name = it[followers[Users.name]],
domain = it[followers[Users.domain]],
screenName = it[followers[Users.screenName]],
description = it[followers[Users.description]],
password = it[followers[Users.password]],
inbox = it[followers[Users.inbox]],
outbox = it[followers[Users.outbox]],
url = it[followers[Users.url]],
publicKey = it[followers[Users.publicKey]],
privateKey = it[followers[Users.privateKey]],
createdAt = Instant.ofEpochMilli(it[followers[Users.createdAt]])
)
}
}
}
override suspend fun addFollowRequest(id: Long, follower: Long) {
query {
FollowRequests.insert {
it[userId] = id
it[followerId] = follower
}
}
return Users.select { Users.id eq id }.map {
it.toUser()
}.singleOrNull()
}
override suspend fun deleteFollowRequest(id: Long, follower: Long) {
query {
FollowRequests.deleteWhere { userId.eq(id) and followerId.eq(follower) }
}
FollowRequests.deleteWhere { userId.eq(id) and followerId.eq(follower) }
}
override suspend fun findFollowRequestsById(id: Long, follower: Long): Boolean {
return query {
FollowRequests.select { (FollowRequests.userId eq id) and (FollowRequests.followerId eq follower) }
.singleOrNull() != null
}
return FollowRequests.select { (FollowRequests.userId eq id) and (FollowRequests.followerId eq follower) }
.singleOrNull() != null
}
override suspend fun delete(id: Long) {
query {
Users.deleteWhere { Users.id.eq(id) }
}
}
override suspend fun deleteFollower(id: Long, follower: Long) {
query {
UsersFollowers.deleteWhere { (userId eq id).and(followerId eq follower) }
}
}
override suspend fun findAll(): List<User> {
return query {
Users.selectAll().map { it.toUser() }
}
}
override suspend fun findAllByLimitAndByOffset(limit: Int, offset: Long): List<User> {
return query {
Users.selectAll().limit(limit, offset).map { it.toUser() }
}
Users.deleteWhere { Users.id.eq(id) }
}
override suspend fun nextId(): Long = idGenerateService.generateId()

View File

@ -1,7 +1,6 @@
package dev.usbharu.hideout.routing
import dev.usbharu.hideout.domain.model.hideout.dto.UserCreateDto
import dev.usbharu.hideout.service.user.IUserService
import dev.usbharu.hideout.service.api.IUserApiService
import io.ktor.http.*
import io.ktor.server.application.*
import io.ktor.server.auth.*
@ -9,7 +8,7 @@ import io.ktor.server.request.*
import io.ktor.server.response.*
import io.ktor.server.routing.*
fun Application.register(userService: IUserService) {
fun Application.register(userApiService: IUserApiService) {
routing {
get("/register") {
val principal = call.principal<UserIdPrincipal>()
@ -37,10 +36,7 @@ fun Application.register(userService: IUserService) {
val parameters = call.receiveParameters()
val password = parameters["password"] ?: return@post call.respondRedirect("/register")
val username = parameters["username"] ?: return@post call.respondRedirect("/register")
if (userService.usernameAlreadyUse(username)) {
return@post call.respondRedirect("/register")
}
userService.createLocalUser(UserCreateDto(username, username, "", password))
userApiService.createUser(username, password)
call.respondRedirect("/users/$username")
}
}

View File

@ -1,9 +1,12 @@
package dev.usbharu.hideout.routing.activitypub
import dev.usbharu.hideout.config.Config
import dev.usbharu.hideout.exception.ParameterNotExistException
import dev.usbharu.hideout.plugins.respondAp
import dev.usbharu.hideout.query.FollowerQueryService
import dev.usbharu.hideout.query.UserQueryService
import dev.usbharu.hideout.service.activitypub.ActivityPubUserService
import dev.usbharu.hideout.service.user.IUserService
import dev.usbharu.hideout.service.core.Transaction
import dev.usbharu.hideout.util.HttpUtil.Activity
import dev.usbharu.hideout.util.HttpUtil.JsonLd
import io.ktor.http.*
@ -12,7 +15,12 @@ import io.ktor.server.request.*
import io.ktor.server.response.*
import io.ktor.server.routing.*
fun Routing.usersAP(activityPubUserService: ActivityPubUserService, userService: IUserService) {
fun Routing.usersAP(
activityPubUserService: ActivityPubUserService,
userQueryService: UserQueryService,
followerQueryService: FollowerQueryService,
transaction: Transaction
) {
route("/users/{name}") {
createChild(ContentTypeRouteSelector(ContentType.Application.Activity, ContentType.Application.JsonLd)).handle {
call.application.log.debug("Signature: ${call.request.header("Signature")}")
@ -26,10 +34,15 @@ fun Routing.usersAP(activityPubUserService: ActivityPubUserService, userService:
)
}
get {
val userEntity = userService.findByNameLocalUser(
call.parameters["name"] ?: throw ParameterNotExistException("Parameter(name='name') does not exist.")
)
call.respondText(userEntity.toString() + "\n" + userService.findFollowersById(userEntity.id))
// TODO: 暫定処置なので治す
transaction.transaction {
val userEntity = userQueryService.findByNameAndDomain(
call.parameters["name"]
?: throw ParameterNotExistException("Parameter(name='name') does not exist."),
Config.configData.domain
)
call.respondText(userEntity.toString() + "\n" + followerQueryService.findFollowersById(userEntity.id))
}
}
}
}
@ -39,7 +52,7 @@ class ContentTypeRouteSelector(private vararg val contentType: ContentType) : Ro
context.call.application.log.debug("Accept: ${context.call.request.accept()}")
val requestContentType = context.call.request.accept() ?: return RouteSelectorEvaluation.FailedParameter
return if (requestContentType.split(",")
.any { contentType.any { contentType -> contentType.match(it) } }
.any { contentType.any { contentType -> contentType.match(it) } }
) {
RouteSelectorEvaluation.Constant
} else {

View File

@ -1,14 +1,9 @@
package dev.usbharu.hideout.routing.api.internal.v1
import dev.usbharu.hideout.config.Config
import dev.usbharu.hideout.domain.model.hideout.form.RefreshToken
import dev.usbharu.hideout.domain.model.hideout.form.UserLogin
import dev.usbharu.hideout.exception.UserNotFoundException
import dev.usbharu.hideout.plugins.TOKEN_AUTH
import dev.usbharu.hideout.repository.IUserRepository
import dev.usbharu.hideout.service.auth.IJwtService
import dev.usbharu.hideout.service.user.IUserAuthService
import io.ktor.http.*
import dev.usbharu.hideout.service.api.UserAuthApiService
import io.ktor.server.application.*
import io.ktor.server.auth.*
import io.ktor.server.auth.jwt.*
@ -16,27 +11,15 @@ import io.ktor.server.request.*
import io.ktor.server.response.*
import io.ktor.server.routing.*
fun Route.auth(
userAuthService: IUserAuthService,
userRepository: IUserRepository,
jwtService: IJwtService
) {
fun Route.auth(userAuthApiService: UserAuthApiService) {
post("/login") {
val loginUser = call.receive<UserLogin>()
val check = userAuthService.verifyAccount(loginUser.username, loginUser.password)
if (check.not()) {
return@post call.respond(HttpStatusCode.Unauthorized)
}
val user = userRepository.findByNameAndDomain(loginUser.username, Config.configData.domain)
?: throw UserNotFoundException("${loginUser.username} was not found.")
return@post call.respond(jwtService.createToken(user))
return@post call.respond(userAuthApiService.login(loginUser.username, loginUser.password))
}
post("/refresh-token") {
val refreshToken = call.receive<RefreshToken>()
return@post call.respond(jwtService.refreshToken(refreshToken))
return@post call.respond(userAuthApiService.refreshToken(refreshToken))
}
authenticate(TOKEN_AUTH) {
get("/auth-check") {

View File

@ -5,7 +5,6 @@ import dev.usbharu.hideout.domain.model.hideout.form.Reaction
import dev.usbharu.hideout.exception.ParameterNotExistException
import dev.usbharu.hideout.plugins.TOKEN_AUTH
import dev.usbharu.hideout.service.api.IPostApiService
import dev.usbharu.hideout.service.reaction.IReactionService
import dev.usbharu.hideout.util.InstantParseUtil
import io.ktor.http.*
import io.ktor.server.application.*
@ -16,7 +15,7 @@ import io.ktor.server.response.*
import io.ktor.server.routing.*
@Suppress("LongMethod")
fun Route.posts(postApiService: IPostApiService, reactionService: IReactionService) {
fun Route.posts(postApiService: IPostApiService) {
route("/posts") {
authenticate(TOKEN_AUTH) {
post {
@ -36,7 +35,7 @@ fun Route.posts(postApiService: IPostApiService, reactionService: IReactionServi
call.parameters["id"]?.toLong()
?: throw ParameterNotExistException("Parameter(id='postsId') does not exist.")
)
call.respond(reactionService.findByPostIdForUser(postId, userId))
call.respond(postApiService.getReactionByPostId(postId, userId))
}
post {
val jwtPrincipal = call.principal<JWTPrincipal>() ?: throw IllegalStateException("no principal")
@ -45,11 +44,11 @@ fun Route.posts(postApiService: IPostApiService, reactionService: IReactionServi
?: throw ParameterNotExistException("Parameter(id='postsId') does not exist.")
val reaction = try {
call.receive<Reaction>()
} catch (e: ContentTransformationException) {
} catch (_: ContentTransformationException) {
Reaction(null)
}
reactionService.sendReaction(reaction.reaction ?: "", userId, postId)
postApiService.appendReaction(reaction.reaction ?: "", userId, postId)
call.respond(HttpStatusCode.NoContent)
}
delete {
@ -57,7 +56,7 @@ fun Route.posts(postApiService: IPostApiService, reactionService: IReactionServi
val userId = jwtPrincipal.payload.getClaim("uid").asLong()
val postId = call.parameters["id"]?.toLong()
?: throw ParameterNotExistException("Parameter(id='postsId') does not exist.")
reactionService.removeReaction(userId, postId)
postApiService.removeReaction(userId, postId)
call.respond(HttpStatusCode.NoContent)
}
}

View File

@ -16,7 +16,7 @@ import io.ktor.server.request.*
import io.ktor.server.response.*
import io.ktor.server.routing.*
@Suppress("LongMethod")
@Suppress("LongMethod", "CognitiveComplexMethod")
fun Route.users(userService: IUserService, userApiService: IUserApiService) {
route("/users") {
get {

View File

@ -4,14 +4,14 @@ import dev.usbharu.hideout.config.Config
import dev.usbharu.hideout.domain.model.wellknown.WebFinger
import dev.usbharu.hideout.exception.IllegalParameterException
import dev.usbharu.hideout.exception.ParameterNotExistException
import dev.usbharu.hideout.service.user.IUserService
import dev.usbharu.hideout.service.api.WebFingerApiService
import dev.usbharu.hideout.util.HttpUtil.Activity
import io.ktor.http.*
import io.ktor.server.application.*
import io.ktor.server.response.*
import io.ktor.server.routing.*
fun Routing.webfinger(userService: IUserService) {
fun Routing.webfinger(webFingerApiService: WebFingerApiService) {
route("/.well-known/webfinger") {
get {
val acct = call.request.queryParameters["resource"]?.decodeURLPart()
@ -25,7 +25,7 @@ fun Routing.webfinger(userService: IUserService) {
.substringAfter("acct:")
.trimStart('@')
val userEntity = userService.findByNameLocalUser(accountName)
val userEntity = webFingerApiService.findByNameAndDomain(accountName, Config.configData.domain)
val webFinger = WebFinger(
subject = acct,

View File

@ -5,12 +5,16 @@ import dev.usbharu.hideout.domain.model.ActivityPubStringResponse
import dev.usbharu.hideout.domain.model.ap.Accept
import dev.usbharu.hideout.domain.model.ap.Follow
import dev.usbharu.hideout.exception.ap.IllegalActivityPubObjectException
import dev.usbharu.hideout.query.UserQueryService
import dev.usbharu.hideout.service.user.IUserService
import io.ktor.http.*
import org.koin.core.annotation.Single
@Single
class ActivityPubAcceptServiceImpl(private val userService: IUserService) : ActivityPubAcceptService {
class ActivityPubAcceptServiceImpl(
private val userService: IUserService,
private val userQueryService: UserQueryService
) : ActivityPubAcceptService {
override suspend fun receiveAccept(accept: Accept): ActivityPubResponse {
val value = accept.`object` ?: throw IllegalActivityPubObjectException("object is null")
if (value.type.contains("Follow").not()) {
@ -20,8 +24,8 @@ class ActivityPubAcceptServiceImpl(private val userService: IUserService) : Acti
val follow = value as Follow
val userUrl = follow.`object` ?: throw IllegalActivityPubObjectException("object is null")
val followerUrl = follow.actor ?: throw IllegalActivityPubObjectException("actor is null")
val user = userService.findByUrl(userUrl)
val follower = userService.findByUrl(followerUrl)
val user = userQueryService.findByUrl(userUrl)
val follower = userQueryService.findByUrl(followerUrl)
userService.follow(user.id, follower.id)
return ActivityPubStringResponse(HttpStatusCode.OK, "accepted")
}

View File

@ -5,12 +5,14 @@ import dev.usbharu.hideout.domain.model.ActivityPubStringResponse
import dev.usbharu.hideout.domain.model.ap.Create
import dev.usbharu.hideout.domain.model.ap.Note
import dev.usbharu.hideout.exception.ap.IllegalActivityPubObjectException
import dev.usbharu.hideout.service.core.Transaction
import io.ktor.http.*
import org.koin.core.annotation.Single
@Single
class ActivityPubCreateServiceImpl(
private val activityPubNoteService: ActivityPubNoteService
private val activityPubNoteService: ActivityPubNoteService,
private val transaction: Transaction
) : ActivityPubCreateService {
override suspend fun receiveCreate(create: Create): ActivityPubResponse {
val value = create.`object` ?: throw IllegalActivityPubObjectException("object is null")
@ -18,8 +20,10 @@ class ActivityPubCreateServiceImpl(
throw IllegalActivityPubObjectException("object is not Note")
}
val note = value as Note
activityPubNoteService.fetchNote(note)
return ActivityPubStringResponse(HttpStatusCode.OK, "Created")
return transaction.transaction {
val note = value as Note
activityPubNoteService.fetchNote(note)
ActivityPubStringResponse(HttpStatusCode.OK, "Created")
}
}
}

View File

@ -3,11 +3,11 @@ package dev.usbharu.hideout.service.activitypub
import dev.usbharu.hideout.domain.model.ActivityPubResponse
import dev.usbharu.hideout.domain.model.ActivityPubStringResponse
import dev.usbharu.hideout.domain.model.ap.Like
import dev.usbharu.hideout.exception.PostNotFoundException
import dev.usbharu.hideout.exception.ap.IllegalActivityPubObjectException
import dev.usbharu.hideout.repository.IPostRepository
import dev.usbharu.hideout.query.PostQueryService
import dev.usbharu.hideout.query.UserQueryService
import dev.usbharu.hideout.service.core.Transaction
import dev.usbharu.hideout.service.reaction.IReactionService
import dev.usbharu.hideout.service.user.IUserService
import io.ktor.http.*
import org.koin.core.annotation.Single
@ -15,26 +15,28 @@ import org.koin.core.annotation.Single
class ActivityPubLikeServiceImpl(
private val reactionService: IReactionService,
private val activityPubUserService: ActivityPubUserService,
private val userService: IUserService,
private val postService: IPostRepository,
private val activityPubNoteService: ActivityPubNoteService
private val activityPubNoteService: ActivityPubNoteService,
private val userQueryService: UserQueryService,
private val postQueryService: PostQueryService,
private val transaction: Transaction
) : ActivityPubLikeService {
override suspend fun receiveLike(like: Like): ActivityPubResponse {
val actor = like.actor ?: throw IllegalActivityPubObjectException("actor is null")
val content = like.content ?: throw IllegalActivityPubObjectException("content is null")
like.`object` ?: throw IllegalActivityPubObjectException("object is null")
val person = activityPubUserService.fetchPerson(actor)
activityPubNoteService.fetchNote(like.`object`!!)
transaction.transaction {
val person = activityPubUserService.fetchPerson(actor)
activityPubNoteService.fetchNote(like.`object`!!)
val user = userService.findByUrl(
person.url
?: throw IllegalActivityPubObjectException("actor is not found")
)
val user = userQueryService.findByUrl(
person.url
?: throw IllegalActivityPubObjectException("actor is not found")
)
val post = postService.findByUrl(like.`object`!!)
?: throw PostNotFoundException("${like.`object`} was not found")
val post = postQueryService.findByUrl(like.`object`!!)
reactionService.receiveReaction(content, actor.substringAfter("://").substringBefore("/"), user.id, post.id)
reactionService.receiveReaction(content, actor.substringAfter("://").substringBefore("/"), user.id, post.id)
}
return ActivityPubStringResponse(HttpStatusCode.OK, "")
}
}

View File

@ -10,9 +10,11 @@ import dev.usbharu.hideout.domain.model.job.DeliverPostJob
import dev.usbharu.hideout.exception.ap.IllegalActivityPubObjectException
import dev.usbharu.hideout.plugins.getAp
import dev.usbharu.hideout.plugins.postAp
import dev.usbharu.hideout.query.FollowerQueryService
import dev.usbharu.hideout.query.PostQueryService
import dev.usbharu.hideout.query.UserQueryService
import dev.usbharu.hideout.repository.IPostRepository
import dev.usbharu.hideout.service.job.JobQueueParentService
import dev.usbharu.hideout.service.user.IUserService
import io.ktor.client.*
import io.ktor.client.statement.*
import kjob.core.job.JobProps
@ -24,16 +26,18 @@ import java.time.Instant
class ActivityPubNoteServiceImpl(
private val httpClient: HttpClient,
private val jobQueueParentService: JobQueueParentService,
private val userService: IUserService,
private val postRepository: IPostRepository,
private val activityPubUserService: ActivityPubUserService
private val activityPubUserService: ActivityPubUserService,
private val userQueryService: UserQueryService,
private val followerQueryService: FollowerQueryService,
private val postQueryService: PostQueryService
) : ActivityPubNoteService {
private val logger = LoggerFactory.getLogger(this::class.java)
override suspend fun createNote(post: Post) {
val followers = userService.findFollowersById(post.userId)
val userEntity = userService.findById(post.userId)
val followers = followerQueryService.findFollowersById(post.userId)
val userEntity = userQueryService.findById(post.userId)
val note = Config.configData.objectMapper.writeValueAsString(post)
followers.forEach { followerEntity ->
jobQueueParentService.schedule(DeliverPostJob) {
@ -70,7 +74,7 @@ class ActivityPubNoteServiceImpl(
}
override suspend fun fetchNote(url: String, targetActor: String?): Note {
val post = postRepository.findByUrl(url)
val post = postQueryService.findByUrl(url)
if (post != null) {
return postToNote(post)
}
@ -83,8 +87,8 @@ class ActivityPubNoteServiceImpl(
}
private suspend fun postToNote(post: Post): Note {
val user = userService.findById(post.userId)
val reply = post.replyId?.let { postRepository.findOneById(it) }
val user = userQueryService.findById(post.userId)
val reply = post.replyId?.let { postQueryService.findById(it) }
return Note(
name = "Post",
id = post.apId,
@ -98,21 +102,28 @@ class ActivityPubNoteServiceImpl(
)
}
private suspend fun ActivityPubNoteServiceImpl.note(
private suspend fun note(
note: Note,
targetActor: String?,
url: String
): Note {
val findByApId = postRepository.findByApId(url)
if (findByApId != null) {
return postToNote(findByApId)
val findByApId = try {
postQueryService.findByApId(url)
} catch (_: NoSuchElementException) {
return internalNote(note, targetActor, url)
} catch (_: IllegalArgumentException) {
return internalNote(note, targetActor, url)
}
return postToNote(findByApId)
}
private suspend fun internalNote(note: Note, targetActor: String?, url: String): Note {
val person = activityPubUserService.fetchPerson(
note.attributedTo ?: throw IllegalActivityPubObjectException("note.attributedTo is null"),
targetActor
)
val user =
userService.findByUrl(person.url ?: throw IllegalActivityPubObjectException("person.url is null"))
userQueryService.findByUrl(person.url ?: throw IllegalActivityPubObjectException("person.url is null"))
val visibility =
if (note.to.contains(public) && note.cc.contains(public)) {
@ -127,7 +138,7 @@ class ActivityPubNoteServiceImpl(
val reply = note.inReplyTo?.let {
fetchNote(it, targetActor)
postRepository.findByUrl(it)
postQueryService.findByUrl(it)
}
postRepository.save(

View File

@ -7,11 +7,12 @@ import dev.usbharu.hideout.domain.model.ap.Undo
import dev.usbharu.hideout.domain.model.hideout.entity.Reaction
import dev.usbharu.hideout.domain.model.job.DeliverReactionJob
import dev.usbharu.hideout.domain.model.job.DeliverRemoveReactionJob
import dev.usbharu.hideout.exception.PostNotFoundException
import dev.usbharu.hideout.plugins.postAp
import dev.usbharu.hideout.query.FollowerQueryService
import dev.usbharu.hideout.query.PostQueryService
import dev.usbharu.hideout.query.UserQueryService
import dev.usbharu.hideout.repository.IPostRepository
import dev.usbharu.hideout.service.job.JobQueueParentService
import dev.usbharu.hideout.service.user.IUserService
import io.ktor.client.*
import kjob.core.job.JobProps
import org.koin.core.annotation.Single
@ -19,16 +20,18 @@ import java.time.Instant
@Single
class ActivityPubReactionServiceImpl(
private val userService: IUserService,
private val jobQueueParentService: JobQueueParentService,
private val iPostRepository: IPostRepository,
private val httpClient: HttpClient
private val httpClient: HttpClient,
private val userQueryService: UserQueryService,
private val followerQueryService: FollowerQueryService,
private val postQueryService: PostQueryService
) : ActivityPubReactionService {
override suspend fun reaction(like: Reaction) {
val followers = userService.findFollowersById(like.userId)
val user = userService.findById(like.userId)
val followers = followerQueryService.findFollowersById(like.userId)
val user = userQueryService.findById(like.userId)
val post =
iPostRepository.findOneById(like.postId) ?: throw PostNotFoundException("${like.postId} was not found.")
postQueryService.findById(like.postId)
followers.forEach { follower ->
jobQueueParentService.schedule(DeliverReactionJob) {
props[it.actor] = user.url
@ -41,10 +44,10 @@ class ActivityPubReactionServiceImpl(
}
override suspend fun removeReaction(like: Reaction) {
val followers = userService.findFollowersById(like.userId)
val user = userService.findById(like.userId)
val followers = followerQueryService.findFollowersById(like.userId)
val user = userQueryService.findById(like.userId)
val post =
iPostRepository.findOneById(like.postId) ?: throw PostNotFoundException("${like.postId} was not found.")
postQueryService.findById(like.postId)
followers.forEach { follower ->
jobQueueParentService.schedule(DeliverRemoveReactionJob) {
props[it.actor] = user.url

View File

@ -8,6 +8,8 @@ import dev.usbharu.hideout.domain.model.ap.Accept
import dev.usbharu.hideout.domain.model.ap.Follow
import dev.usbharu.hideout.domain.model.job.ReceiveFollowJob
import dev.usbharu.hideout.plugins.postAp
import dev.usbharu.hideout.query.UserQueryService
import dev.usbharu.hideout.service.core.Transaction
import dev.usbharu.hideout.service.job.JobQueueParentService
import dev.usbharu.hideout.service.user.IUserService
import io.ktor.client.*
@ -20,7 +22,9 @@ class ActivityPubReceiveFollowServiceImpl(
private val jobQueueParentService: JobQueueParentService,
private val activityPubUserService: ActivityPubUserService,
private val userService: IUserService,
private val httpClient: HttpClient
private val httpClient: HttpClient,
private val userQueryService: UserQueryService,
private val transaction: Transaction
) : ActivityPubReceiveFollowService {
override suspend fun receiveFollow(follow: Follow): ActivityPubResponse {
// TODO: Verify HTTP Signature
@ -33,22 +37,26 @@ class ActivityPubReceiveFollowServiceImpl(
}
override suspend fun receiveFollowJob(props: JobProps<ReceiveFollowJob>) {
val actor = props[ReceiveFollowJob.actor]
val targetActor = props[ReceiveFollowJob.targetActor]
val person = activityPubUserService.fetchPerson(actor, targetActor)
val follow = Config.configData.objectMapper.readValue<Follow>(props[ReceiveFollowJob.follow])
httpClient.postAp(
urlString = person.inbox ?: throw IllegalArgumentException("inbox is not found"),
username = "$targetActor#pubkey",
jsonLd = Accept(
name = "Follow",
`object` = follow,
actor = targetActor
transaction.transaction {
val actor = props[ReceiveFollowJob.actor]
val targetActor = props[ReceiveFollowJob.targetActor]
val person = activityPubUserService.fetchPerson(actor, targetActor)
val follow = Config.configData.objectMapper.readValue<Follow>(props[ReceiveFollowJob.follow])
httpClient.postAp(
urlString = person.inbox ?: throw IllegalArgumentException("inbox is not found"),
username = "$targetActor#pubkey",
jsonLd = Accept(
name = "Follow",
`object` = follow,
actor = targetActor
)
)
)
val users =
userService.findByUrls(listOf(targetActor, follow.actor ?: throw IllegalArgumentException("actor is null")))
userService.followRequest(users.first { it.url == targetActor }.id, users.first { it.url == follow.actor }.id)
val targetEntity = userQueryService.findByUrl(targetActor)
val followActorEntity =
userQueryService.findByUrl(follow.actor ?: throw java.lang.IllegalArgumentException("Actor is null"))
userService.followRequest(targetEntity.id, followActorEntity.id)
}
}
}

View File

@ -4,6 +4,8 @@ import dev.usbharu.hideout.domain.model.ActivityPubResponse
import dev.usbharu.hideout.domain.model.ActivityPubStringResponse
import dev.usbharu.hideout.domain.model.ap.Follow
import dev.usbharu.hideout.domain.model.ap.Undo
import dev.usbharu.hideout.query.UserQueryService
import dev.usbharu.hideout.service.core.Transaction
import dev.usbharu.hideout.service.user.IUserService
import io.ktor.http.*
import org.koin.core.annotation.Single
@ -12,7 +14,9 @@ import org.koin.core.annotation.Single
@Suppress("UnsafeCallOnNullableType")
class ActivityPubUndoServiceImpl(
private val userService: IUserService,
private val activityPubUserService: ActivityPubUserService
private val activityPubUserService: ActivityPubUserService,
private val userQueryService: UserQueryService,
private val transaction: Transaction
) : ActivityPubUndoService {
override suspend fun receiveUndo(undo: Undo): ActivityPubResponse {
if (undo.actor == null) {
@ -31,11 +35,12 @@ class ActivityPubUndoServiceImpl(
if (follow.`object` == null) {
return ActivityPubStringResponse(HttpStatusCode.BadRequest, "object.object is null")
}
activityPubUserService.fetchPerson(undo.actor!!, follow.`object`)
val follower = userService.findByUrl(undo.actor!!)
val target = userService.findByUrl(follow.`object`!!)
userService.unfollow(target.id, follower.id)
transaction.transaction {
activityPubUserService.fetchPerson(undo.actor!!, follow.`object`)
val follower = userQueryService.findByUrl(undo.actor!!)
val target = userQueryService.findByUrl(follow.`object`!!)
userService.unfollow(target.id, follower.id)
}
}
else -> {}

View File

@ -6,9 +6,10 @@ import dev.usbharu.hideout.domain.model.ap.Image
import dev.usbharu.hideout.domain.model.ap.Key
import dev.usbharu.hideout.domain.model.ap.Person
import dev.usbharu.hideout.domain.model.hideout.dto.RemoteUserCreateDto
import dev.usbharu.hideout.exception.UserNotFoundException
import dev.usbharu.hideout.exception.ap.IllegalActivityPubObjectException
import dev.usbharu.hideout.plugins.getAp
import dev.usbharu.hideout.query.UserQueryService
import dev.usbharu.hideout.service.core.Transaction
import dev.usbharu.hideout.service.user.IUserService
import dev.usbharu.hideout.util.HttpUtil.Activity
import io.ktor.client.*
@ -20,13 +21,17 @@ import org.koin.core.annotation.Single
@Single
class ActivityPubUserServiceImpl(
private val userService: IUserService,
private val httpClient: HttpClient
private val httpClient: HttpClient,
private val userQueryService: UserQueryService,
private val transaction: Transaction
) :
ActivityPubUserService {
override suspend fun getPersonByName(name: String): Person {
val userEntity = transaction.transaction {
userQueryService.findByNameAndDomain(name, Config.configData.domain)
}
// TODO: JOINで書き直し
val userEntity = userService.findByNameLocalUser(name)
val userUrl = "${Config.configData.url}/users/$name"
return Person(
type = emptyList(),
@ -55,7 +60,7 @@ class ActivityPubUserServiceImpl(
override suspend fun fetchPerson(url: String, targetActor: String?): Person {
return try {
val userEntity = userService.findByUrl(url)
val userEntity = userQueryService.findByUrl(url)
return Person(
type = emptyList(),
name = userEntity.name,
@ -79,7 +84,7 @@ class ActivityPubUserServiceImpl(
publicKeyPem = userEntity.publicKey
)
)
} catch (ignore: UserNotFoundException) {
} catch (ignore: NoSuchElementException) {
val httpResponse = if (targetActor != null) {
httpClient.getAp(url, "$targetActor#pubkey")
} else {

View File

@ -1,6 +1,7 @@
package dev.usbharu.hideout.service.api
import dev.usbharu.hideout.domain.model.hideout.dto.PostResponse
import dev.usbharu.hideout.domain.model.hideout.dto.ReactionResponse
import java.time.Instant
@Suppress("LongParameterList")
@ -25,4 +26,8 @@ interface IPostApiService {
limit: Int? = null,
userId: Long? = null
): List<PostResponse>
suspend fun getReactionByPostId(postId: Long, userId: Long? = null): List<ReactionResponse>
suspend fun appendReaction(reaction: String, userId: Long, postId: Long)
suspend fun removeReaction(userId: Long, postId: Long)
}

View File

@ -12,8 +12,6 @@ interface IUserApiService {
suspend fun findByAcct(acct: Acct): UserResponse
suspend fun findByAccts(accts: List<Acct>): List<UserResponse>
suspend fun findFollowers(userId: Long): List<UserResponse>
suspend fun findFollowings(userId: Long): List<UserResponse>
@ -21,4 +19,6 @@ interface IUserApiService {
suspend fun findFollowersByAcct(acct: Acct): List<UserResponse>
suspend fun findFollowingsByAcct(acct: Acct): List<UserResponse>
suspend fun createUser(username: String, password: String): UserResponse
}

View File

@ -3,15 +3,14 @@ package dev.usbharu.hideout.service.api
import dev.usbharu.hideout.config.Config
import dev.usbharu.hideout.domain.model.hideout.dto.PostCreateDto
import dev.usbharu.hideout.domain.model.hideout.dto.PostResponse
import dev.usbharu.hideout.repository.*
import dev.usbharu.hideout.domain.model.hideout.dto.ReactionResponse
import dev.usbharu.hideout.query.PostResponseQueryService
import dev.usbharu.hideout.query.ReactionQueryService
import dev.usbharu.hideout.repository.IUserRepository
import dev.usbharu.hideout.service.core.Transaction
import dev.usbharu.hideout.service.post.IPostService
import dev.usbharu.hideout.service.reaction.IReactionService
import dev.usbharu.hideout.util.AcctUtil
import kotlinx.coroutines.Dispatchers
import org.jetbrains.exposed.sql.and
import org.jetbrains.exposed.sql.innerJoin
import org.jetbrains.exposed.sql.select
import org.jetbrains.exposed.sql.selectAll
import org.jetbrains.exposed.sql.transactions.experimental.newSuspendedTransaction
import org.koin.core.annotation.Single
import java.time.Instant
import dev.usbharu.hideout.domain.model.hideout.form.Post as FormPost
@ -19,36 +18,31 @@ import dev.usbharu.hideout.domain.model.hideout.form.Post as FormPost
@Single
class PostApiServiceImpl(
private val postService: IPostService,
private val postRepository: IPostRepository,
private val userRepository: IUserRepository
private val userRepository: IUserRepository,
private val postResponseQueryService: PostResponseQueryService,
private val reactionQueryService: ReactionQueryService,
private val reactionService: IReactionService,
private val transaction: Transaction
) : IPostApiService {
override suspend fun createPost(postForm: FormPost, userId: Long): PostResponse {
val createdPost = postService.createLocal(
PostCreateDto(
text = postForm.text,
overview = postForm.overview,
visibility = postForm.visibility,
repostId = postForm.repostId,
repolyId = postForm.replyId,
userId = userId
return transaction.transaction {
val createdPost = postService.createLocal(
PostCreateDto(
text = postForm.text,
overview = postForm.overview,
visibility = postForm.visibility,
repostId = postForm.repostId,
repolyId = postForm.replyId,
userId = userId
)
)
)
val creator = userRepository.findById(userId)
return PostResponse.from(createdPost, creator!!)
}
@Suppress("InjectDispatcher")
suspend fun <T> query(block: suspend () -> T): T =
newSuspendedTransaction(Dispatchers.IO) { block() }
override suspend fun getById(id: Long, userId: Long?): PostResponse {
val query = query {
Posts.innerJoin(Users, onColumn = { Posts.userId }, otherColumn = { Users.id }).select { Posts.id eq id }
.single()
val creator = userRepository.findById(userId)
PostResponse.from(createdPost, creator!!)
}
return PostResponse.from(query.toPost(), query.toUser())
}
override suspend fun getById(id: Long, userId: Long?): PostResponse = postResponseQueryService.findById(id, userId)
override suspend fun getAll(
since: Instant?,
until: Instant?,
@ -56,11 +50,15 @@ class PostApiServiceImpl(
maxId: Long?,
limit: Int?,
userId: Long?
): List<PostResponse> {
return query {
Posts.innerJoin(Users, onColumn = { Posts.userId }, otherColumn = { id }).selectAll()
.map { PostResponse.from(it.toPost(), it.toUser()) }
}
): List<PostResponse> = transaction.transaction {
postResponseQueryService.findAll(
since = since?.toEpochMilli(),
until = until?.toEpochMilli(),
minId = minId,
maxId = maxId,
limit = limit,
userId = userId
)
}
override suspend fun getByUser(
@ -75,18 +73,24 @@ class PostApiServiceImpl(
val idOrNull = nameOrId.toLongOrNull()
return if (idOrNull == null) {
val acct = AcctUtil.parse(nameOrId)
query {
Posts.innerJoin(Users, onColumn = { Posts.userId }, otherColumn = { id }).select {
Users.name.eq(acct.username)
.and(Users.domain eq (acct.domain ?: Config.configData.domain))
}.map { PostResponse.from(it.toPost(), it.toUser()) }
}
postResponseQueryService.findByUserNameAndUserDomain(acct.username, acct.domain ?: Config.configData.domain)
} else {
query {
Posts.innerJoin(Users, onColumn = { Posts.userId }, otherColumn = { id }).select {
Posts.userId eq idOrNull
}.map { PostResponse.from(it.toPost(), it.toUser()) }
}
postResponseQueryService.findByUserId(idOrNull)
}
}
override suspend fun getReactionByPostId(postId: Long, userId: Long?): List<ReactionResponse> =
transaction.transaction { reactionQueryService.findByPostIdWithUsers(postId, userId) }
override suspend fun appendReaction(reaction: String, userId: Long, postId: Long) {
transaction.transaction {
reactionService.sendReaction(reaction, userId, postId)
}
}
override suspend fun removeReaction(userId: Long, postId: Long) {
transaction.transaction {
reactionService.removeReaction(userId, postId)
}
}
}

View File

@ -2,37 +2,54 @@ package dev.usbharu.hideout.service.api
import dev.usbharu.hideout.config.Config
import dev.usbharu.hideout.domain.model.Acct
import dev.usbharu.hideout.domain.model.hideout.dto.UserCreateDto
import dev.usbharu.hideout.domain.model.hideout.dto.UserResponse
import dev.usbharu.hideout.exception.UsernameAlreadyExistException
import dev.usbharu.hideout.query.FollowerQueryService
import dev.usbharu.hideout.query.UserQueryService
import dev.usbharu.hideout.service.core.Transaction
import dev.usbharu.hideout.service.user.IUserService
import org.koin.core.annotation.Single
import kotlin.math.min
@Single
class UserApiServiceImpl(private val userService: IUserService) : IUserApiService {
class UserApiServiceImpl(
private val userQueryService: UserQueryService,
private val followerQueryService: FollowerQueryService,
private val userService: IUserService,
private val transaction: Transaction
) : IUserApiService {
override suspend fun findAll(limit: Int?, offset: Long): List<UserResponse> =
userService.findAll(limit, offset).map { UserResponse.from(it) }
userQueryService.findAll(min(limit ?: 100, 100), offset).map { UserResponse.from(it) }
override suspend fun findById(id: Long): UserResponse = UserResponse.from(userService.findById(id))
override suspend fun findById(id: Long): UserResponse = UserResponse.from(userQueryService.findById(id))
override suspend fun findByIds(ids: List<Long>): List<UserResponse> =
userService.findByIds(ids).map { UserResponse.from(it) }
userQueryService.findByIds(ids).map { UserResponse.from(it) }
override suspend fun findByAcct(acct: Acct): UserResponse =
UserResponse.from(userService.findByNameAndDomain(acct.username, acct.domain))
override suspend fun findByAccts(accts: List<Acct>): List<UserResponse> {
return userService.findByNameAndDomains(accts.map { it.username to (it.domain ?: Config.configData.domain) })
.map { UserResponse.from(it) }
}
UserResponse.from(userQueryService.findByNameAndDomain(acct.username, acct.domain ?: Config.configData.domain))
override suspend fun findFollowers(userId: Long): List<UserResponse> =
userService.findFollowersById(userId).map { UserResponse.from(it) }
followerQueryService.findFollowersById(userId).map { UserResponse.from(it) }
override suspend fun findFollowings(userId: Long): List<UserResponse> =
userService.findFollowingById(userId).map { UserResponse.from(it) }
followerQueryService.findFollowingById(userId).map { UserResponse.from(it) }
override suspend fun findFollowersByAcct(acct: Acct): List<UserResponse> =
userService.findFollowersByNameAndDomain(acct.username, acct.domain).map { UserResponse.from(it) }
followerQueryService.findFollowersByNameAndDomain(acct.username, acct.domain ?: Config.configData.domain)
.map { UserResponse.from(it) }
override suspend fun findFollowingsByAcct(acct: Acct): List<UserResponse> =
userService.findFollowingByNameAndDomain(acct.username, acct.domain).map { UserResponse.from(it) }
followerQueryService.findFollowingByNameAndDomain(acct.username, acct.domain ?: Config.configData.domain)
.map { UserResponse.from(it) }
override suspend fun createUser(username: String, password: String): UserResponse {
return transaction.transaction {
if (userQueryService.existByNameAndDomain(username, Config.configData.domain)) {
throw UsernameAlreadyExistException()
}
UserResponse.from(userService.createLocalUser(UserCreateDto(username, username, "", password)))
}
}
}

View File

@ -0,0 +1,9 @@
package dev.usbharu.hideout.service.api
import dev.usbharu.hideout.domain.model.hideout.dto.JwtToken
import dev.usbharu.hideout.domain.model.hideout.form.RefreshToken
interface UserAuthApiService {
suspend fun login(username: String, password: String): JwtToken
suspend fun refreshToken(refreshToken: RefreshToken): JwtToken
}

View File

@ -0,0 +1,35 @@
package dev.usbharu.hideout.service.api
import dev.usbharu.hideout.config.Config
import dev.usbharu.hideout.domain.model.hideout.dto.JwtToken
import dev.usbharu.hideout.domain.model.hideout.form.RefreshToken
import dev.usbharu.hideout.exception.InvalidUsernameOrPasswordException
import dev.usbharu.hideout.query.UserQueryService
import dev.usbharu.hideout.service.auth.IJwtService
import dev.usbharu.hideout.service.core.Transaction
import dev.usbharu.hideout.service.user.UserAuthService
import org.koin.core.annotation.Single
@Single
class UserAuthApiServiceImpl(
private val userAuthService: UserAuthService,
private val userQueryService: UserQueryService,
private val jwtService: IJwtService,
private val transaction: Transaction
) : UserAuthApiService {
override suspend fun login(username: String, password: String): JwtToken {
return transaction.transaction {
if (userAuthService.verifyAccount(username, password).not()) {
throw InvalidUsernameOrPasswordException()
}
val user = userQueryService.findByNameAndDomain(username, Config.configData.domain)
jwtService.createToken(user)
}
}
override suspend fun refreshToken(refreshToken: RefreshToken): JwtToken {
return transaction.transaction {
jwtService.refreshToken(refreshToken)
}
}
}

View File

@ -0,0 +1,7 @@
package dev.usbharu.hideout.service.api
import dev.usbharu.hideout.domain.model.hideout.entity.User
interface WebFingerApiService {
suspend fun findByNameAndDomain(name: String, domain: String): User
}

View File

@ -0,0 +1,16 @@
package dev.usbharu.hideout.service.api
import dev.usbharu.hideout.domain.model.hideout.entity.User
import dev.usbharu.hideout.query.UserQueryService
import dev.usbharu.hideout.service.core.Transaction
import org.koin.core.annotation.Single
@Single
class WebFingerApiServiceImpl(private val transaction: Transaction, private val userQueryService: UserQueryService) :
WebFingerApiService {
override suspend fun findByNameAndDomain(name: String, domain: String): User {
return transaction.transaction {
userQueryService.findByNameAndDomain(name, domain)
}
}
}

View File

@ -1,15 +1,19 @@
package dev.usbharu.hideout.service.auth
import dev.usbharu.hideout.plugins.KtorKeyMap
import dev.usbharu.hideout.repository.IUserRepository
import dev.usbharu.hideout.query.UserQueryService
import dev.usbharu.hideout.service.core.Transaction
import io.ktor.http.*
import org.koin.core.annotation.Single
import tech.barbero.http.message.signing.SignatureHeaderVerifier
@Single
class HttpSignatureVerifyServiceImpl(private val userAuthService: IUserRepository) : HttpSignatureVerifyService {
class HttpSignatureVerifyServiceImpl(
private val userQueryService: UserQueryService,
private val transaction: Transaction
) : HttpSignatureVerifyService {
override fun verify(headers: Headers): Boolean {
val build = SignatureHeaderVerifier.builder().keyMap(KtorKeyMap(userAuthService)).build()
val build = SignatureHeaderVerifier.builder().keyMap(KtorKeyMap(userQueryService, transaction)).build()
return true
// build.verify(object : HttpMessage {
// override fun headerValues(name: String?): MutableList<String> {

View File

@ -8,13 +8,12 @@ import dev.usbharu.hideout.domain.model.hideout.entity.JwtRefreshToken
import dev.usbharu.hideout.domain.model.hideout.entity.User
import dev.usbharu.hideout.domain.model.hideout.form.RefreshToken
import dev.usbharu.hideout.exception.InvalidRefreshTokenException
import dev.usbharu.hideout.query.JwtRefreshTokenQueryService
import dev.usbharu.hideout.query.UserQueryService
import dev.usbharu.hideout.repository.IJwtRefreshTokenRepository
import dev.usbharu.hideout.service.core.IMetaService
import dev.usbharu.hideout.service.user.IUserService
import dev.usbharu.hideout.util.RsaUtil
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.async
import kotlinx.coroutines.runBlocking
import org.koin.core.annotation.Single
import java.time.Instant
import java.time.temporal.ChronoUnit
@ -25,26 +24,19 @@ import java.util.*
class JwtServiceImpl(
private val metaService: IMetaService,
private val refreshTokenRepository: IJwtRefreshTokenRepository,
private val userService: IUserService
private val userQueryService: UserQueryService,
private val refreshTokenQueryService: JwtRefreshTokenQueryService
) : IJwtService {
private val privateKey by lazy {
CoroutineScope(Dispatchers.IO).async {
RsaUtil.decodeRsaPrivateKey(metaService.getJwtMeta().privateKey)
}
private val privateKey = runBlocking {
RsaUtil.decodeRsaPrivateKey(metaService.getJwtMeta().privateKey)
}
private val publicKey by lazy {
CoroutineScope(Dispatchers.IO).async {
RsaUtil.decodeRsaPublicKey(metaService.getJwtMeta().publicKey)
}
private val publicKey = runBlocking {
RsaUtil.decodeRsaPublicKey(metaService.getJwtMeta().publicKey)
}
private val keyId by lazy {
CoroutineScope(Dispatchers.IO).async {
metaService.getJwtMeta().kid
}
}
private val keyId = runBlocking { metaService.getJwtMeta().kid }
@Suppress("MagicNumber")
override suspend fun createToken(user: User): JwtToken {
@ -52,10 +44,10 @@ class JwtServiceImpl(
val token = JWT.create()
.withAudience("${Config.configData.url}/users/${user.name}")
.withIssuer(Config.configData.url)
.withKeyId(keyId.await().toString())
.withKeyId(keyId.toString())
.withClaim("uid", user.id)
.withExpiresAt(now.plus(30, ChronoUnit.MINUTES))
.sign(Algorithm.RSA256(publicKey.await(), privateKey.await()))
.sign(Algorithm.RSA256(publicKey, privateKey))
val jwtRefreshToken = JwtRefreshToken(
id = refreshTokenRepository.generateId(),
@ -69,10 +61,13 @@ class JwtServiceImpl(
}
override suspend fun refreshToken(refreshToken: RefreshToken): JwtToken {
val token = refreshTokenRepository.findByToken(refreshToken.refreshToken)
?: throw InvalidRefreshTokenException("Invalid Refresh Token")
val token = try {
refreshTokenQueryService.findByToken(refreshToken.refreshToken)
} catch (_: NoSuchElementException) {
throw InvalidRefreshTokenException("Invalid Refresh Token")
}
val user = userService.findById(token.userId)
val user = userQueryService.findById(token.userId)
val now = Instant.now()
if (token.createdAt.isAfter(now)) {
@ -87,14 +82,14 @@ class JwtServiceImpl(
}
override suspend fun revokeToken(refreshToken: RefreshToken) {
refreshTokenRepository.deleteByToken(refreshToken.refreshToken)
refreshTokenQueryService.deleteByToken(refreshToken.refreshToken)
}
override suspend fun revokeToken(user: User) {
refreshTokenRepository.deleteByUserId(user.id)
refreshTokenQueryService.deleteByUserId(user.id)
}
override suspend fun revokeAll() {
refreshTokenRepository.deleteAll()
refreshTokenQueryService.deleteAll()
}
}

View File

@ -0,0 +1,13 @@
package dev.usbharu.hideout.service.core
import org.jetbrains.exposed.sql.transactions.experimental.newSuspendedTransaction
import org.koin.core.annotation.Single
@Single
class ExposedTransaction : Transaction {
override suspend fun <T> transaction(block: suspend () -> T): T {
return newSuspendedTransaction {
block()
}
}
}

View File

@ -7,10 +7,12 @@ import dev.usbharu.hideout.repository.IMetaRepository
import org.koin.core.annotation.Single
@Single
class MetaServiceImpl(private val metaRepository: IMetaRepository) : IMetaService {
override suspend fun getMeta(): Meta = metaRepository.get() ?: throw NotInitException("Meta is null")
class MetaServiceImpl(private val metaRepository: IMetaRepository, private val transaction: Transaction) :
IMetaService {
override suspend fun getMeta(): Meta =
transaction.transaction { metaRepository.get() ?: throw NotInitException("Meta is null") }
override suspend fun updateMeta(meta: Meta) {
override suspend fun updateMeta(meta: Meta) = transaction.transaction {
metaRepository.save(meta)
}

View File

@ -11,23 +11,29 @@ import java.security.KeyPairGenerator
import java.util.*
@Single
class ServerInitialiseServiceImpl(private val metaRepository: IMetaRepository) : IServerInitialiseService {
class ServerInitialiseServiceImpl(
private val metaRepository: IMetaRepository,
private val transaction: Transaction
) :
IServerInitialiseService {
val logger: Logger = LoggerFactory.getLogger(ServerInitialiseServiceImpl::class.java)
override suspend fun init() {
val savedMeta = metaRepository.get()
val implementationVersion = ServerUtil.getImplementationVersion()
if (wasInitialised(savedMeta).not()) {
logger.info("Start Initialise")
initialise(implementationVersion)
logger.info("Finish Initialise")
return
}
transaction.transaction {
val savedMeta = metaRepository.get()
val implementationVersion = ServerUtil.getImplementationVersion()
if (wasInitialised(savedMeta).not()) {
logger.info("Start Initialise")
initialise(implementationVersion)
logger.info("Finish Initialise")
return@transaction
}
if (isVersionChanged(requireNotNull(savedMeta))) {
logger.info("Version changed!! (${savedMeta.version} -> $implementationVersion)")
updateVersion(savedMeta, implementationVersion)
if (isVersionChanged(requireNotNull(savedMeta))) {
logger.info("Version changed!! (${savedMeta.version} -> $implementationVersion)")
updateVersion(savedMeta, implementationVersion)
}
}
}

View File

@ -0,0 +1,5 @@
package dev.usbharu.hideout.service.core
interface Transaction {
suspend fun <T> transaction(block: suspend () -> T): T
}

View File

@ -1,10 +1,7 @@
package dev.usbharu.hideout.service.reaction
import dev.usbharu.hideout.domain.model.hideout.dto.ReactionResponse
interface IReactionService {
suspend fun receiveReaction(name: String, domain: String, userId: Long, postId: Long)
suspend fun sendReaction(name: String, userId: Long, postId: Long)
suspend fun removeReaction(userId: Long, postId: Long)
suspend fun findByPostIdForUser(postId: Long, userId: Long): List<ReactionResponse>
}

View File

@ -1,25 +1,19 @@
package dev.usbharu.hideout.service.reaction
import dev.usbharu.hideout.domain.model.hideout.dto.Account
import dev.usbharu.hideout.domain.model.hideout.dto.ReactionResponse
import dev.usbharu.hideout.domain.model.hideout.entity.Reaction
import dev.usbharu.hideout.query.ReactionQueryService
import dev.usbharu.hideout.repository.ReactionRepository
import dev.usbharu.hideout.repository.Reactions
import dev.usbharu.hideout.repository.Users
import dev.usbharu.hideout.service.activitypub.ActivityPubReactionService
import org.jetbrains.exposed.sql.ResultRow
import org.jetbrains.exposed.sql.leftJoin
import org.jetbrains.exposed.sql.select
import org.jetbrains.exposed.sql.transactions.experimental.newSuspendedTransaction
import org.koin.core.annotation.Single
@Single
class ReactionServiceImpl(
private val reactionRepository: ReactionRepository,
private val activityPubReactionService: ActivityPubReactionService
private val activityPubReactionService: ActivityPubReactionService,
private val reactionQueryService: ReactionQueryService
) : IReactionService {
override suspend fun receiveReaction(name: String, domain: String, userId: Long, postId: Long) {
if (reactionRepository.reactionAlreadyExist(postId, userId, 0).not()) {
if (reactionQueryService.reactionAlreadyExist(postId, userId, 0).not()) {
reactionRepository.save(
Reaction(reactionRepository.generateId(), 0, postId, userId)
)
@ -27,9 +21,9 @@ class ReactionServiceImpl(
}
override suspend fun sendReaction(name: String, userId: Long, postId: Long) {
if (reactionRepository.reactionAlreadyExist(postId, userId, 0)) {
if (reactionQueryService.reactionAlreadyExist(postId, userId, 0)) {
// delete
reactionRepository.deleteByPostIdAndUserId(postId, userId)
reactionQueryService.deleteByPostIdAndUserId(postId, userId)
} else {
val reaction = Reaction(reactionRepository.generateId(), 0, postId, userId)
reactionRepository.save(reaction)
@ -38,18 +32,6 @@ class ReactionServiceImpl(
}
override suspend fun removeReaction(userId: Long, postId: Long) {
reactionRepository.deleteByPostIdAndUserId(postId, userId)
}
override suspend fun findByPostIdForUser(postId: Long, userId: Long): List<ReactionResponse> {
return newSuspendedTransaction {
Reactions
.leftJoin(Users, onColumn = { Reactions.userId }, otherColumn = { id })
.select { Reactions.postId.eq(postId) }
.groupBy { resultRow: ResultRow -> ReactionResponse("", true, "", listOf()) }
.map { entry: Map.Entry<ReactionResponse, List<ResultRow>> ->
entry.key.copy(accounts = entry.value.map { Account(it[Users.screenName], "", it[Users.url]) })
}
}
reactionQueryService.deleteByPostIdAndUserId(postId, userId)
}
}

View File

@ -6,23 +6,6 @@ import dev.usbharu.hideout.domain.model.hideout.entity.User
@Suppress("TooManyFunctions")
interface IUserService {
suspend fun findAll(limit: Int? = 100, offset: Long? = 0): List<User>
suspend fun findById(id: Long): User
suspend fun findByIds(ids: List<Long>): List<User>
suspend fun findByName(name: String): List<User>
suspend fun findByNameLocalUser(name: String): User
suspend fun findByNameAndDomain(name: String, domain: String? = null): User
suspend fun findByNameAndDomains(names: List<Pair<String, String>>): List<User>
suspend fun findByUrl(url: String): User
suspend fun findByUrls(urls: List<String>): List<User>
suspend fun usernameAlreadyUse(username: String): Boolean
@ -30,14 +13,6 @@ interface IUserService {
suspend fun createRemoteUser(user: RemoteUserCreateDto): User
suspend fun findFollowersById(id: Long): List<User>
suspend fun findFollowersByNameAndDomain(name: String, domain: String?): List<User>
suspend fun findFollowingById(id: Long): List<User>
suspend fun findFollowingByNameAndDomain(name: String, domain: String?): List<User>
/**
* フォローリクエストを送信する
*

View File

@ -1,7 +1,7 @@
package dev.usbharu.hideout.service.user
import dev.usbharu.hideout.config.Config
import dev.usbharu.hideout.repository.IUserRepository
import dev.usbharu.hideout.query.UserQueryService
import io.ktor.util.*
import org.koin.core.annotation.Single
import java.security.*
@ -9,7 +9,7 @@ import java.util.*
@Single
class UserAuthService(
val userRepository: IUserRepository
val userQueryService: UserQueryService
) : IUserAuthService {
override fun hash(password: String): String {
@ -18,13 +18,12 @@ class UserAuthService(
}
override suspend fun usernameAlreadyUse(username: String): Boolean {
userRepository.findByName(username)
userQueryService.findByName(username)
return true
}
override suspend fun verifyAccount(username: String, password: String): Boolean {
val userEntity = userRepository.findByNameAndDomain(username, Config.configData.domain)
?: return false
val userEntity = userQueryService.findByNameAndDomain(username, Config.configData.domain)
return userEntity.password == hash(password)
}

View File

@ -6,55 +6,25 @@ import dev.usbharu.hideout.domain.model.hideout.dto.SendFollowDto
import dev.usbharu.hideout.domain.model.hideout.dto.UserCreateDto
import dev.usbharu.hideout.domain.model.hideout.entity.User
import dev.usbharu.hideout.exception.UserNotFoundException
import dev.usbharu.hideout.query.FollowerQueryService
import dev.usbharu.hideout.query.UserQueryService
import dev.usbharu.hideout.repository.IUserRepository
import dev.usbharu.hideout.service.activitypub.ActivityPubSendFollowService
import org.koin.core.annotation.Single
import java.lang.Integer.min
import java.time.Instant
@Single
class UserService(
private val userRepository: IUserRepository,
private val userAuthService: IUserAuthService,
private val activityPubSendFollowService: ActivityPubSendFollowService
private val activityPubSendFollowService: ActivityPubSendFollowService,
private val userQueryService: UserQueryService,
private val followerQueryService: FollowerQueryService
) :
IUserService {
private val maxLimit = 100
override suspend fun findAll(limit: Int?, offset: Long?): List<User> {
return userRepository.findAllByLimitAndByOffset(
min(limit ?: maxLimit, maxLimit),
offset ?: 0
)
}
override suspend fun findById(id: Long): User =
userRepository.findById(id) ?: throw UserNotFoundException("$id was not found.")
override suspend fun findByIds(ids: List<Long>): List<User> = userRepository.findByIds(ids)
override suspend fun findByName(name: String): List<User> = userRepository.findByName(name)
override suspend fun findByNameLocalUser(name: String): User {
return userRepository.findByNameAndDomain(name, Config.configData.domain)
?: throw UserNotFoundException("$name was not found.")
}
override suspend fun findByNameAndDomain(name: String, domain: String?): User {
return userRepository.findByNameAndDomain(name, domain ?: Config.configData.domain)
?: throw UserNotFoundException("$name was not found.")
}
override suspend fun findByNameAndDomains(names: List<Pair<String, String>>): List<User> =
userRepository.findByNameAndDomains(names)
override suspend fun findByUrl(url: String): User =
userRepository.findByUrl(url) ?: throw UserNotFoundException("$url was not found.")
override suspend fun findByUrls(urls: List<String>): List<User> = userRepository.findByUrls(urls)
override suspend fun usernameAlreadyUse(username: String): Boolean {
val findByNameAndDomain = userRepository.findByNameAndDomain(username, Config.configData.domain)
val findByNameAndDomain = userQueryService.findByNameAndDomain(username, Config.configData.domain)
return findByNameAndDomain != null
}
@ -96,19 +66,6 @@ class UserService(
return userRepository.save(userEntity)
}
override suspend fun findFollowersById(id: Long): List<User> = userRepository.findFollowersById(id)
override suspend fun findFollowersByNameAndDomain(name: String, domain: String?): List<User> {
TODO("Not yet implemented")
}
override suspend fun findFollowingById(id: Long): List<User> {
TODO("Not yet implemented")
}
override suspend fun findFollowingByNameAndDomain(name: String, domain: String?): List<User> {
TODO("Not yet implemented")
}
// TODO APのフォロー処理を作る
override suspend fun followRequest(id: Long, followerId: Long): Boolean {
val user = userRepository.findById(id) ?: throw UserNotFoundException("$id was not found.")
@ -127,14 +84,14 @@ class UserService(
}
override suspend fun follow(id: Long, followerId: Long) {
userRepository.createFollower(id, followerId)
followerQueryService.appendFollower(id, followerId)
if (userRepository.findFollowRequestsById(id, followerId)) {
userRepository.deleteFollowRequest(id, followerId)
}
}
override suspend fun unfollow(id: Long, followerId: Long): Boolean {
userRepository.deleteFollower(id, followerId)
followerQueryService.removeFollower(id, followerId)
return false
}
}

View File

@ -202,6 +202,7 @@ class ExposedJobRepository(
}
}
@Suppress("CyclomaticComplexMethod")
private fun String?.parseJsonMap(): Map<String, Any> {
this ?: return emptyMap()
return json.parseToJsonElement(this).jsonObject.mapValues { (_, el) ->
@ -232,6 +233,7 @@ class ExposedJobRepository(
}
}
@Suppress("CyclomaticComplexMethod")
private fun Map<String, Any>.stringify(): String? {
if (isEmpty()) {
return null

View File

@ -4,7 +4,7 @@
<pattern>%d{YYYY-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<root level="TRACE">
<root level="INFO">
<appender-ref ref="STDOUT"/>
</root>
<logger name="org.eclipse.jetty" level="INFO"/>

View File

@ -2,41 +2,29 @@ package dev.usbharu.hideout.plugins
import dev.usbharu.hideout.domain.model.ap.JsonLd
import dev.usbharu.hideout.domain.model.hideout.entity.User
import dev.usbharu.hideout.repository.IUserRepository
import dev.usbharu.hideout.query.UserQueryService
import dev.usbharu.hideout.service.user.toPem
import io.ktor.client.*
import io.ktor.client.engine.mock.*
import io.ktor.client.plugins.logging.*
import kotlinx.coroutines.runBlocking
import org.junit.jupiter.api.Test
import org.mockito.kotlin.any
import org.mockito.kotlin.doAnswer
import org.mockito.kotlin.mock
import utils.TestTransaction
import java.security.KeyPairGenerator
import java.time.Instant
class ActivityPubKtTest {
@Test
fun HttpSignTest(): Unit = runBlocking {
val ktorKeyMap = KtorKeyMap(object : IUserRepository {
override suspend fun save(user: User): User {
TODO("Not yet implemented")
}
override suspend fun findById(id: Long): User? {
TODO("Not yet implemented")
}
override suspend fun findByIds(ids: List<Long>): List<User> {
TODO("Not yet implemented")
}
override suspend fun findByName(name: String): List<User> {
TODO()
}
override suspend fun findByNameAndDomain(name: String, domain: String): User {
fun HttpSignTest() {
val userQueryService = mock<UserQueryService> {
onBlocking { findByNameAndDomain(any(), any()) } doAnswer {
val keyPairGenerator = KeyPairGenerator.getInstance("RSA")
keyPairGenerator.initialize(1024)
val generateKeyPair = keyPairGenerator.generateKeyPair()
return User(
User(
1,
"test",
"localhost",
@ -51,78 +39,25 @@ class ActivityPubKtTest {
Instant.now()
)
}
override suspend fun findByDomain(domain: String): List<User> {
TODO("Not yet implemented")
}
override suspend fun findByNameAndDomains(names: List<Pair<String, String>>): List<User> {
TODO("Not yet implemented")
}
override suspend fun findByUrl(url: String): User? {
TODO("Not yet implemented")
}
override suspend fun findByUrls(urls: List<String>): List<User> {
TODO("Not yet implemented")
}
override suspend fun delete(id: Long) {
TODO("Not yet implemented")
}
override suspend fun findAll(): List<User> {
TODO("Not yet implemented")
}
override suspend fun findAllByLimitAndByOffset(limit: Int, offset: Long): List<User> {
TODO("Not yet implemented")
}
override suspend fun createFollower(id: Long, follower: Long) {
TODO("Not yet implemented")
}
override suspend fun deleteFollower(id: Long, follower: Long) {
TODO("Not yet implemented")
}
override suspend fun findFollowersById(id: Long): List<User> {
TODO("Not yet implemented")
}
override suspend fun addFollowRequest(id: Long, follower: Long) {
TODO("Not yet implemented")
}
override suspend fun deleteFollowRequest(id: Long, follower: Long) {
TODO("Not yet implemented")
}
override suspend fun findFollowRequestsById(id: Long, follower: Long): Boolean {
TODO("Not yet implemented")
}
override suspend fun nextId(): Long {
TODO("Not yet implemented")
}
})
val httpClient = HttpClient(
MockEngine { httpRequestData ->
respondOk()
}
) {
install(httpSignaturePlugin) {
keyMap = ktorKeyMap
}
install(Logging) {
logger = Logger.DEFAULT
level = LogLevel.ALL
}
}
runBlocking {
val ktorKeyMap = KtorKeyMap(userQueryService, TestTransaction)
httpClient.postAp("https://localhost", "test", JsonLd(emptyList()))
val httpClient = HttpClient(
MockEngine { httpRequestData ->
respondOk()
}
) {
install(httpSignaturePlugin) {
keyMap = ktorKeyMap
}
install(Logging) {
logger = Logger.DEFAULT
level = LogLevel.ALL
}
}
httpClient.postAp("https://localhost", "test", JsonLd(emptyList()))
}
}
}

View File

@ -1,9 +1,13 @@
package dev.usbharu.hideout.plugins
import dev.usbharu.hideout.domain.model.hideout.entity.User
import dev.usbharu.hideout.repository.IUserRepository
import dev.usbharu.hideout.query.UserQueryService
import dev.usbharu.hideout.service.user.toPem
import org.junit.jupiter.api.Test
import org.mockito.kotlin.any
import org.mockito.kotlin.doAnswer
import org.mockito.kotlin.mock
import utils.TestTransaction
import java.security.KeyPairGenerator
import java.time.Instant
@ -11,28 +15,12 @@ class KtorKeyMapTest {
@Test
fun getPrivateKey() {
val ktorKeyMap = KtorKeyMap(object : IUserRepository {
override suspend fun save(user: User): User {
TODO("Not yet implemented")
}
override suspend fun findById(id: Long): User? {
TODO("Not yet implemented")
}
override suspend fun findByIds(ids: List<Long>): List<User> {
TODO("Not yet implemented")
}
override suspend fun findByName(name: String): List<User> {
TODO()
}
override suspend fun findByNameAndDomain(name: String, domain: String): User {
val userQueryService = mock<UserQueryService> {
onBlocking { findByNameAndDomain(any(), any()) } doAnswer {
val keyPairGenerator = KeyPairGenerator.getInstance("RSA")
keyPairGenerator.initialize(1024)
val generateKeyPair = keyPairGenerator.generateKeyPair()
return User(
User(
1,
"test",
"localhost",
@ -47,63 +35,8 @@ class KtorKeyMapTest {
createdAt = Instant.now()
)
}
override suspend fun findByDomain(domain: String): List<User> {
TODO("Not yet implemented")
}
override suspend fun findByNameAndDomains(names: List<Pair<String, String>>): List<User> {
TODO("Not yet implemented")
}
override suspend fun findByUrl(url: String): User? {
TODO("Not yet implemented")
}
override suspend fun findByUrls(urls: List<String>): List<User> {
TODO("Not yet implemented")
}
override suspend fun delete(id: Long) {
TODO("Not yet implemented")
}
override suspend fun findAll(): List<User> {
TODO("Not yet implemented")
}
override suspend fun findAllByLimitAndByOffset(limit: Int, offset: Long): List<User> {
TODO("Not yet implemented")
}
override suspend fun createFollower(id: Long, follower: Long) {
TODO("Not yet implemented")
}
override suspend fun deleteFollower(id: Long, follower: Long) {
TODO("Not yet implemented")
}
override suspend fun findFollowersById(id: Long): List<User> {
TODO("Not yet implemented")
}
override suspend fun addFollowRequest(id: Long, follower: Long) {
TODO("Not yet implemented")
}
override suspend fun deleteFollowRequest(id: Long, follower: Long) {
TODO("Not yet implemented")
}
override suspend fun findFollowRequestsById(id: Long, follower: Long): Boolean {
TODO("Not yet implemented")
}
override suspend fun nextId(): Long {
TODO("Not yet implemented")
}
})
}
val ktorKeyMap = KtorKeyMap(userQueryService, TestTransaction)
ktorKeyMap.getPrivateKey("test")
}

View File

@ -14,8 +14,10 @@ import dev.usbharu.hideout.domain.model.hideout.entity.User
import dev.usbharu.hideout.domain.model.hideout.form.RefreshToken
import dev.usbharu.hideout.domain.model.hideout.form.UserLogin
import dev.usbharu.hideout.exception.InvalidRefreshTokenException
import dev.usbharu.hideout.repository.IUserRepository
import dev.usbharu.hideout.exception.InvalidUsernameOrPasswordException
import dev.usbharu.hideout.query.UserQueryService
import dev.usbharu.hideout.routing.api.internal.v1.auth
import dev.usbharu.hideout.service.api.UserAuthApiService
import dev.usbharu.hideout.service.auth.IJwtService
import dev.usbharu.hideout.service.core.IMetaService
import dev.usbharu.hideout.service.user.IUserAuthService
@ -45,11 +47,12 @@ class SecurityKtTest {
config = ApplicationConfig("empty.conf")
}
Config.configData = ConfigData(url = "http://example.com", objectMapper = jacksonObjectMapper())
val userAuthService = mock<IUserAuthService> {
onBlocking { verifyAccount(eq("testUser"), eq("password")) } doReturn true
val jwtToken = JwtToken("Token", "RefreshToken")
val userAuthService = mock<UserAuthApiService> {
onBlocking { login(eq("testUser"), eq("password")) } doReturn jwtToken
}
val metaService = mock<IMetaService>()
val userRepository = mock<IUserRepository> {
val userQueryService = mock<UserQueryService> {
onBlocking { findByNameAndDomain(eq("testUser"), eq("example.com")) } doReturn User(
id = 1L,
name = "testUser",
@ -65,16 +68,12 @@ class SecurityKtTest {
createdAt = Instant.now()
)
}
val jwtToken = JwtToken("Token", "RefreshToken")
val jwtService = mock<IJwtService> {
onBlocking { createToken(any()) } doReturn jwtToken
}
val jwkProvider = mock<JwkProvider>()
application {
configureSerialization()
configureSecurity(jwkProvider, metaService)
routing {
auth(userAuthService, userRepository, jwtService)
auth(userAuthService)
}
}
@ -88,30 +87,36 @@ class SecurityKtTest {
}
@Test
fun `login 存在しないユーザーのログインに失敗する`() = testApplication {
environment {
config = ApplicationConfig("empty.conf")
}
Config.configData = ConfigData(url = "http://example.com", objectMapper = jacksonObjectMapper())
val userAuthService = mock<IUserAuthService> {
onBlocking { verifyAccount(anyString(), anyString()) }.doReturn(false)
}
val metaService = mock<IMetaService>()
val userRepository = mock<IUserRepository>()
val jwtService = mock<IJwtService>()
val jwkProvider = mock<JwkProvider>()
application {
configureSerialization()
configureSecurity(jwkProvider, metaService)
routing {
auth(userAuthService, userRepository, jwtService)
fun `login 存在しないユーザーのログインに失敗する`() {
testApplication {
environment {
config = ApplicationConfig("empty.conf")
}
Config.configData = ConfigData(url = "http://example.com", objectMapper = jacksonObjectMapper())
mock<IUserAuthService> {
onBlocking { verifyAccount(anyString(), anyString()) }.doReturn(false)
}
val metaService = mock<IMetaService>()
mock<UserQueryService>()
mock<IJwtService>()
val jwkProvider = mock<JwkProvider>()
val userAuthApiService = mock<UserAuthApiService> {
onBlocking { login(anyString(), anyString()) } doThrow InvalidUsernameOrPasswordException()
}
application {
configureStatusPages()
configureSerialization()
configureSecurity(jwkProvider, metaService)
routing {
auth(userAuthApiService)
}
}
client.post("/login") {
contentType(ContentType.Application.Json)
setBody(Config.configData.objectMapper.writeValueAsString(UserLogin("InvalidTtestUser", "password")))
}.apply {
assertEquals(HttpStatusCode.Unauthorized, call.response.status)
}
}
client.post("/login") {
contentType(ContentType.Application.Json)
setBody(Config.configData.objectMapper.writeValueAsString(UserLogin("InvalidTtestUser", "password")))
}.apply {
assertEquals(HttpStatusCode.Unauthorized, call.response.status)
}
}
@ -121,18 +126,17 @@ class SecurityKtTest {
config = ApplicationConfig("empty.conf")
}
Config.configData = ConfigData(url = "http://example.com", objectMapper = jacksonObjectMapper())
val userAuthService = mock<IUserAuthService> {
onBlocking { verifyAccount(anyString(), eq("InvalidPassword")) } doReturn false
}
val metaService = mock<IMetaService>()
val userRepository = mock<IUserRepository>()
val jwtService = mock<IJwtService>()
val jwkProvider = mock<JwkProvider>()
val userAuthApiService = mock<UserAuthApiService> {
onBlocking { login(anyString(), eq("InvalidPassword")) } doThrow InvalidUsernameOrPasswordException()
}
application {
configureStatusPages()
configureSerialization()
configureSecurity(jwkProvider, metaService)
routing {
auth(userAuthService, userRepository, jwtService)
auth(userAuthApiService)
}
}
client.post("/login") {
@ -153,7 +157,7 @@ class SecurityKtTest {
configureSerialization()
configureSecurity(mock(), mock())
routing {
auth(mock(), mock(), mock())
auth(mock())
}
}
client.get("/auth-check").apply {
@ -171,7 +175,7 @@ class SecurityKtTest {
configureSerialization()
configureSecurity(mock(), mock())
routing {
auth(mock(), mock(), mock())
auth(mock())
}
}
client.get("/auth-check") {
@ -191,7 +195,7 @@ class SecurityKtTest {
configureSerialization()
configureSecurity(mock(), mock())
routing {
auth(mock(), mock(), mock())
auth(mock())
}
}
client.get("/auth-check") {
@ -212,7 +216,7 @@ class SecurityKtTest {
configureSerialization()
configureSecurity(mock(), mock())
routing {
auth(mock(), mock(), mock())
auth(mock())
}
}
client.get("/auth-check") {
@ -271,7 +275,7 @@ class SecurityKtTest {
configureSerialization()
configureSecurity(jwkProvider, metaService)
routing {
auth(mock(), mock(), mock())
auth(mock())
}
}
@ -332,7 +336,7 @@ class SecurityKtTest {
configureSerialization()
configureSecurity(jwkProvider, metaService)
routing {
auth(mock(), mock(), mock())
auth(mock())
}
}
client.get("/auth-check") {
@ -391,7 +395,7 @@ class SecurityKtTest {
configureSerialization()
configureSecurity(jwkProvider, metaService)
routing {
auth(mock(), mock(), mock())
auth(mock())
}
}
client.get("/auth-check") {
@ -450,7 +454,7 @@ class SecurityKtTest {
configureSerialization()
configureSecurity(jwkProvider, metaService)
routing {
auth(mock(), mock(), mock())
auth(mock())
}
}
client.get("/auth-check") {
@ -508,7 +512,7 @@ class SecurityKtTest {
configureSerialization()
configureSecurity(jwkProvider, metaService)
routing {
auth(mock(), mock(), mock())
auth(mock())
}
}
client.get("/auth-check") {
@ -524,14 +528,14 @@ class SecurityKtTest {
environment {
config = ApplicationConfig("empty.conf")
}
val jwtService = mock<IJwtService> {
val jwtService = mock<UserAuthApiService> {
onBlocking { refreshToken(any()) }.doReturn(JwtToken("token", "refreshToken2"))
}
application {
configureSerialization()
configureSecurity(mock(), mock())
routing {
auth(mock(), mock(), jwtService)
auth(jwtService)
}
}
client.post("/refresh-token") {
@ -548,7 +552,7 @@ class SecurityKtTest {
environment {
config = ApplicationConfig("empty.conf")
}
val jwtService = mock<IJwtService> {
val jwtService = mock<UserAuthApiService> {
onBlocking { refreshToken(any()) } doThrow InvalidRefreshTokenException("Invalid Refresh Token")
}
application {
@ -556,7 +560,7 @@ class SecurityKtTest {
configureSerialization()
configureSecurity(mock(), mock())
routing {
auth(mock(), mock(), jwtService)
auth(jwtService)
}
}
client.post("/refresh-token") {

View File

@ -10,6 +10,8 @@ import org.jetbrains.exposed.sql.Database
import org.jetbrains.exposed.sql.SchemaUtils
import org.jetbrains.exposed.sql.insert
import org.jetbrains.exposed.sql.select
import org.jetbrains.exposed.sql.transactions.TransactionManager
import org.jetbrains.exposed.sql.transactions.experimental.newSuspendedTransaction
import org.jetbrains.exposed.sql.transactions.transaction
import org.junit.jupiter.api.AfterEach
import org.junit.jupiter.api.BeforeEach
@ -37,6 +39,7 @@ class JwtRefreshTokenRepositoryImplTest {
transaction(db) {
SchemaUtils.drop(JwtRefreshTokens)
}
TransactionManager.closeAndUnregister(db)
}
@Test
@ -53,9 +56,11 @@ class JwtRefreshTokenRepositoryImplTest {
val expiresAt = now.plus(10, ChronoUnit.MINUTES)
val expect = JwtRefreshToken(1L, 2L, "refreshToken", now, expiresAt)
repository.save(expect)
val actual = repository.findById(1L)
assertEquals(expect, actual)
newSuspendedTransaction {
repository.save(expect)
val actual = repository.findById(1L)
assertEquals(expect, actual)
}
}
@Test
@ -68,7 +73,7 @@ class JwtRefreshTokenRepositoryImplTest {
}
}
)
transaction {
newSuspendedTransaction {
JwtRefreshTokens.insert {
it[id] = 1L
it[userId] = 2L
@ -76,17 +81,17 @@ class JwtRefreshTokenRepositoryImplTest {
it[createdAt] = Instant.now().toEpochMilli()
it[expiresAt] = Instant.now().plus(10, ChronoUnit.MINUTES).toEpochMilli()
}
repository.save(
JwtRefreshToken(
id = 1L,
userId = 2L,
refreshToken = "refreshToken2",
createdAt = Instant.now(),
expiresAt = Instant.now().plus(10, ChronoUnit.MINUTES)
)
)
}
repository.save(
JwtRefreshToken(
id = 1L,
userId = 2L,
refreshToken = "refreshToken2",
createdAt = Instant.now(),
expiresAt = Instant.now().plus(10, ChronoUnit.MINUTES)
)
)
transaction {
val toJwtRefreshToken = JwtRefreshTokens.select { JwtRefreshTokens.id.eq(1L) }.single().toJwtRefreshToken()
assertEquals("refreshToken2", toJwtRefreshToken.refreshToken)

View File

@ -1,151 +0,0 @@
@file:OptIn(ExperimentalCoroutinesApi::class)
package dev.usbharu.hideout.repository
import dev.usbharu.hideout.domain.model.hideout.entity.User
import dev.usbharu.hideout.service.core.IdGenerateService
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.runTest
import org.jetbrains.exposed.sql.Database
import org.jetbrains.exposed.sql.SchemaUtils
import org.jetbrains.exposed.sql.select
import org.jetbrains.exposed.sql.transactions.transaction
import org.junit.jupiter.api.AfterEach
import org.junit.jupiter.api.Assertions.assertIterableEquals
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
import java.time.Clock
import java.time.Instant
import java.time.ZoneId
class UserRepositoryTest {
lateinit var db: Database
@BeforeEach
fun beforeEach() {
db = Database.connect("jdbc:h2:mem:test;DB_CLOSE_DELAY=-1", driver = "org.h2.Driver")
transaction(db) {
SchemaUtils.create(Users)
SchemaUtils.create(UsersFollowers)
SchemaUtils.create(FollowRequests)
}
}
@AfterEach
fun tearDown() {
transaction(db) {
SchemaUtils.drop(UsersFollowers)
SchemaUtils.drop(FollowRequests)
SchemaUtils.drop(Users)
}
}
@Test
fun `findFollowersById フォロワー一覧を取得`() = runTest {
val userRepository = UserRepository(
db,
object : IdGenerateService {
override suspend fun generateId(): Long {
TODO("Not yet implemented")
}
}
)
val user = userRepository.save(
User(
id = 0L,
name = "test",
domain = "example.com",
screenName = "testUser",
description = "This user is test user.",
password = "https://example.com/inbox",
inbox = "",
outbox = "https://example.com/outbox",
url = "https://example.com",
publicKey = "",
createdAt = Instant.now(Clock.tickMillis(ZoneId.systemDefault()))
)
)
val follower = userRepository.save(
User(
id = 1L,
name = "follower",
domain = "follower.example.com",
screenName = "followerUser",
description = "This user is follower user.",
password = "",
inbox = "https://follower.example.com/inbox",
outbox = "https://follower.example.com/outbox",
url = "https://follower.example.com",
publicKey = "",
createdAt = Instant.now(Clock.tickMillis(ZoneId.systemDefault()))
)
)
val follower2 = userRepository.save(
User(
id = 3L,
name = "follower2",
domain = "follower2.example.com",
screenName = "followerUser2",
description = "This user is follower user 2.",
password = "",
inbox = "https://follower2.example.com/inbox",
outbox = "https://follower2.example.com/outbox",
url = "https://follower2.example.com",
publicKey = "",
createdAt = Instant.now(Clock.tickMillis(ZoneId.systemDefault()))
)
)
userRepository.createFollower(user.id, follower.id)
userRepository.createFollower(user.id, follower2.id)
assertIterableEquals(listOf(follower, follower2), userRepository.findFollowersById(user.id))
}
@Test
fun `createFollower フォロワー追加`() = runTest {
val userRepository = UserRepository(
db,
object : IdGenerateService {
override suspend fun generateId(): Long {
TODO("Not yet implemented")
}
}
)
val user = userRepository.save(
User(
0L,
"test",
"example.com",
"testUser",
"This user is test user.",
"https://example.com/inbox",
"",
"https://example.com/outbox",
"https://example.com",
publicKey = "",
createdAt = Instant.now()
)
)
val follower = userRepository.save(
User(
1L,
"follower",
"follower.example.com",
"followerUser",
"This user is follower user.",
"",
"https://follower.example.com/inbox",
"https://follower.example.com/outbox",
"https://follower.example.com",
publicKey = "",
createdAt = Instant.now()
)
)
userRepository.createFollower(user.id, follower.id)
transaction {
val followerIds =
UsersFollowers.select { UsersFollowers.userId eq user.id }.map { it[UsersFollowers.followerId] }
assertIterableEquals(listOf(follower.id), followerIds)
}
}
}

View File

@ -11,8 +11,8 @@ import dev.usbharu.hideout.domain.model.ap.Key
import dev.usbharu.hideout.domain.model.ap.Person
import dev.usbharu.hideout.domain.model.hideout.entity.User
import dev.usbharu.hideout.plugins.configureSerialization
import dev.usbharu.hideout.query.UserQueryService
import dev.usbharu.hideout.service.activitypub.ActivityPubUserService
import dev.usbharu.hideout.service.user.IUserService
import dev.usbharu.hideout.util.HttpUtil.Activity
import dev.usbharu.hideout.util.HttpUtil.JsonLd
import io.ktor.client.request.*
@ -26,6 +26,7 @@ import org.mockito.ArgumentMatchers.anyString
import org.mockito.kotlin.doReturn
import org.mockito.kotlin.eq
import org.mockito.kotlin.mock
import utils.TestTransaction
import java.time.Instant
import kotlin.test.assertEquals
import kotlin.test.assertTrue
@ -62,8 +63,6 @@ class UsersAPTest {
)
person.context = listOf("https://www.w3.org/ns/activitystreams")
val userService = mock<IUserService> {}
val activityPubUserService = mock<ActivityPubUserService> {
onBlocking { getPersonByName(anyString()) } doReturn person
}
@ -71,7 +70,7 @@ class UsersAPTest {
application {
configureSerialization()
routing {
usersAP(activityPubUserService, userService)
usersAP(activityPubUserService, mock(), mock(), TestTransaction)
}
}
client.get("/users/test") {
@ -122,8 +121,6 @@ class UsersAPTest {
)
person.context = listOf("https://www.w3.org/ns/activitystreams")
val userService = mock<IUserService> {}
val activityPubUserService = mock<ActivityPubUserService> {
onBlocking { getPersonByName(anyString()) } doReturn person
}
@ -131,7 +128,7 @@ class UsersAPTest {
application {
configureSerialization()
routing {
usersAP(activityPubUserService, userService)
usersAP(activityPubUserService, mock(), mock(), TestTransaction)
}
}
client.get("/users/test") {
@ -174,8 +171,8 @@ class UsersAPTest {
environment {
config = ApplicationConfig("empty.conf")
}
val userService = mock<IUserService> {
onBlocking { findByNameLocalUser(eq("test")) } doReturn User(
val userService = mock<UserQueryService> {
onBlocking { findByNameAndDomain(eq("test"), anyString()) } doReturn User(
1L,
"test",
"example.com",
@ -192,7 +189,7 @@ class UsersAPTest {
}
application {
routing {
usersAP(mock(), userService)
usersAP(mock(), userService, mock(), TestTransaction)
}
}
client.get("/users/test") {

View File

@ -6,7 +6,6 @@ import com.fasterxml.jackson.module.kotlin.readValue
import dev.usbharu.hideout.config.Config
import dev.usbharu.hideout.domain.model.hideout.dto.PostResponse
import dev.usbharu.hideout.domain.model.hideout.dto.UserResponse
import dev.usbharu.hideout.domain.model.hideout.entity.Post
import dev.usbharu.hideout.domain.model.hideout.entity.Visibility
import dev.usbharu.hideout.plugins.TOKEN_AUTH
import dev.usbharu.hideout.plugins.configureSecurity
@ -78,7 +77,7 @@ class PostsTest {
configureSecurity(mock(), mock())
routing {
route("/api/internal/v1") {
posts(postService, mock())
posts(postService)
}
}
}
@ -159,7 +158,7 @@ class PostsTest {
configureSerialization()
routing {
route("/api/internal/v1") {
posts(postService, mock())
posts(postService)
}
}
}
@ -200,7 +199,7 @@ class PostsTest {
configureSecurity(mock(), mock())
routing {
route("/api/internal/v1") {
posts(postService, mock())
posts(postService)
}
}
}
@ -251,7 +250,7 @@ class PostsTest {
}
routing {
route("/api/internal/v1") {
posts(postService, mock())
posts(postService)
}
}
}
@ -308,7 +307,7 @@ class PostsTest {
}
routing {
route("/api/internal/v1") {
posts(postService, mock())
posts(postService)
}
}
configureSerialization()
@ -379,7 +378,7 @@ class PostsTest {
configureSecurity(mock(), mock())
routing {
route("/api/internal/v1") {
posts(postService, mock())
posts(postService)
}
}
}
@ -440,7 +439,7 @@ class PostsTest {
configureSecurity(mock(), mock())
routing {
route("/api/internal/v1") {
posts(postService, mock())
posts(postService)
}
}
}
@ -501,7 +500,7 @@ class PostsTest {
configureSecurity(mock(), mock())
routing {
route("/api/internal/v1") {
posts(postService, mock())
posts(postService)
}
}
}
@ -562,7 +561,7 @@ class PostsTest {
configureSecurity(mock(), mock())
routing {
route("/api/internal/v1") {
posts(postService, mock())
posts(postService)
}
}
}
@ -602,7 +601,7 @@ class PostsTest {
configureSecurity(mock(), mock())
routing {
route("/api/internal/v1") {
posts(postService, mock())
posts(postService)
}
}
}
@ -642,7 +641,7 @@ class PostsTest {
configureSecurity(mock(), mock())
routing {
route("/api/internal/v1") {
posts(postService, mock())
posts(postService)
}
}
}
@ -682,7 +681,7 @@ class PostsTest {
configureSecurity(mock(), mock())
routing {
route("/api/internal/v1") {
posts(postService, mock())
posts(postService)
}
}
}
@ -722,7 +721,7 @@ class PostsTest {
configureSecurity(mock(), mock())
routing {
route("/api/internal/v1") {
posts(postService, mock())
posts(postService)
}
}
}

View File

@ -9,8 +9,9 @@ import dev.usbharu.hideout.domain.model.hideout.entity.Post
import dev.usbharu.hideout.domain.model.hideout.entity.User
import dev.usbharu.hideout.domain.model.hideout.entity.Visibility
import dev.usbharu.hideout.domain.model.job.DeliverPostJob
import dev.usbharu.hideout.query.FollowerQueryService
import dev.usbharu.hideout.query.UserQueryService
import dev.usbharu.hideout.service.job.JobQueueParentService
import dev.usbharu.hideout.service.user.IUserService
import io.ktor.client.*
import io.ktor.client.engine.mock.*
import kjob.core.job.JobProps
@ -55,7 +56,7 @@ class ActivityPubNoteServiceImplTest {
createdAt = Instant.now()
)
)
val userService = mock<IUserService> {
val userQueryService = mock<UserQueryService> {
onBlocking { findById(eq(1L)) } doReturn User(
1L,
"test",
@ -69,11 +70,21 @@ class ActivityPubNoteServiceImplTest {
publicKey = "",
createdAt = Instant.now()
)
}
val followerQueryService = mock<FollowerQueryService> {
onBlocking { findFollowersById(eq(1L)) } doReturn followers
}
val jobQueueParentService = mock<JobQueueParentService>()
val activityPubNoteService =
ActivityPubNoteServiceImpl(mock(), jobQueueParentService, userService, mock(), mock())
ActivityPubNoteServiceImpl(
mock(),
jobQueueParentService,
mock(),
mock(),
userQueryService,
followerQueryService,
mock()
)
val postEntity = Post(
1L,
1L,
@ -96,7 +107,15 @@ class ActivityPubNoteServiceImplTest {
respondOk()
}
)
val activityPubNoteService = ActivityPubNoteServiceImpl(httpClient, mock(), mock(), mock(), mock())
val activityPubNoteService = ActivityPubNoteServiceImpl(
httpClient,
mock(),
mock(),
mock(),
mock(),
mock(),
mock()
)
activityPubNoteService.createNoteJob(
JobProps(
data = mapOf<String, Any>(

View File

@ -9,6 +9,7 @@ import dev.usbharu.hideout.config.ConfigData
import dev.usbharu.hideout.domain.model.ap.*
import dev.usbharu.hideout.domain.model.hideout.entity.User
import dev.usbharu.hideout.domain.model.job.ReceiveFollowJob
import dev.usbharu.hideout.query.UserQueryService
import dev.usbharu.hideout.service.job.JobQueueParentService
import dev.usbharu.hideout.service.user.IUserService
import io.ktor.client.*
@ -23,6 +24,7 @@ import org.junit.jupiter.api.Test
import org.mockito.ArgumentMatchers.anyString
import org.mockito.kotlin.*
import utils.JsonObjectMapper
import utils.TestTransaction
import java.time.Instant
class ActivityPubReceiveFollowServiceImplTest {
@ -32,7 +34,7 @@ class ActivityPubReceiveFollowServiceImplTest {
onBlocking { schedule(eq(ReceiveFollowJob), any()) } doReturn Unit
}
val activityPubFollowService =
ActivityPubReceiveFollowServiceImpl(jobQueueParentService, mock(), mock(), mock())
ActivityPubReceiveFollowServiceImpl(jobQueueParentService, mock(), mock(), mock(), mock(), TestTransaction)
activityPubFollowService.receiveFollow(
Follow(
emptyList(),
@ -97,8 +99,8 @@ class ActivityPubReceiveFollowServiceImplTest {
val activityPubUserService = mock<ActivityPubUserService> {
onBlocking { fetchPerson(anyString(), any()) } doReturn person
}
val userService = mock<IUserService> {
onBlocking { findByUrls(any()) } doReturn listOf(
val userQueryService = mock<UserQueryService> {
onBlocking { findByUrl(eq("https://example.com")) } doReturn
User(
id = 1L,
name = "test",
@ -110,7 +112,8 @@ class ActivityPubReceiveFollowServiceImplTest {
url = "https://example.com",
publicKey = "",
createdAt = Instant.now()
),
)
onBlocking { findByUrl(eq("https://follower.example.com")) } doReturn
User(
id = 2L,
name = "follower",
@ -123,7 +126,9 @@ class ActivityPubReceiveFollowServiceImplTest {
publicKey = "",
createdAt = Instant.now()
)
)
}
val userService = mock<IUserService> {
onBlocking { followRequest(any(), any()) } doReturn false
}
val activityPubFollowService =
@ -156,7 +161,9 @@ class ActivityPubReceiveFollowServiceImplTest {
)
respondOk()
}
)
),
userQueryService,
TestTransaction
)
activityPubFollowService.receiveFollowJob(
JobProps(

View File

@ -12,15 +12,17 @@ import dev.usbharu.hideout.domain.model.hideout.entity.JwtRefreshToken
import dev.usbharu.hideout.domain.model.hideout.entity.User
import dev.usbharu.hideout.domain.model.hideout.form.RefreshToken
import dev.usbharu.hideout.exception.InvalidRefreshTokenException
import dev.usbharu.hideout.query.JwtRefreshTokenQueryService
import dev.usbharu.hideout.query.UserQueryService
import dev.usbharu.hideout.repository.IJwtRefreshTokenRepository
import dev.usbharu.hideout.service.core.IMetaService
import dev.usbharu.hideout.service.user.IUserService
import dev.usbharu.hideout.util.Base64Util
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.runTest
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.assertThrows
import org.mockito.kotlin.doReturn
import org.mockito.kotlin.doThrow
import org.mockito.kotlin.mock
import java.security.KeyPairGenerator
import java.security.interfaces.RSAPrivateKey
@ -50,7 +52,7 @@ class JwtServiceImplTest {
val refreshTokenRepository = mock<IJwtRefreshTokenRepository> {
onBlocking { generateId() } doReturn 1L
}
val jwtService = JwtServiceImpl(metaService, refreshTokenRepository, mock())
val jwtService = JwtServiceImpl(metaService, refreshTokenRepository, mock(), mock())
val token = jwtService.createToken(
User(
id = 1L,
@ -93,6 +95,10 @@ class JwtServiceImplTest {
val generateKeyPair = keyPairGenerator.generateKeyPair()
val refreshTokenRepository = mock<IJwtRefreshTokenRepository> {
onBlocking { generateId() } doReturn 2L
}
val jwtRefreshTokenQueryService = mock<JwtRefreshTokenQueryService> {
onBlocking { findByToken("refreshToken") } doReturn JwtRefreshToken(
id = 1L,
userId = 1L,
@ -100,9 +106,8 @@ class JwtServiceImplTest {
createdAt = Instant.now().minus(60, ChronoUnit.MINUTES),
expiresAt = Instant.now().plus(14, ChronoUnit.DAYS).minus(60, ChronoUnit.MINUTES)
)
onBlocking { generateId() } doReturn 2L
}
val userService = mock<IUserService> {
val userService = mock<UserQueryService> {
onBlocking { findById(1L) } doReturn User(
id = 1L,
name = "test",
@ -125,7 +130,7 @@ class JwtServiceImplTest {
Base64Util.encode(generateKeyPair.public.encoded)
)
}
val jwtService = JwtServiceImpl(metaService, refreshTokenRepository, userService)
val jwtService = JwtServiceImpl(metaService, refreshTokenRepository, userService, jwtRefreshTokenQueryService)
val refreshToken = jwtService.refreshToken(RefreshToken("refreshToken"))
assertNotEquals("", refreshToken.token)
assertNotEquals("", refreshToken.refreshToken)
@ -147,16 +152,28 @@ class JwtServiceImplTest {
@Test
fun `refreshToken 無効なリフレッシュトークンは失敗する`() = runTest {
val refreshTokenRepository = mock<IJwtRefreshTokenRepository> {
onBlocking { findByToken("InvalidRefreshToken") } doReturn null
val refreshTokenRepository = mock<JwtRefreshTokenQueryService> {
onBlocking { findByToken("InvalidRefreshToken") } doThrow NoSuchElementException()
}
val jwtService = JwtServiceImpl(mock(), refreshTokenRepository, mock())
val kid = UUID.randomUUID()
val keyPairGenerator = KeyPairGenerator.getInstance("RSA")
keyPairGenerator.initialize(2048)
val generateKeyPair = keyPairGenerator.generateKeyPair()
val metaService = mock<IMetaService> {
onBlocking { getJwtMeta() } doReturn Jwt(
kid,
Base64Util.encode(generateKeyPair.private.encoded),
Base64Util.encode(generateKeyPair.public.encoded)
)
}
val jwtService = JwtServiceImpl(metaService, mock(), mock(), refreshTokenRepository)
assertThrows<InvalidRefreshTokenException> { jwtService.refreshToken(RefreshToken("InvalidRefreshToken")) }
}
@Test
fun `refreshToken 未来に作成されたリフレッシュトークンは失敗する`() = runTest {
val refreshTokenRepository = mock<IJwtRefreshTokenRepository> {
val refreshTokenRepository = mock<JwtRefreshTokenQueryService> {
onBlocking { findByToken("refreshToken") } doReturn JwtRefreshToken(
id = 1L,
userId = 1L,
@ -165,13 +182,25 @@ class JwtServiceImplTest {
expiresAt = Instant.now().plus(10, ChronoUnit.MINUTES).plus(14, ChronoUnit.DAYS)
)
}
val jwtService = JwtServiceImpl(mock(), refreshTokenRepository, mock())
val kid = UUID.randomUUID()
val keyPairGenerator = KeyPairGenerator.getInstance("RSA")
keyPairGenerator.initialize(2048)
val generateKeyPair = keyPairGenerator.generateKeyPair()
val metaService = mock<IMetaService> {
onBlocking { getJwtMeta() } doReturn Jwt(
kid,
Base64Util.encode(generateKeyPair.private.encoded),
Base64Util.encode(generateKeyPair.public.encoded)
)
}
val jwtService = JwtServiceImpl(metaService, mock(), mock(), refreshTokenRepository)
assertThrows<InvalidRefreshTokenException> { jwtService.refreshToken(RefreshToken("refreshToken")) }
}
@Test
fun `refreshToken 期限切れのリフレッシュトークンでは失敗する`() = runTest {
val refreshTokenRepository = mock<IJwtRefreshTokenRepository> {
val refreshTokenRepository = mock<JwtRefreshTokenQueryService> {
onBlocking { findByToken("refreshToken") } doReturn JwtRefreshToken(
id = 1L,
userId = 1L,
@ -180,7 +209,19 @@ class JwtServiceImplTest {
expiresAt = Instant.now().minus(16, ChronoUnit.DAYS)
)
}
val jwtService = JwtServiceImpl(mock(), refreshTokenRepository, mock())
val kid = UUID.randomUUID()
val keyPairGenerator = KeyPairGenerator.getInstance("RSA")
keyPairGenerator.initialize(2048)
val generateKeyPair = keyPairGenerator.generateKeyPair()
val metaService = mock<IMetaService> {
onBlocking { getJwtMeta() } doReturn Jwt(
kid,
Base64Util.encode(generateKeyPair.private.encoded),
Base64Util.encode(generateKeyPair.public.encoded)
)
}
val jwtService = JwtServiceImpl(metaService, mock(), mock(), refreshTokenRepository)
assertThrows<InvalidRefreshTokenException> { jwtService.refreshToken(RefreshToken("refreshToken")) }
}
}

View File

@ -11,6 +11,7 @@ import kotlinx.coroutines.test.runTest
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.assertThrows
import org.mockito.kotlin.*
import utils.TestTransaction
import java.util.*
import kotlin.test.assertEquals
@ -21,7 +22,7 @@ class MetaServiceImplTest {
val metaRepository = mock<IMetaRepository> {
onBlocking { get() } doReturn meta
}
val metaService = MetaServiceImpl(metaRepository)
val metaService = MetaServiceImpl(metaRepository, TestTransaction)
val actual = metaService.getMeta()
assertEquals(meta, actual)
}
@ -31,7 +32,7 @@ class MetaServiceImplTest {
val metaRepository = mock<IMetaRepository> {
onBlocking { get() } doReturn null
}
val metaService = MetaServiceImpl(metaRepository)
val metaService = MetaServiceImpl(metaRepository, TestTransaction)
assertThrows<NotInitException> { metaService.getMeta() }
}
@ -41,7 +42,7 @@ class MetaServiceImplTest {
val metaRepository = mock<IMetaRepository> {
onBlocking { save(any()) } doReturn Unit
}
val metaServiceImpl = MetaServiceImpl(metaRepository)
val metaServiceImpl = MetaServiceImpl(metaRepository, TestTransaction)
metaServiceImpl.updateMeta(meta)
argumentCaptor<Meta> {
verify(metaRepository).save(capture())
@ -55,7 +56,7 @@ class MetaServiceImplTest {
val metaRepository = mock<IMetaRepository> {
onBlocking { get() } doReturn meta
}
val metaService = MetaServiceImpl(metaRepository)
val metaService = MetaServiceImpl(metaRepository, TestTransaction)
val actual = metaService.getJwtMeta()
assertEquals(meta.jwt, actual)
}
@ -65,7 +66,7 @@ class MetaServiceImplTest {
val metaRepository = mock<IMetaRepository> {
onBlocking { get() } doReturn null
}
val metaService = MetaServiceImpl(metaRepository)
val metaService = MetaServiceImpl(metaRepository, TestTransaction)
assertThrows<NotInitException> { metaService.getJwtMeta() }
}
}

View File

@ -10,6 +10,7 @@ import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.runTest
import org.junit.jupiter.api.Test
import org.mockito.kotlin.*
import utils.TestTransaction
import java.util.*
import kotlin.test.assertEquals
@ -20,7 +21,7 @@ class ServerInitialiseServiceImplTest {
onBlocking { get() } doReturn null
onBlocking { save(any()) } doReturn Unit
}
val serverInitialiseServiceImpl = ServerInitialiseServiceImpl(metaRepository)
val serverInitialiseServiceImpl = ServerInitialiseServiceImpl(metaRepository, TestTransaction)
serverInitialiseServiceImpl.init()
verify(metaRepository, times(1)).save(any())
@ -32,7 +33,7 @@ class ServerInitialiseServiceImplTest {
val metaRepository = mock<IMetaRepository> {
onBlocking { get() } doReturn meta
}
val serverInitialiseServiceImpl = ServerInitialiseServiceImpl(metaRepository)
val serverInitialiseServiceImpl = ServerInitialiseServiceImpl(metaRepository, TestTransaction)
serverInitialiseServiceImpl.init()
verify(metaRepository, times(0)).save(any())
}
@ -45,7 +46,7 @@ class ServerInitialiseServiceImplTest {
onBlocking { save(any()) } doReturn Unit
}
val serverInitialiseServiceImpl = ServerInitialiseServiceImpl(metaRepository)
val serverInitialiseServiceImpl = ServerInitialiseServiceImpl(metaRepository, TestTransaction)
serverInitialiseServiceImpl.init()
verify(metaRepository, times(1)).save(any())
argumentCaptor<Meta> {

View File

@ -28,7 +28,7 @@ class UserServiceTest {
onBlocking { hash(anyString()) } doReturn "hashedPassword"
onBlocking { generateKeyPair() } doReturn generateKeyPair
}
val userService = UserService(userRepository, userAuthService, mock())
val userService = UserService(userRepository, userAuthService, mock(), mock(), mock())
userService.createLocalUser(UserCreateDto("test", "testUser", "XXXXXXXXXXXXX", "test"))
verify(userRepository, times(1)).save(any())
argumentCaptor<dev.usbharu.hideout.domain.model.hideout.entity.User> {
@ -54,7 +54,7 @@ class UserServiceTest {
val userRepository = mock<IUserRepository> {
onBlocking { nextId() } doReturn 113345L
}
val userService = UserService(userRepository, mock(), mock())
val userService = UserService(userRepository, mock(), mock(), mock(), mock())
val user = RemoteUserCreateDto(
"test",
"example.com",

View File

@ -1,28 +0,0 @@
package utils
import org.jetbrains.exposed.sql.Transaction
import org.jetbrains.exposed.sql.transactions.TransactionManager
import org.junit.jupiter.api.extension.*
class DBResetInterceptor : BeforeAllCallback, AfterAllCallback, BeforeEachCallback, AfterEachCallback {
private lateinit var transactionAll: Transaction
private lateinit var transactionEach: Transaction
override fun beforeAll(context: ExtensionContext?) {
transactionAll = TransactionManager.manager.newTransaction()
}
override fun afterAll(context: ExtensionContext?) {
transactionAll.rollback()
transactionAll.close()
}
override fun beforeEach(context: ExtensionContext?) {
transactionEach = TransactionManager.manager.newTransaction(outerTransaction = transactionAll)
}
override fun afterEach(context: ExtensionContext?) {
transactionEach.rollback()
transactionEach.close()
}
}

View File

@ -1,18 +0,0 @@
package utils
import org.jetbrains.exposed.sql.Database
import org.jetbrains.exposed.sql.DatabaseConfig
import org.junit.jupiter.api.extension.ExtendWith
@ExtendWith(DBResetInterceptor::class)
abstract class DatabaseTestBase {
companion object {
init {
Database.connect(
"jdbc:h2:mem:test;DB_CLOSE_DELAY=-1",
driver = "org.h2.Driver",
databaseConfig = DatabaseConfig { useNestedTransactions = true }
)
}
}
}

View File

@ -0,0 +1,7 @@
package utils
import dev.usbharu.hideout.service.core.Transaction
object TestTransaction : Transaction {
override suspend fun <T> transaction(block: suspend () -> T): T = block()
}