Merge pull request #11 from usbharu/feature/detekt

Feature/detekt
This commit is contained in:
usbharu 2023-04-30 02:02:15 +09:00 committed by GitHub
commit daf6acf4f8
76 changed files with 689 additions and 616 deletions

View File

@ -9,6 +9,7 @@ plugins {
kotlin("jvm") version "1.8.21" kotlin("jvm") version "1.8.21"
id("io.ktor.plugin") version "2.3.0" id("io.ktor.plugin") version "2.3.0"
id("org.graalvm.buildtools.native") version "0.9.21" id("org.graalvm.buildtools.native") version "0.9.21"
id("io.gitlab.arturbosch.detekt") version "1.22.0"
// id("org.jetbrains.kotlin.plugin.serialization") version "1.8.10" // id("org.jetbrains.kotlin.plugin.serialization") version "1.8.10"
} }
@ -88,6 +89,7 @@ dependencies {
testImplementation("org.slf4j:slf4j-simple:2.0.7") testImplementation("org.slf4j:slf4j-simple:2.0.7")
detektPlugins("io.gitlab.arturbosch.detekt:detekt-formatting:1.22.0")
} }
jib { jib {
@ -129,3 +131,11 @@ graalvmNative {
} }
} }
} }
detekt {
parallel = true
config = files("detekt.yml")
buildUponDefaultConfig = true
basePath = rootDir.absolutePath
autoCorrect = true
}

147
detekt.yml Normal file
View File

@ -0,0 +1,147 @@
build:
maxIssues: 20
style:
ClassOrdering:
active: true
MandatoryBracesIfStatements:
active: true
MandatoryBracesLoops:
active: true
MultilineLambdaItParameter:
active: false
UseEmptyCounterpart:
active: true
ExpressionBodySyntax:
active: true
WildcardImport:
active: false
ReturnCount:
active: false
MagicNumber:
ignorePropertyDeclaration: true
ForbiddenComment:
active: false
complexity:
CognitiveComplexMethod:
active: true
ComplexCondition:
active: true
ComplexInterface:
active: true
threshold: 30
LabeledExpression:
active: false
NamedArguments:
active: true
ignoreArgumentsMatchingNames: true
threshold: 5
NestedBlockDepth:
active: true
NestedScopeFunctions:
active: true
ReplaceSafeCallChainWithRun:
active: true
StringLiteralDuplication:
active: false
LongParameterList:
constructorThreshold: 10
TooManyFunctions:
ignoreDeprecated: true
ignoreOverridden: true
ignorePrivate: true
LongMethod:
active: true
excludes:
- "**/test/**"
exceptions:
ExceptionRaisedInUnexpectedLocation:
active: true
NotImplementedDeclaration:
active: true
ObjectExtendsThrowable:
active: true
ThrowingExceptionInMain:
active: true
ThrowingExceptionsWithoutMessageOrCause:
active: true
ThrowingNewInstanceOfSameException:
active: true
TooGenericExceptionCaught:
active: true
TooGenericExceptionThrown:
active: true
formatting:
Indentation:
indentSize: 4
NoWildcardImports:
active: false
naming:
FunctionMaxLength:
active: true
excludes:
- "**/test/**"
FunctionMinLength:
active: true
LambdaParameterNaming:
active: true
ConstructorParameterNaming:
excludes:
- "**/domain/model/ap/*"
ignoreOverridden: true
VariableNaming:
excludes:
- "**/domain/model/ap/*"
performance:
UnnecessaryPartOfBinaryExpression:
active: true
UnnecessaryTemporaryInstantiation:
active: true
potential-bugs:
CastToNullableType:
active: true
DontDowncastCollectionTypes:
active: true
ElseCaseInsteadOfExhaustiveWhen:
active: true

View File

@ -42,9 +42,9 @@ val Application.property: Application.(propertyName: String) -> String
environment.config.property(it).getString() environment.config.property(it).getString()
} }
@Suppress("unused") // application.conf references the main function. This annotation prevents the IDE from marking it as unused. // application.conf references the main function. This annotation prevents the IDE from marking it as unused.
@Suppress("unused")
fun Application.parent() { fun Application.parent() {
Config.configData = ConfigData( Config.configData = ConfigData(
url = property("hideout.url"), url = property("hideout.url"),
objectMapper = jacksonObjectMapper().enable(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY) objectMapper = jacksonObjectMapper().enable(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY)
@ -62,12 +62,12 @@ fun Application.parent() {
) )
} }
single<IUserRepository> { UserRepository(get(),get()) } single<IUserRepository> { UserRepository(get(), get()) }
single<IUserAuthService> { UserAuthService(get()) } single<IUserAuthService> { UserAuthService(get()) }
single<HttpSignatureVerifyService> { HttpSignatureVerifyServiceImpl(get()) } single<HttpSignatureVerifyService> { HttpSignatureVerifyServiceImpl(get()) }
single<JobQueueParentService> { single<JobQueueParentService> {
val kJobJobQueueService = KJobJobQueueParentService(get()) val kJobJobQueueService = KJobJobQueueParentService(get())
kJobJobQueueService.init(listOf()) kJobJobQueueService.init(emptyList())
kJobJobQueueService kJobJobQueueService
} }
single<HttpClient> { single<HttpClient> {
@ -83,15 +83,14 @@ fun Application.parent() {
} }
single<ActivityPubFollowService> { ActivityPubFollowServiceImpl(get(), get(), get(), get()) } single<ActivityPubFollowService> { ActivityPubFollowServiceImpl(get(), get(), get(), get()) }
single<ActivityPubService> { ActivityPubServiceImpl(get(), get()) } single<ActivityPubService> { ActivityPubServiceImpl(get(), get()) }
single<IUserService> { UserService(get(),get()) } single<IUserService> { UserService(get(), get()) }
single<ActivityPubUserService> { ActivityPubUserServiceImpl(get(), get()) } single<ActivityPubUserService> { ActivityPubUserServiceImpl(get(), get()) }
single<ActivityPubNoteService> { ActivityPubNoteServiceImpl(get(), get(), get()) } single<ActivityPubNoteService> { ActivityPubNoteServiceImpl(get(), get(), get()) }
single<IPostService> { PostService(get(), get()) } single<IPostService> { PostService(get(), get()) }
single<IPostRepository> { PostRepositoryImpl(get(), get()) } single<IPostRepository> { PostRepositoryImpl(get(), get()) }
single<IdGenerateService> {TwitterSnowflakeIdGenerateService} single<IdGenerateService> { TwitterSnowflakeIdGenerateService }
} }
configureKoin(module) configureKoin(module)
configureHTTP() configureHTTP()
configureStaticRouting() configureStaticRouting()
@ -120,7 +119,7 @@ fun Application.worker() {
activityPubService.processActivity(this, it) activityPubService.processActivity(this, it)
} }
} }
kJob.register(DeliverPostJob){ kJob.register(DeliverPostJob) {
execute { execute {
activityPubService.processActivity(this, it) activityPubService.processActivity(this, it)
} }

View File

@ -9,7 +9,7 @@ open class Accept : Object {
name: String, name: String,
`object`: Object?, `object`: Object?,
actor: String? actor: String?
) : super(add(type, "Accept"), name,actor) { ) : super(add(type, "Accept"), name, actor) {
this.`object` = `object` this.`object` = `object`
} }
@ -29,9 +29,5 @@ open class Accept : Object {
return result return result
} }
override fun toString(): String { override fun toString(): String = "Accept(`object`=$`object`, actor=$actor) ${super.toString()}"
return "Accept(`object`=$`object`, actor=$actor) ${super.toString()}"
}
} }

View File

@ -33,9 +33,5 @@ open class Create : Object {
return result return result
} }
override fun toString(): String { override fun toString(): String = "Create(`object`=$`object`) ${super.toString()}"
return "Create(`object`=$`object`) ${super.toString()}"
}
} }

View File

@ -9,9 +9,7 @@ open class Follow : Object {
name: String, name: String,
`object`: String?, `object`: String?,
actor: String? actor: String?
) : super(add(type, "Follow"), name,actor) { ) : super(add(type, "Follow"), name, actor) {
this.`object` = `object` this.`object` = `object`
} }
} }

View File

@ -28,6 +28,4 @@ open class Image : Object {
result = 31 * result + (url?.hashCode() ?: 0) result = 31 * result + (url?.hashCode() ?: 0)
return result return result
} }
} }

View File

@ -34,15 +34,9 @@ open class JsonLd {
return context == other.context return context == other.context
} }
override fun hashCode(): Int { override fun hashCode(): Int = context.hashCode()
return context.hashCode()
}
override fun toString(): String {
return "JsonLd(context=$context)"
}
override fun toString(): String = "JsonLd(context=$context)"
} }
class ContextDeserializer : JsonDeserializer<String>() { class ContextDeserializer : JsonDeserializer<String>() {
@ -60,10 +54,7 @@ class ContextDeserializer : JsonDeserializer<String>() {
class ContextSerializer : JsonSerializer<List<String>>() { class ContextSerializer : JsonSerializer<List<String>>() {
override fun isEmpty(value: List<String>?): Boolean = value.isNullOrEmpty()
override fun isEmpty(value: List<String>?): Boolean {
return value.isNullOrEmpty()
}
override fun serialize(value: List<String>?, gen: JsonGenerator?, serializers: SerializerProvider?) { override fun serialize(value: List<String>?, gen: JsonGenerator?, serializers: SerializerProvider?) {
if (value.isNullOrEmpty()) { if (value.isNullOrEmpty()) {
@ -80,5 +71,4 @@ class ContextSerializer : JsonSerializer<List<String>>() {
gen?.writeEndArray() gen?.writeEndArray()
} }
} }
} }

View File

@ -11,7 +11,7 @@ open class Key : Object {
id: String?, id: String?,
owner: String?, owner: String?,
publicKeyPem: String? publicKeyPem: String?
) : super(add(type, "Key"), name,id) { ) : super(add(type, "Key"), name, id) {
this.owner = owner this.owner = owner
this.publicKeyPem = publicKeyPem this.publicKeyPem = publicKeyPem
} }
@ -33,6 +33,4 @@ open class Key : Object {
result = 31 * result + (publicKeyPem?.hashCode() ?: 0) result = 31 * result + (publicKeyPem?.hashCode() ?: 0)
return result return result
} }
} }

View File

@ -48,9 +48,6 @@ open class Note : Object {
return result return result
} }
override fun toString(): String { override fun toString(): String =
return "Note(id=$id, attributedTo=$attributedTo, content=$content, published=$published, to=$to) ${super.toString()}" "Note(id=$id, attributedTo=$attributedTo, content=$content, published=$published, to=$to) ${super.toString()}"
}
} }

View File

@ -10,27 +10,16 @@ open class Object : JsonLd {
private var type: List<String> = emptyList() private var type: List<String> = emptyList()
var name: String? = null var name: String? = null
var actor: String? = null var actor: String? = null
var id:String? = null var id: String? = null
protected constructor() protected constructor()
constructor(type: List<String>, name: String? = null,actor:String? = null,id:String? = null) : super() { constructor(type: List<String>, name: String? = null, actor: String? = null, id: String? = null) : super() {
this.type = type this.type = type
this.name = name this.name = name
this.actor = actor this.actor = actor
this.id = id this.id = id
} }
companion object {
@JvmStatic
protected fun add(list: List<String>, type: String): List<String> {
val toMutableList = list.toMutableList()
toMutableList.add(type)
return toMutableList.distinct()
}
}
override fun equals(other: Any?): Boolean { override fun equals(other: Any?): Boolean {
if (this === other) return true if (this === other) return true
if (other !is Object) return false if (other !is Object) return false
@ -49,11 +38,16 @@ open class Object : JsonLd {
return result return result
} }
override fun toString(): String { override fun toString(): String = "Object(type=$type, name=$name, actor=$actor) ${super.toString()}"
return "Object(type=$type, name=$name, actor=$actor) ${super.toString()}"
companion object {
@JvmStatic
protected fun add(list: List<String>, type: String): List<String> {
val toMutableList = list.toMutableList()
toMutableList.add(type)
return toMutableList.distinct()
}
} }
} }
class TypeSerializer : JsonSerializer<List<String>>() { class TypeSerializer : JsonSerializer<List<String>>() {
@ -69,5 +63,4 @@ class TypeSerializer : JsonSerializer<List<String>>() {
gen?.writeEndArray() gen?.writeEndArray()
} }
} }
} }

View File

@ -10,6 +10,8 @@ open class Person : Object {
var publicKey: Key? = null var publicKey: Key? = null
protected constructor() : super() protected constructor() : super()
@Suppress("LongParameterList")
constructor( constructor(
type: List<String> = emptyList(), type: List<String> = emptyList(),
name: String, name: String,
@ -21,7 +23,7 @@ open class Person : Object {
url: String?, url: String?,
icon: Image?, icon: Image?,
publicKey: Key? publicKey: Key?
) : super(add(type, "Person"), name,id = id) { ) : super(add(type, "Person"), name, id = id) {
this.preferredUsername = preferredUsername this.preferredUsername = preferredUsername
this.summary = summary this.summary = summary
this.inbox = inbox this.inbox = inbox
@ -56,6 +58,4 @@ open class Person : Object {
result = 31 * result + (publicKey?.hashCode() ?: 0) result = 31 * result + (publicKey?.hashCode() ?: 0)
return result return result
} }
} }

View File

@ -1,12 +1,12 @@
package dev.usbharu.hideout.domain.model.hideout.dto package dev.usbharu.hideout.domain.model.hideout.dto
data class RemoteUserCreateDto( data class RemoteUserCreateDto(
val name:String, val name: String,
val domain:String, val domain: String,
val screenName:String, val screenName: String,
val description:String, val description: String,
val inbox:String, val inbox: String,
val outbox:String, val outbox: String,
val url:String, val url: String,
val publicKey:String, val publicKey: String,
) )

View File

@ -1,8 +1,8 @@
package dev.usbharu.hideout.domain.model.hideout.dto package dev.usbharu.hideout.domain.model.hideout.dto
data class UserCreateDto( data class UserCreateDto(
val name:String, val name: String,
val screenName:String, val screenName: String,
val description:String, val description: String,
val password:String val password: String
) )

View File

@ -61,9 +61,8 @@ val httpSignaturePlugin = createClientPlugin("HttpSign", ::HttpSignaturePluginCo
format.timeZone = TimeZone.getTimeZone("GMT") format.timeZone = TimeZone.getTimeZone("GMT")
onRequest { request, body -> onRequest { request, body ->
request.header("Date", format.format(Date())) request.header("Date", format.format(Date()))
request.header("Host", "${request.url.host}") request.header("Host", request.url.host)
println(request.bodyType) println(request.bodyType)
println(request.bodyType?.type) println(request.bodyType?.type)
if (request.bodyType?.type == String::class) { if (request.bodyType?.type == String::class) {
@ -72,7 +71,7 @@ val httpSignaturePlugin = createClientPlugin("HttpSign", ::HttpSignaturePluginCo
// UserAuthService.sha256.reset() // UserAuthService.sha256.reset()
val digest = val digest =
Base64.getEncoder().encodeToString(UserAuthService.sha256.digest(body.toByteArray(Charsets.UTF_8))) Base64.getEncoder().encodeToString(UserAuthService.sha256.digest(body.toByteArray(Charsets.UTF_8)))
request.headers.append("Digest", "sha-256=" + digest) request.headers.append("Digest", "sha-256=$digest")
} }
if (request.headers.contains("Signature")) { if (request.headers.contains("Signature")) {
@ -82,11 +81,21 @@ val httpSignaturePlugin = createClientPlugin("HttpSign", ::HttpSignaturePluginCo
s.split(",").forEach { parameters.add(it) } s.split(",").forEach { parameters.add(it) }
} }
val keyId = parameters.find { it.startsWith("keyId") }?.split("=")?.get(1)?.replace("\"", "") val keyId = parameters.find { it.startsWith("keyId") }
.orEmpty()
.split("=")[1]
.replace("\"", "")
val algorithm = val algorithm =
parameters.find { it.startsWith("algorithm") }?.split("=")?.get(1)?.replace("\"", "") parameters.find { it.startsWith("algorithm") }
val headers = parameters.find { it.startsWith("headers") }?.split("=")?.get(1)?.replace("\"", "") .orEmpty()
?.split(" ")?.toMutableList().orEmpty() .split("=")[1]
.replace("\"", "")
val headers = parameters.find { it.startsWith("headers") }
.orEmpty()
.split("=")[1]
.replace("\"", "")
.split(" ")
.toMutableList()
val algorithmType = when (algorithm) { val algorithmType = when (algorithm) {
"rsa-sha256" -> { "rsa-sha256" -> {
@ -132,32 +141,24 @@ val httpSignaturePlugin = createClientPlugin("HttpSign", ::HttpSignaturePluginCo
request.headers.remove("Signature") request.headers.remove("Signature")
signer!!.sign(object : HttpMessage, HttpRequest { signer!!.sign(object : HttpMessage, HttpRequest {
override fun headerValues(name: String?): MutableList<String> { override fun headerValues(name: String?): MutableList<String> =
return name?.let { request.headers.getAll(it) }?.toMutableList() ?: mutableListOf() name?.let { request.headers.getAll(it) }?.toMutableList() ?: mutableListOf()
}
override fun addHeader(name: String?, value: String?) { override fun addHeader(name: String?, value: String?) {
val split = value?.split("=").orEmpty() val split = value?.split("=").orEmpty()
name?.let { request.header(it, split.get(0) + "=\"" + split.get(1).trim('"') + "\"") } name?.let { request.header(it, split.get(0) + "=\"" + split.get(1).trim('"') + "\"") }
} }
override fun method(): String { override fun method(): String = request.method.value
return request.method.value
}
override fun uri(): URI {
return request.url.build().toURI()
}
override fun uri(): URI = request.url.build().toURI()
}) })
val signatureHeader = request.headers.getAll("Signature").orEmpty() val signatureHeader = request.headers.getAll("Signature").orEmpty()
request.headers.remove("Signature") request.headers.remove("Signature")
signatureHeader.map { it.replace("; ", ",").replace(";", ",") }.joinToString(",") signatureHeader.joinToString(",") { it.replace("; ", ",").replace(";", ",") }
.let { request.header("Signature", it) } .let { request.header("Signature", it) }
} }
} }
} }
@ -167,7 +168,8 @@ class KtorKeyMap(private val userAuthRepository: IUserRepository) : KeyMap {
.substringAfterLast("/") .substringAfterLast("/")
val publicBytes = Base64.getDecoder().decode( val publicBytes = Base64.getDecoder().decode(
userAuthRepository.findByNameAndDomain( userAuthRepository.findByNameAndDomain(
username, Config.configData.domain username,
Config.configData.domain
)?.publicKey?.replace("-----BEGIN PUBLIC KEY-----", "-----END PUBLIC KEY-----")?.replace("", "") )?.publicKey?.replace("-----BEGIN PUBLIC KEY-----", "-----END PUBLIC KEY-----")?.replace("", "")
?.replace("\n", "") ?.replace("\n", "")
) )
@ -180,7 +182,8 @@ class KtorKeyMap(private val userAuthRepository: IUserRepository) : KeyMap {
.substringAfterLast("/") .substringAfterLast("/")
val publicBytes = Base64.getDecoder().decode( val publicBytes = Base64.getDecoder().decode(
userAuthRepository.findByNameAndDomain( userAuthRepository.findByNameAndDomain(
username, Config.configData.domain username,
Config.configData.domain
)?.privateKey?.replace("-----BEGIN PRIVATE KEY-----", "")?.replace("-----END PRIVATE KEY-----", "") )?.privateKey?.replace("-----BEGIN PRIVATE KEY-----", "")?.replace("-----END PRIVATE KEY-----", "")
?.replace("\n", "") ?.replace("\n", "")
) )
@ -188,7 +191,6 @@ class KtorKeyMap(private val userAuthRepository: IUserRepository) : KeyMap {
return@runBlocking KeyFactory.getInstance("RSA").generatePrivate(x509EncodedKeySpec) return@runBlocking KeyFactory.getInstance("RSA").generatePrivate(x509EncodedKeySpec)
} }
override fun getSecretKey(keyId: String?): SecretKey { @Suppress("NotImplementedDeclaration")
TODO("Not yet implemented") override fun getSecretKey(keyId: String?): SecretKey = TODO("Not yet implemented")
}
} }

View File

@ -31,6 +31,5 @@ fun Application.configureRouting(
route("/api/v1") { route("/api/v1") {
statuses(postService) statuses(postService)
} }
} }
} }

View File

@ -1,16 +1,16 @@
@file:Suppress("UnusedPrivateMember")
package dev.usbharu.hideout.plugins package dev.usbharu.hideout.plugins
import dev.usbharu.hideout.service.IUserAuthService import dev.usbharu.hideout.service.IUserAuthService
import io.ktor.server.application.* import io.ktor.server.application.*
import io.ktor.server.auth.* import io.ktor.server.auth.*
data class UserSession(val username: String) : Principal const val TOKEN_AUTH = "token-auth"
const val tokenAuth = "token-auth"
fun Application.configureSecurity(userAuthService: IUserAuthService) { fun Application.configureSecurity(userAuthService: IUserAuthService) {
install(Authentication) { install(Authentication) {
bearer(tokenAuth) { bearer(TOKEN_AUTH) {
authenticate { bearerTokenCredential -> authenticate { bearerTokenCredential ->
UserIdPrincipal(bearerTokenCredential.token) UserIdPrincipal(bearerTokenCredential.token)
} }

View File

@ -4,7 +4,7 @@ import dev.usbharu.hideout.domain.model.Post
import dev.usbharu.hideout.domain.model.PostEntity import dev.usbharu.hideout.domain.model.PostEntity
interface IPostRepository { interface IPostRepository {
suspend fun insert(post:Post):PostEntity suspend fun insert(post: Post): PostEntity
suspend fun findOneById(id:Long):PostEntity suspend fun findOneById(id: Long): PostEntity
suspend fun delete(id:Long) suspend fun delete(id: Long)
} }

View File

@ -2,6 +2,7 @@ package dev.usbharu.hideout.repository
import dev.usbharu.hideout.domain.model.hideout.entity.User import dev.usbharu.hideout.domain.model.hideout.entity.User
@Suppress("TooManyFunctions")
interface IUserRepository { interface IUserRepository {
suspend fun save(user: User): User suspend fun save(user: User): User
@ -13,11 +14,11 @@ interface IUserRepository {
suspend fun findByNameAndDomain(name: String, domain: String): User? suspend fun findByNameAndDomain(name: String, domain: String): User?
suspend fun findByDomain(domain:String): List<User> suspend fun findByDomain(domain: String): List<User>
suspend fun findByNameAndDomains(names: List<Pair<String,String>>): List<User> suspend fun findByNameAndDomains(names: List<Pair<String, String>>): List<User>
suspend fun findByUrl(url:String): User? suspend fun findByUrl(url: String): User?
suspend fun findByUrls(urls: List<String>): List<User> suspend fun findByUrls(urls: List<String>): List<User>
@ -34,5 +35,5 @@ interface IUserRepository {
suspend fun deleteFollower(id: Long, follower: Long) suspend fun deleteFollower(id: Long, follower: Long)
suspend fun findFollowersById(id: Long): List<User> suspend fun findFollowersById(id: Long): List<User>
suspend fun nextId():Long suspend fun nextId(): Long
} }

View File

@ -1,7 +1,10 @@
package dev.usbharu.hideout.repository package dev.usbharu.hideout.repository
import dev.usbharu.hideout.config.Config import dev.usbharu.hideout.config.Config
import dev.usbharu.hideout.domain.model.* import dev.usbharu.hideout.domain.model.Post
import dev.usbharu.hideout.domain.model.PostEntity
import dev.usbharu.hideout.domain.model.Posts
import dev.usbharu.hideout.domain.model.toPost
import dev.usbharu.hideout.service.IdGenerateService import dev.usbharu.hideout.service.IdGenerateService
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import org.jetbrains.exposed.sql.* import org.jetbrains.exposed.sql.*
@ -22,7 +25,6 @@ class PostRepositoryImpl(database: Database, private val idGenerateService: IdGe
override suspend fun insert(post: Post): PostEntity { override suspend fun insert(post: Post): PostEntity {
return query { return query {
val generateId = idGenerateService.generateId() val generateId = idGenerateService.generateId()
val name = Users.select { Users.id eq post.userId }.single().toUser().name val name = Users.select { Users.id eq post.userId }.single().toUser().name
val postUrl = Config.configData.url + "/users/$name/posts/$generateId" val postUrl = Config.configData.url + "/users/$name/posts/$generateId"
@ -38,15 +40,15 @@ class PostRepositoryImpl(database: Database, private val idGenerateService: IdGe
it[replyId] = post.replyId it[replyId] = post.replyId
} }
return@query PostEntity( return@query PostEntity(
generateId, id = generateId,
post.userId, userId = post.userId,
post.overview, overview = post.overview,
post.text, text = post.text,
post.createdAt, createdAt = post.createdAt,
post.visibility, visibility = post.visibility,
postUrl, url = postUrl,
post.repostId, repostId = post.repostId,
post.replyId replyId = post.replyId
) )
} }
} }

View File

@ -136,13 +136,13 @@ class UserRepository(private val database: Database, private val idGenerateServi
Users.innerJoin( Users.innerJoin(
otherTable = UsersFollowers, otherTable = UsersFollowers,
onColumn = { Users.id }, onColumn = { Users.id },
otherColumn = { userId }) otherColumn = { userId }
)
.innerJoin( .innerJoin(
otherTable = followers, otherTable = followers,
onColumn = { UsersFollowers.followerId }, onColumn = { UsersFollowers.followerId },
otherColumn = { followers[Users.id] }) otherColumn = { followers[Users.id] }
)
.slice( .slice(
followers.get(Users.id), followers.get(Users.id),
followers.get(Users.name), followers.get(Users.name),
@ -177,7 +177,6 @@ class UserRepository(private val database: Database, private val idGenerateServi
} }
} }
override suspend fun delete(id: Long) { override suspend fun delete(id: Long) {
query { query {
Users.deleteWhere { Users.id.eq(id) } Users.deleteWhere { Users.id.eq(id) }
@ -202,9 +201,7 @@ class UserRepository(private val database: Database, private val idGenerateServi
} }
} }
override suspend fun nextId(): Long { override suspend fun nextId(): Long = idGenerateService.generateId()
return idGenerateService.generateId()
}
} }
object Users : Table("users") { object Users : Table("users") {
@ -230,18 +227,18 @@ object Users : Table("users") {
fun ResultRow.toUser(): User { fun ResultRow.toUser(): User {
return User( return User(
this[Users.id], id = this[Users.id],
this[Users.name], name = this[Users.name],
this[Users.domain], domain = this[Users.domain],
this[Users.screenName], screenName = this[Users.screenName],
this[Users.description], description = this[Users.description],
this[Users.password], password = this[Users.password],
this[Users.inbox], inbox = this[Users.inbox],
this[Users.outbox], outbox = this[Users.outbox],
this[Users.url], url = this[Users.url],
this[Users.publicKey], publicKey = this[Users.publicKey],
this[Users.privateKey], privateKey = this[Users.privateKey],
Instant.ofEpochMilli((this[Users.createdAt])) createdAt = Instant.ofEpochMilli((this[Users.createdAt]))
) )
} }

View File

@ -10,7 +10,6 @@ import io.ktor.server.response.*
import io.ktor.server.routing.* import io.ktor.server.routing.*
fun Application.register(userService: IUserService) { fun Application.register(userService: IUserService) {
routing { routing {
get("/register") { get("/register") {
val principal = call.principal<UserIdPrincipal>() val principal = call.principal<UserIdPrincipal>()

View File

@ -14,8 +14,7 @@ import io.ktor.server.routing.*
fun Routing.inbox( fun Routing.inbox(
httpSignatureVerifyService: HttpSignatureVerifyService, httpSignatureVerifyService: HttpSignatureVerifyService,
activityPubService: dev.usbharu.hideout.service.activitypub.ActivityPubService activityPubService: dev.usbharu.hideout.service.activitypub.ActivityPubService
){ ) {
route("/inbox") { route("/inbox") {
get { get {
call.respond(HttpStatusCode.MethodNotAllowed) call.respond(HttpStatusCode.MethodNotAllowed)
@ -32,17 +31,20 @@ fun Routing.inbox(
when (response) { when (response) {
is ActivityPubObjectResponse -> call.respond( is ActivityPubObjectResponse -> call.respond(
response.httpStatusCode, response.httpStatusCode,
Config.configData.objectMapper.writeValueAsString(response.message.apply { Config.configData.objectMapper.writeValueAsString(
response.message.apply {
context = context =
listOf("https://www.w3.org/ns/activitystreams") listOf("https://www.w3.org/ns/activitystreams")
}) }
) )
)
is ActivityPubStringResponse -> call.respond(response.httpStatusCode, response.message) is ActivityPubStringResponse -> call.respond(response.httpStatusCode, response.message)
null -> call.respond(HttpStatusCode.NotImplemented) null -> call.respond(HttpStatusCode.NotImplemented)
} }
} }
} }
route("/users/{name}/inbox"){ route("/users/{name}/inbox") {
get { get {
call.respond(HttpStatusCode.MethodNotAllowed) call.respond(HttpStatusCode.MethodNotAllowed)
} }
@ -58,15 +60,17 @@ fun Routing.inbox(
when (response) { when (response) {
is ActivityPubObjectResponse -> call.respond( is ActivityPubObjectResponse -> call.respond(
response.httpStatusCode, response.httpStatusCode,
Config.configData.objectMapper.writeValueAsString(response.message.apply { Config.configData.objectMapper.writeValueAsString(
response.message.apply {
context = context =
listOf("https://www.w3.org/ns/activitystreams") listOf("https://www.w3.org/ns/activitystreams")
}) }
) )
)
is ActivityPubStringResponse -> call.respond(response.httpStatusCode, response.message) is ActivityPubStringResponse -> call.respond(response.httpStatusCode, response.message)
null -> call.respond(HttpStatusCode.NotImplemented) null -> call.respond(HttpStatusCode.NotImplemented)
} }
} }
} }
} }

View File

@ -6,7 +6,6 @@ import io.ktor.server.response.*
import io.ktor.server.routing.* import io.ktor.server.routing.*
fun Routing.outbox() { fun Routing.outbox() {
route("/outbox") { route("/outbox") {
get { get {
call.respond(HttpStatusCode.NotImplemented) call.respond(HttpStatusCode.NotImplemented)
@ -15,7 +14,7 @@ fun Routing.outbox() {
call.respond(HttpStatusCode.NotImplemented) call.respond(HttpStatusCode.NotImplemented)
} }
} }
route("/users/{name}/outbox"){ route("/users/{name}/outbox") {
get { get {
call.respond(HttpStatusCode.NotImplemented) call.respond(HttpStatusCode.NotImplemented)
} }
@ -23,5 +22,4 @@ fun Routing.outbox() {
call.respond(HttpStatusCode.NotImplemented) call.respond(HttpStatusCode.NotImplemented)
} }
} }
} }

View File

@ -34,7 +34,6 @@ fun Routing.usersAP(activityPubUserService: ActivityPubUserService, userService:
class ContentTypeRouteSelector(private vararg val contentType: ContentType) : RouteSelector() { class ContentTypeRouteSelector(private vararg val contentType: ContentType) : RouteSelector() {
override fun evaluate(context: RoutingResolveContext, segmentIndex: Int): RouteSelectorEvaluation { override fun evaluate(context: RoutingResolveContext, segmentIndex: Int): RouteSelectorEvaluation {
context.call.application.log.debug("Accept: ${context.call.request.accept()}") context.call.application.log.debug("Accept: ${context.call.request.accept()}")
val requestContentType = context.call.request.accept() ?: return RouteSelectorEvaluation.FailedParameter val requestContentType = context.call.request.accept() ?: return RouteSelectorEvaluation.FailedParameter
return if (requestContentType.split(",") return if (requestContentType.split(",")
@ -45,5 +44,4 @@ class ContentTypeRouteSelector(private vararg val contentType: ContentType) : Ro
RouteSelectorEvaluation.FailedParameter RouteSelectorEvaluation.FailedParameter
} }
} }
} }

View File

@ -3,7 +3,6 @@ package dev.usbharu.hideout.routing.api.v1
import dev.usbharu.hideout.domain.model.Post import dev.usbharu.hideout.domain.model.Post
import dev.usbharu.hideout.domain.model.api.StatusForPost import dev.usbharu.hideout.domain.model.api.StatusForPost
import dev.usbharu.hideout.service.IPostService import dev.usbharu.hideout.service.IPostService
import dev.usbharu.hideout.service.impl.PostService
import io.ktor.server.application.* import io.ktor.server.application.*
import io.ktor.server.request.* import io.ktor.server.request.*
import io.ktor.server.response.* import io.ktor.server.response.*

View File

@ -11,8 +11,8 @@ import io.ktor.server.application.*
import io.ktor.server.response.* import io.ktor.server.response.*
import io.ktor.server.routing.* import io.ktor.server.routing.*
fun Routing.webfinger(userService: IUserService){ fun Routing.webfinger(userService: IUserService) {
route("/.well-known/webfinger"){ route("/.well-known/webfinger") {
get { get {
val acct = call.request.queryParameters["resource"]?.decodeURLPart() val acct = call.request.queryParameters["resource"]?.decodeURLPart()
?: throw ParameterNotExistException("Parameter(name='resource') does not exist.") ?: throw ParameterNotExistException("Parameter(name='resource') does not exist.")

View File

@ -3,5 +3,5 @@ package dev.usbharu.hideout.service
import dev.usbharu.hideout.domain.model.Post import dev.usbharu.hideout.domain.model.Post
interface IPostService { interface IPostService {
suspend fun create(post:Post) suspend fun create(post: Post)
} }

View File

@ -7,8 +7,7 @@ interface IUserAuthService {
suspend fun usernameAlreadyUse(username: String): Boolean suspend fun usernameAlreadyUse(username: String): Boolean
suspend fun generateKeyPair():KeyPair suspend fun generateKeyPair(): KeyPair
suspend fun verifyAccount(username: String, password: String): Boolean suspend fun verifyAccount(username: String, password: String): Boolean
} }

View File

@ -1,5 +1,5 @@
package dev.usbharu.hideout.service package dev.usbharu.hideout.service
interface IdGenerateService { interface IdGenerateService {
suspend fun generateId():Long suspend fun generateId(): Long
} }

View File

@ -5,7 +5,8 @@ import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock import kotlinx.coroutines.sync.withLock
import java.time.Instant import java.time.Instant
open class SnowflakeIdGenerateService(private val baseTime:Long) : IdGenerateService { @Suppress("MagicNumber")
open class SnowflakeIdGenerateService(private val baseTime: Long) : IdGenerateService {
var lastTimeStamp: Long = -1 var lastTimeStamp: Long = -1
var sequenceId: Int = 0 var sequenceId: Int = 0
val mutex = Mutex() val mutex = Mutex()
@ -13,22 +14,15 @@ open class SnowflakeIdGenerateService(private val baseTime:Long) : IdGenerateSer
@Throws(IllegalStateException::class) @Throws(IllegalStateException::class)
override suspend fun generateId(): Long { override suspend fun generateId(): Long {
return mutex.withLock { return mutex.withLock {
var timestamp = getTime() var timestamp = getTime()
if (timestamp < lastTimeStamp) { if (timestamp < lastTimeStamp) {
while (timestamp <= lastTimeStamp) { timestamp = wait(timestamp)
delay(1L)
timestamp = getTime()
}
// throw IllegalStateException(" $lastTimeStamp $timestamp ${lastTimeStamp-timestamp} ") // throw IllegalStateException(" $lastTimeStamp $timestamp ${lastTimeStamp-timestamp} ")
} }
if (timestamp == lastTimeStamp) { if (timestamp == lastTimeStamp) {
sequenceId++ sequenceId++
if (sequenceId >= 4096) { if (sequenceId >= 4096) {
while (timestamp <= lastTimeStamp) { timestamp = wait(timestamp)
delay(1L)
timestamp = getTime()
}
sequenceId = 0 sequenceId = 0
} }
} else { } else {
@ -37,11 +31,16 @@ open class SnowflakeIdGenerateService(private val baseTime:Long) : IdGenerateSer
lastTimeStamp = timestamp lastTimeStamp = timestamp
return@withLock (timestamp - baseTime).shl(22).or(1L.shl(12)).or(sequenceId.toLong()) return@withLock (timestamp - baseTime).shl(22).or(1L.shl(12)).or(sequenceId.toLong())
} }
} }
private fun getTime(): Long { private suspend fun wait(timestamp: Long): Long {
return Instant.now().toEpochMilli() var timestamp1 = timestamp
while (timestamp1 <= lastTimeStamp) {
delay(1L)
timestamp1 = getTime()
} }
return timestamp1
}
private fun getTime(): Long = Instant.now().toEpochMilli()
} }

View File

@ -1,4 +1,5 @@
package dev.usbharu.hideout.service package dev.usbharu.hideout.service
// 2010-11-04T01:42:54.657 // 2010-11-04T01:42:54.657
@Suppress("MagicNumber")
object TwitterSnowflakeIdGenerateService : SnowflakeIdGenerateService(1288834974657L) object TwitterSnowflakeIdGenerateService : SnowflakeIdGenerateService(1288834974657L)

View File

@ -1,11 +1,11 @@
package dev.usbharu.hideout.service.activitypub package dev.usbharu.hideout.service.activitypub
import dev.usbharu.hideout.domain.model.ap.Follow
import dev.usbharu.hideout.domain.model.ActivityPubResponse import dev.usbharu.hideout.domain.model.ActivityPubResponse
import dev.usbharu.hideout.domain.model.ap.Follow
import dev.usbharu.hideout.domain.model.job.ReceiveFollowJob import dev.usbharu.hideout.domain.model.job.ReceiveFollowJob
import kjob.core.job.JobProps import kjob.core.job.JobProps
interface ActivityPubFollowService { interface ActivityPubFollowService {
suspend fun receiveFollow(follow: Follow):ActivityPubResponse suspend fun receiveFollow(follow: Follow): ActivityPubResponse
suspend fun receiveFollowJob(props: JobProps<ReceiveFollowJob>) suspend fun receiveFollowJob(props: JobProps<ReceiveFollowJob>)
} }

View File

@ -33,7 +33,7 @@ class ActivityPubFollowServiceImpl(
override suspend fun receiveFollowJob(props: JobProps<ReceiveFollowJob>) { override suspend fun receiveFollowJob(props: JobProps<ReceiveFollowJob>) {
val actor = props[ReceiveFollowJob.actor] val actor = props[ReceiveFollowJob.actor]
val targetActor = props[ReceiveFollowJob.targetActor] val targetActor = props[ReceiveFollowJob.targetActor]
val person = activityPubUserService.fetchPerson(actor,targetActor) val person = activityPubUserService.fetchPerson(actor, targetActor)
val follow = Config.configData.objectMapper.readValue<Follow>(props[ReceiveFollowJob.follow]) val follow = Config.configData.objectMapper.readValue<Follow>(props[ReceiveFollowJob.follow])
httpClient.postAp( httpClient.postAp(
urlString = person.inbox ?: throw IllegalArgumentException("inbox is not found"), urlString = person.inbox ?: throw IllegalArgumentException("inbox is not found"),

View File

@ -6,6 +6,6 @@ import kjob.core.job.JobProps
interface ActivityPubNoteService { interface ActivityPubNoteService {
suspend fun createNote(post:PostEntity) suspend fun createNote(post: PostEntity)
suspend fun createNoteJob(props:JobProps<DeliverPostJob>) suspend fun createNoteJob(props: JobProps<DeliverPostJob>)
} }

View File

@ -35,7 +35,6 @@ class ActivityPubNoteServiceImpl(
} }
} }
override suspend fun createNoteJob(props: JobProps<DeliverPostJob>) { override suspend fun createNoteJob(props: JobProps<DeliverPostJob>) {
val actor = props[DeliverPostJob.actor] val actor = props[DeliverPostJob.actor]
val postEntity = Config.configData.objectMapper.readValue<PostEntity>(props[DeliverPostJob.post]) val postEntity = Config.configData.objectMapper.readValue<PostEntity>(props[DeliverPostJob.post])

View File

@ -9,7 +9,7 @@ interface ActivityPubService {
suspend fun processActivity(json: String, type: ActivityType): ActivityPubResponse? suspend fun processActivity(json: String, type: ActivityType): ActivityPubResponse?
suspend fun <T : HideoutJob> processActivity(job: JobContextWithProps<T>,hideoutJob: HideoutJob) suspend fun <T : HideoutJob> processActivity(job: JobContextWithProps<T>, hideoutJob: HideoutJob)
} }
enum class ActivityType { enum class ActivityType {

View File

@ -10,6 +10,7 @@ import dev.usbharu.hideout.domain.model.job.ReceiveFollowJob
import dev.usbharu.hideout.exception.JsonParseException import dev.usbharu.hideout.exception.JsonParseException
import kjob.core.dsl.JobContextWithProps import kjob.core.dsl.JobContextWithProps
import kjob.core.job.JobProps import kjob.core.job.JobProps
import org.slf4j.Logger
import org.slf4j.LoggerFactory import org.slf4j.LoggerFactory
class ActivityPubServiceImpl( class ActivityPubServiceImpl(
@ -17,7 +18,7 @@ class ActivityPubServiceImpl(
private val activityPubNoteService: ActivityPubNoteService private val activityPubNoteService: ActivityPubNoteService
) : ActivityPubService { ) : ActivityPubService {
val logger = LoggerFactory.getLogger(this::class.java) val logger: Logger = LoggerFactory.getLogger(this::class.java)
override fun parseActivity(json: String): ActivityType { override fun parseActivity(json: String): ActivityType {
val readTree = Config.configData.objectMapper.readTree(json) val readTree = Config.configData.objectMapper.readTree(json)
logger.debug("readTree: {}", readTree) logger.debug("readTree: {}", readTree)
@ -33,6 +34,7 @@ class ActivityPubServiceImpl(
return ActivityType.values().first { it.name.equals(type.asText(), true) } return ActivityType.values().first { it.name.equals(type.asText(), true) }
} }
@Suppress("CyclomaticComplexMethod", "NotImplementedDeclaration")
override suspend fun processActivity(json: String, type: ActivityType): ActivityPubResponse { override suspend fun processActivity(json: String, type: ActivityType): ActivityPubResponse {
return when (type) { return when (type) {
ActivityType.Accept -> TODO() ActivityType.Accept -> TODO()
@ -80,6 +82,4 @@ class ActivityPubServiceImpl(
DeliverPostJob -> activityPubNoteService.createNoteJob(job.props as JobProps<DeliverPostJob>) DeliverPostJob -> activityPubNoteService.createNoteJob(job.props as JobProps<DeliverPostJob>)
} }
} }
} }

View File

@ -3,7 +3,7 @@ package dev.usbharu.hideout.service.activitypub
import dev.usbharu.hideout.domain.model.ap.Person import dev.usbharu.hideout.domain.model.ap.Person
interface ActivityPubUserService { interface ActivityPubUserService {
suspend fun getPersonByName(name:String): Person suspend fun getPersonByName(name: String): Person
suspend fun fetchPerson(url: String, targetActor: String? = null): Person suspend fun fetchPerson(url: String, targetActor: String? = null): Person
} }

View File

@ -15,7 +15,6 @@ import io.ktor.client.*
import io.ktor.client.request.* import io.ktor.client.request.*
import io.ktor.client.statement.* import io.ktor.client.statement.*
import io.ktor.http.* import io.ktor.http.*
import org.slf4j.LoggerFactory
class ActivityPubUserServiceImpl( class ActivityPubUserServiceImpl(
private val userService: IUserService, private val userService: IUserService,
@ -23,7 +22,6 @@ class ActivityPubUserServiceImpl(
) : ) :
ActivityPubUserService { ActivityPubUserService {
private val logger = LoggerFactory.getLogger(this::class.java)
override suspend fun getPersonByName(name: String): Person { override suspend fun getPersonByName(name: String): Person {
// TODO: JOINで書き直し // TODO: JOINで書き直し
val userEntity = userService.findByNameLocalUser(name) val userEntity = userService.findByNameLocalUser(name)
@ -79,11 +77,10 @@ class ActivityPubUserServiceImpl(
publicKeyPem = userEntity.publicKey publicKeyPem = userEntity.publicKey
) )
) )
} catch (e: UserNotFoundException) { } catch (e: UserNotFoundException) {
val httpResponse = if (targetActor != null) { val httpResponse = if (targetActor != null) {
httpClient.getAp(url,"$targetActor#pubkey") httpClient.getAp(url, "$targetActor#pubkey")
}else { } else {
httpClient.get(url) { httpClient.get(url) {
accept(ContentType.Application.Activity) accept(ContentType.Application.Activity)
} }
@ -95,16 +92,17 @@ class ActivityPubUserServiceImpl(
name = person.preferredUsername name = person.preferredUsername
?: throw IllegalActivityPubObjectException("preferredUsername is null"), ?: throw IllegalActivityPubObjectException("preferredUsername is null"),
domain = url.substringAfter("://").substringBeforeLast("/"), domain = url.substringAfter("://").substringBeforeLast("/"),
screenName = (person.name ?: person.preferredUsername) ?: throw IllegalActivityPubObjectException("preferredUsername is null"), screenName = (person.name ?: person.preferredUsername)
description = person.summary ?: "", ?: throw IllegalActivityPubObjectException("preferredUsername is null"),
description = person.summary.orEmpty(),
inbox = person.inbox ?: throw IllegalActivityPubObjectException("inbox is null"), inbox = person.inbox ?: throw IllegalActivityPubObjectException("inbox is null"),
outbox = person.outbox ?: throw IllegalActivityPubObjectException("outbox is null"), outbox = person.outbox ?: throw IllegalActivityPubObjectException("outbox is null"),
url = url, url = url,
publicKey = person.publicKey?.publicKeyPem ?: throw IllegalActivityPubObjectException("publicKey is null"), publicKey = person.publicKey?.publicKeyPem
?: throw IllegalActivityPubObjectException("publicKey is null"),
) )
) )
person person
} }
} }
} }

View File

@ -4,6 +4,7 @@ import dev.usbharu.hideout.domain.model.hideout.dto.RemoteUserCreateDto
import dev.usbharu.hideout.domain.model.hideout.dto.UserCreateDto import dev.usbharu.hideout.domain.model.hideout.dto.UserCreateDto
import dev.usbharu.hideout.domain.model.hideout.entity.User import dev.usbharu.hideout.domain.model.hideout.entity.User
@Suppress("TooManyFunctions")
interface IUserService { interface IUserService {
suspend fun findAll(limit: Int? = 100, offset: Long? = 0): List<User> suspend fun findAll(limit: Int? = 100, offset: Long? = 0): List<User>

View File

@ -4,14 +4,17 @@ import dev.usbharu.hideout.domain.model.Post
import dev.usbharu.hideout.repository.IPostRepository import dev.usbharu.hideout.repository.IPostRepository
import dev.usbharu.hideout.service.IPostService import dev.usbharu.hideout.service.IPostService
import dev.usbharu.hideout.service.activitypub.ActivityPubNoteService import dev.usbharu.hideout.service.activitypub.ActivityPubNoteService
import dev.usbharu.hideout.service.job.JobQueueParentService
import org.slf4j.LoggerFactory import org.slf4j.LoggerFactory
class PostService(private val postRepository:IPostRepository,private val activityPubNoteService: ActivityPubNoteService) : IPostService { class PostService(
private val postRepository: IPostRepository,
private val activityPubNoteService: ActivityPubNoteService
) : IPostService {
private val logger = LoggerFactory.getLogger(this::class.java) private val logger = LoggerFactory.getLogger(this::class.java)
override suspend fun create(post: Post) { override suspend fun create(post: Post) {
logger.debug("create post={}",post) logger.debug("create post={}", post)
val postEntity = postRepository.insert(post) val postEntity = postRepository.insert(post)
activityPubNoteService.createNote(postEntity) activityPubNoteService.createNote(postEntity)
} }

View File

@ -12,14 +12,13 @@ class UserAuthService(
val userRepository: IUserRepository val userRepository: IUserRepository
) : IUserAuthService { ) : IUserAuthService {
override fun hash(password: String): String { override fun hash(password: String): String {
val digest = sha256.digest(password.toByteArray(Charsets.UTF_8)) val digest = sha256.digest(password.toByteArray(Charsets.UTF_8))
return hex(digest) return hex(digest)
} }
override suspend fun usernameAlreadyUse(username: String): Boolean { override suspend fun usernameAlreadyUse(username: String): Boolean {
userRepository.findByName(username) ?: return false userRepository.findByName(username)
return true return true
} }
@ -31,24 +30,25 @@ class UserAuthService(
override suspend fun generateKeyPair(): KeyPair { override suspend fun generateKeyPair(): KeyPair {
val keyPairGenerator = KeyPairGenerator.getInstance("RSA") val keyPairGenerator = KeyPairGenerator.getInstance("RSA")
keyPairGenerator.initialize(2048) keyPairGenerator.initialize(keySize)
return keyPairGenerator.generateKeyPair() return keyPairGenerator.generateKeyPair()
} }
companion object { companion object {
val sha256: MessageDigest = MessageDigest.getInstance("SHA-256") val sha256: MessageDigest = MessageDigest.getInstance("SHA-256")
const val keySize = 2048
const val pemSize = 64
} }
} }
fun PublicKey.toPem(): String { fun PublicKey.toPem(): String {
return "-----BEGIN PUBLIC KEY-----\n" + return "-----BEGIN PUBLIC KEY-----\n" +
Base64.getEncoder().encodeToString(encoded).chunked(64).joinToString("\n") + Base64.getEncoder().encodeToString(encoded).chunked(UserAuthService.pemSize).joinToString("\n") +
"\n-----END PUBLIC KEY-----\n" "\n-----END PUBLIC KEY-----\n"
} }
fun PrivateKey.toPem(): String { fun PrivateKey.toPem(): String {
return "-----BEGIN PRIVATE KEY-----\n" + return "-----BEGIN PRIVATE KEY-----\n" +
Base64.getEncoder().encodeToString(encoded).chunked(64).joinToString("\n") + Base64.getEncoder().encodeToString(encoded).chunked(UserAuthService.pemSize).joinToString("\n") +
"\n-----END PRIVATE KEY-----\n" "\n-----END PRIVATE KEY-----\n"
} }

View File

@ -15,41 +15,31 @@ class UserService(private val userRepository: IUserRepository, private val userA
private val maxLimit = 100 private val maxLimit = 100
override suspend fun findAll(limit: Int?, offset: Long?): List<User> { override suspend fun findAll(limit: Int?, offset: Long?): List<User> {
return userRepository.findAllByLimitAndByOffset( return userRepository.findAllByLimitAndByOffset(
min(limit ?: maxLimit, maxLimit), min(limit ?: maxLimit, maxLimit),
offset ?: 0 offset ?: 0
) )
} }
override suspend fun findById(id: Long): User { override suspend fun findById(id: Long): User =
return userRepository.findById(id) ?: throw UserNotFoundException("$id was not found.") userRepository.findById(id) ?: throw UserNotFoundException("$id was not found.")
}
override suspend fun findByIds(ids: List<Long>): List<User> { override suspend fun findByIds(ids: List<Long>): List<User> = userRepository.findByIds(ids)
return userRepository.findByIds(ids)
}
override suspend fun findByName(name: String): List<User> { override suspend fun findByName(name: String): List<User> = userRepository.findByName(name)
return userRepository.findByName(name)
}
override suspend fun findByNameLocalUser(name: String): User { override suspend fun findByNameLocalUser(name: String): User {
return userRepository.findByNameAndDomain(name, Config.configData.domain) return userRepository.findByNameAndDomain(name, Config.configData.domain)
?: throw UserNotFoundException("$name was not found.") ?: throw UserNotFoundException("$name was not found.")
} }
override suspend fun findByNameAndDomains(names: List<Pair<String, String>>): List<User> { override suspend fun findByNameAndDomains(names: List<Pair<String, String>>): List<User> =
return userRepository.findByNameAndDomains(names) userRepository.findByNameAndDomains(names)
}
override suspend fun findByUrl(url: String): User { override suspend fun findByUrl(url: String): User =
return userRepository.findByUrl(url) ?: throw UserNotFoundException("$url was not found.") userRepository.findByUrl(url) ?: throw UserNotFoundException("$url was not found.")
}
override suspend fun findByUrls(urls: List<String>): List<User> { override suspend fun findByUrls(urls: List<String>): List<User> = userRepository.findByUrls(urls)
return userRepository.findByUrls(urls)
}
override suspend fun usernameAlreadyUse(username: String): Boolean { override suspend fun usernameAlreadyUse(username: String): Boolean {
val findByNameAndDomain = userRepository.findByNameAndDomain(username, Config.configData.domain) val findByNameAndDomain = userRepository.findByNameAndDomain(username, Config.configData.domain)
@ -58,7 +48,7 @@ class UserService(private val userRepository: IUserRepository, private val userA
override suspend fun createLocalUser(user: UserCreateDto): User { override suspend fun createLocalUser(user: UserCreateDto): User {
val nextId = userRepository.nextId() val nextId = userRepository.nextId()
val HashedPassword = userAuthService.hash(user.password) val hashedPassword = userAuthService.hash(user.password)
val keyPair = userAuthService.generateKeyPair() val keyPair = userAuthService.generateKeyPair()
val userEntity = User( val userEntity = User(
id = nextId, id = nextId,
@ -66,13 +56,13 @@ class UserService(private val userRepository: IUserRepository, private val userA
domain = Config.configData.domain, domain = Config.configData.domain,
screenName = user.screenName, screenName = user.screenName,
description = user.description, description = user.description,
password = HashedPassword, password = hashedPassword,
inbox = "${Config.configData.url}/users/${user.name}/inbox", inbox = "${Config.configData.url}/users/${user.name}/inbox",
outbox = "${Config.configData.url}/users/${user.name}/outbox", outbox = "${Config.configData.url}/users/${user.name}/outbox",
url = "${Config.configData.url}/users/${user.name}", url = "${Config.configData.url}/users/${user.name}",
publicKey = keyPair.public.toPem(), publicKey = keyPair.public.toPem(),
privateKey = keyPair.private.toPem(), privateKey = keyPair.private.toPem(),
Instant.now() createdAt = Instant.now()
) )
return userRepository.save(userEntity) return userRepository.save(userEntity)
} }
@ -94,12 +84,7 @@ class UserService(private val userRepository: IUserRepository, private val userA
return userRepository.save(userEntity) return userRepository.save(userEntity)
} }
override suspend fun findFollowersById(id: Long): List<User> { override suspend fun findFollowersById(id: Long): List<User> = userRepository.findFollowersById(id)
return userRepository.findFollowersById(id)
}
override suspend fun addFollowers(id: Long, follower: Long) {
return userRepository.createFollower(id, follower)
}
override suspend fun addFollowers(id: Long, follower: Long) = userRepository.createFollower(id, follower)
} }

View File

@ -5,6 +5,6 @@ import kjob.core.dsl.ScheduleContext
interface JobQueueParentService { interface JobQueueParentService {
fun init(jobDefines:List<Job>) fun init(jobDefines: List<Job>)
suspend fun <J : Job> schedule(job: J, block: ScheduleContext<J>.(J) -> Unit = {}) suspend fun <J : Job> schedule(job: J, block: ScheduleContext<J>.(J) -> Unit = {})
} }

View File

@ -1,10 +1,10 @@
package dev.usbharu.hideout.service.job package dev.usbharu.hideout.service.job
import kjob.core.Job import kjob.core.Job
import kjob.core.dsl.JobContextWithProps
import kjob.core.dsl.JobRegisterContext
import kjob.core.dsl.KJobFunctions import kjob.core.dsl.KJobFunctions
import kjob.core.dsl.JobContextWithProps as JCWP
import kjob.core.dsl.JobRegisterContext as JRC
interface JobQueueWorkerService { interface JobQueueWorkerService {
fun init(defines: List<Pair<Job, JobRegisterContext<Job, JobContextWithProps<Job>>.(Job) -> KJobFunctions<Job, JobContextWithProps<Job>>>>) fun init(defines: List<Pair<Job, JRC<Job, JCWP<Job>>.(Job) -> KJobFunctions<Job, JCWP<Job>>>>)
} }

View File

@ -17,9 +17,7 @@ class KJobJobQueueParentService(private val database: Database) : JobQueueParent
isWorker = false isWorker = false
}.start() }.start()
override fun init(jobDefines: List<Job>) { override fun init(jobDefines: List<Job>) = Unit
}
override suspend fun <J : Job> schedule(job: J, block: ScheduleContext<J>.(J) -> Unit) { override suspend fun <J : Job> schedule(job: J, block: ScheduleContext<J>.(J) -> Unit) {
logger.debug("schedule job={}", job.name) logger.debug("schedule job={}", job.name)

View File

@ -2,11 +2,11 @@ package dev.usbharu.hideout.service.job
import dev.usbharu.kjob.exposed.ExposedKJob import dev.usbharu.kjob.exposed.ExposedKJob
import kjob.core.Job import kjob.core.Job
import kjob.core.dsl.JobContextWithProps
import kjob.core.dsl.JobRegisterContext
import kjob.core.dsl.KJobFunctions import kjob.core.dsl.KJobFunctions
import kjob.core.kjob import kjob.core.kjob
import org.jetbrains.exposed.sql.Database import org.jetbrains.exposed.sql.Database
import kjob.core.dsl.JobContextWithProps as JCWP
import kjob.core.dsl.JobRegisterContext as JRC
class KJobJobQueueWorkerService(private val database: Database) : JobQueueWorkerService { class KJobJobQueueWorkerService(private val database: Database) : JobQueueWorkerService {
@ -19,10 +19,11 @@ class KJobJobQueueWorkerService(private val database: Database) : JobQueueWorker
}.start() }.start()
} }
override fun init(defines: List<Pair<Job,JobRegisterContext<Job, JobContextWithProps<Job>>.(Job) -> KJobFunctions<Job, JobContextWithProps<Job>>>>) { override fun init(
defines: List<Pair<Job, JRC<Job, JCWP<Job>>.(Job) -> KJobFunctions<Job, JCWP<Job>>>>
) {
defines.forEach { job -> defines.forEach { job ->
kjob.register(job.first, job.second) kjob.register(job.first, job.second)
} }
} }
} }

View File

@ -3,5 +3,5 @@ package dev.usbharu.hideout.service.signature
import io.ktor.http.* import io.ktor.http.*
interface HttpSignatureVerifyService { interface HttpSignatureVerifyService {
fun verify(headers:Headers):Boolean fun verify(headers: Headers): Boolean
} }

View File

@ -2,24 +2,22 @@ package dev.usbharu.hideout.service.signature
import dev.usbharu.hideout.plugins.KtorKeyMap import dev.usbharu.hideout.plugins.KtorKeyMap
import dev.usbharu.hideout.repository.IUserRepository import dev.usbharu.hideout.repository.IUserRepository
import dev.usbharu.hideout.service.IUserAuthService
import io.ktor.http.* import io.ktor.http.*
import tech.barbero.http.message.signing.HttpMessage
import tech.barbero.http.message.signing.SignatureHeaderVerifier import tech.barbero.http.message.signing.SignatureHeaderVerifier
class HttpSignatureVerifyServiceImpl(private val userAuthService: IUserRepository) : HttpSignatureVerifyService { class HttpSignatureVerifyServiceImpl(private val userAuthService: IUserRepository) : HttpSignatureVerifyService {
override fun verify(headers: Headers): Boolean { override fun verify(headers: Headers): Boolean {
val build = SignatureHeaderVerifier.builder().keyMap(KtorKeyMap(userAuthService)).build() val build = SignatureHeaderVerifier.builder().keyMap(KtorKeyMap(userAuthService)).build()
return true return true
build.verify(object : HttpMessage { // build.verify(object : HttpMessage {
override fun headerValues(name: String?): MutableList<String> { // override fun headerValues(name: String?): MutableList<String> {
return name?.let { headers.getAll(it) }?.toMutableList() ?: mutableListOf() // return name?.let { headers.getAll(it) }?.toMutableList() ?: mutableListOf()
} // }
//
override fun addHeader(name: String?, value: String?) { // override fun addHeader(name: String?, value: String?) {
TODO() // TODO()
} // }
//
}) // })
} }
} }

View File

@ -3,6 +3,18 @@ package dev.usbharu.hideout.util
import io.ktor.http.* import io.ktor.http.*
object HttpUtil { object HttpUtil {
val ContentType.Application.Activity: ContentType
get() = ContentType("application", "activity+json")
val ContentType.Application.JsonLd: ContentType
get() {
return ContentType(
contentType = "application",
contentSubtype = "ld+json",
parameters = listOf(HeaderValueParam("profile", "https://www.w3.org/ns/activitystreams"))
)
}
fun isContentTypeOfActivityPub( fun isContentTypeOfActivityPub(
contentType: String, contentType: String,
subType: String, subType: String,
@ -25,15 +37,5 @@ object HttpUtil {
contentType.parameter("profile").orEmpty() contentType.parameter("profile").orEmpty()
) )
} }
val ContentType.Application.Activity: ContentType
get() = ContentType("application", "activity+json")
val ContentType.Application.JsonLd: ContentType
get() = ContentType(
"application",
"ld+json",
listOf(HeaderValueParam("profile", "https://www.w3.org/ns/activitystreams"))
)
// fun // fun
} }

View File

@ -56,7 +56,6 @@ class ExposedJobRepository(
suspend fun <T> query(block: suspend () -> T): T = newSuspendedTransaction(Dispatchers.IO) { block() } suspend fun <T> query(block: suspend () -> T): T = newSuspendedTransaction(Dispatchers.IO) { block() }
override suspend fun completeProgress(id: String): Boolean { override suspend fun completeProgress(id: String): Boolean {
val now = Instant.now(clock).toEpochMilli() val now = Instant.now(clock).toEpochMilli()
return query { return query {
@ -86,12 +85,17 @@ class ExposedJobRepository(
override suspend fun get(id: String): ScheduledJob? { override suspend fun get(id: String): ScheduledJob? {
val single = query { jobs.select(jobs.id eq id.toLong()).singleOrNull() } ?: return null val single = query { jobs.select(jobs.id eq id.toLong()).singleOrNull() } ?: return null
return single.toScheduledJob() return single.toScheduledJob()
} }
override suspend fun reset(id: String, oldKjobId: UUID?): Boolean { override suspend fun reset(id: String, oldKjobId: UUID?): Boolean {
return query { return query {
jobs.update({ jobs.id eq id.toLong() and if (oldKjobId == null) jobs.kjobId.isNull() else jobs.kjobId eq oldKjobId.toString() }) { jobs.update({
jobs.id eq id.toLong() and if (oldKjobId == null) {
jobs.kjobId.isNull()
} else {
jobs.kjobId eq oldKjobId.toString()
}
}) {
it[jobs.status] = JobStatus.CREATED.name it[jobs.status] = JobStatus.CREATED.name
it[jobs.statusMessage] = null it[jobs.statusMessage] = null
it[jobs.kjobId] = null it[jobs.kjobId] = null
@ -107,7 +111,18 @@ class ExposedJobRepository(
override suspend fun save(jobSettings: JobSettings, runAt: Instant?): ScheduledJob { override suspend fun save(jobSettings: JobSettings, runAt: Instant?): ScheduledJob {
val now = Instant.now(clock) val now = Instant.now(clock)
val scheduledJob = val scheduledJob =
ScheduledJob("", JobStatus.CREATED, runAt, null, 0, null, now, now, jobSettings, JobProgress(0)) ScheduledJob(
id = "",
status = JobStatus.CREATED,
runAt = runAt,
statusMessage = null,
retries = 0,
kjobId = null,
createdAt = now,
updatedAt = now,
settings = jobSettings,
progress = JobProgress(0)
)
val id = query { val id = query {
jobs.insert { jobs.insert {
it[jobs.status] = scheduledJob.status.name it[jobs.status] = scheduledJob.status.name
@ -168,7 +183,13 @@ class ExposedJobRepository(
retries: Int retries: Int
): Boolean { ): Boolean {
return query { return query {
jobs.update({ (jobs.id eq id.toLong()) and if (oldKjobId == null) jobs.kjobId.isNull() else jobs.kjobId eq oldKjobId.toString() }) { jobs.update({
(jobs.id eq id.toLong()) and if (oldKjobId == null) {
jobs.kjobId.isNull()
} else {
jobs.kjobId eq oldKjobId.toString()
}
}) {
it[jobs.status] = status.name it[jobs.status] = status.name
it[jobs.retries] = retries it[jobs.retries] = retries
it[jobs.updatedAt] = Instant.now(clock).toEpochMilli() it[jobs.updatedAt] = Instant.now(clock).toEpochMilli()
@ -214,6 +235,7 @@ class ExposedJobRepository(
return null return null
} }
@Suppress("UNCHECKED_CAST")
fun listSerialize(value: List<*>): JsonElement { fun listSerialize(value: List<*>): JsonElement {
return if (value.isEmpty()) { return if (value.isEmpty()) {
buildJsonObject { buildJsonObject {
@ -227,7 +249,7 @@ class ExposedJobRepository(
is Int -> "i" to (value as List<Int>).map(::JsonPrimitive) is Int -> "i" to (value as List<Int>).map(::JsonPrimitive)
is String -> "s" to (value as List<String>).map(::JsonPrimitive) is String -> "s" to (value as List<String>).map(::JsonPrimitive)
is Boolean -> "b" to (value as List<Boolean>).map(::JsonPrimitive) is Boolean -> "b" to (value as List<Boolean>).map(::JsonPrimitive)
else -> error("Cannot serialize unsupported list property value: $value") else -> error("Cannot serialize unsupported list property value: $item")
} }
buildJsonObject { buildJsonObject {
put("t", t) put("t", t)
@ -265,6 +287,7 @@ class ExposedJobRepository(
retries = single[retries], retries = single[retries],
kjobId = single[kjobId]?.let { kjobId = single[kjobId]?.let {
try { try {
@Suppress("SwallowedException")
UUID.fromString(it) UUID.fromString(it)
} catch (e: IllegalArgumentException) { } catch (e: IllegalArgumentException) {
null null

View File

@ -9,24 +9,6 @@ import java.time.Clock
class ExposedKJob(config: Configuration) : BaseKJob<ExposedKJob.Configuration>(config) { class ExposedKJob(config: Configuration) : BaseKJob<ExposedKJob.Configuration>(config) {
companion object : KJobFactory<ExposedKJob, Configuration> {
override fun create(configure: Configuration.() -> Unit): KJob {
return ExposedKJob(Configuration().apply(configure))
}
}
class Configuration : BaseKJob.Configuration() {
var connectionString: String? = null
var driverClassName: String? = null
var connectionDatabase: Database? = null
var jobTableName = "kjobJobs"
var lockTableName = "kjobLocks"
var expireLockInMinutes = 5L
}
private val database: Database = config.connectionDatabase ?: Database.connect( private val database: Database = config.connectionDatabase ?: Database.connect(
requireNotNull(config.connectionString), requireNotNull(config.connectionString),
requireNotNull(config.driverClassName) requireNotNull(config.driverClassName)
@ -34,6 +16,7 @@ class ExposedKJob(config: Configuration) : BaseKJob<ExposedKJob.Configuration>(c
override val jobRepository: ExposedJobRepository override val jobRepository: ExposedJobRepository
get() = ExposedJobRepository(database, config.jobTableName, Clock.systemUTC(), config.json) get() = ExposedJobRepository(database, config.jobTableName, Clock.systemUTC(), config.json)
override val lockRepository: ExposedLockRepository override val lockRepository: ExposedLockRepository
get() = ExposedLockRepository(database, config, clock) get() = ExposedLockRepository(database, config, clock)
@ -47,4 +30,20 @@ class ExposedKJob(config: Configuration) : BaseKJob<ExposedKJob.Configuration>(c
super.shutdown() super.shutdown()
lockRepository.clearExpired() lockRepository.clearExpired()
} }
companion object : KJobFactory<ExposedKJob, Configuration> {
override fun create(configure: Configuration.() -> Unit): KJob = ExposedKJob(Configuration().apply(configure))
}
class Configuration : BaseKJob.Configuration() {
var connectionString: String? = null
var driverClassName: String? = null
var connectionDatabase: Database? = null
var jobTableName = "kjobJobs"
var lockTableName = "kjobLocks"
var expireLockInMinutes = 5L
}
} }

View File

@ -2,7 +2,6 @@
{ {
"type": "agent-extracted", "type": "agent-extracted",
"classes": [ "classes": [
] ]
} }
] ]

View File

@ -691,55 +691,46 @@
{ {
"name": "getCreatedAt", "name": "getCreatedAt",
"parameterTypes": [ "parameterTypes": [
] ]
}, },
{ {
"name": "getId", "name": "getId",
"parameterTypes": [ "parameterTypes": [
] ]
}, },
{ {
"name": "getOverview", "name": "getOverview",
"parameterTypes": [ "parameterTypes": [
] ]
}, },
{ {
"name": "getReplyId", "name": "getReplyId",
"parameterTypes": [ "parameterTypes": [
] ]
}, },
{ {
"name": "getRepostId", "name": "getRepostId",
"parameterTypes": [ "parameterTypes": [
] ]
}, },
{ {
"name": "getText", "name": "getText",
"parameterTypes": [ "parameterTypes": [
] ]
}, },
{ {
"name": "getUrl", "name": "getUrl",
"parameterTypes": [ "parameterTypes": [
] ]
}, },
{ {
"name": "getUserId", "name": "getUserId",
"parameterTypes": [ "parameterTypes": [
] ]
}, },
{ {
"name": "getVisibility", "name": "getVisibility",
"parameterTypes": [ "parameterTypes": [
] ]
} }
] ]
@ -753,7 +744,6 @@
{ {
"name": "<init>", "name": "<init>",
"parameterTypes": [ "parameterTypes": [
] ]
}, },
{ {
@ -765,19 +755,16 @@
{ {
"name": "getActor", "name": "getActor",
"parameterTypes": [ "parameterTypes": [
] ]
}, },
{ {
"name": "getObject", "name": "getObject",
"parameterTypes": [ "parameterTypes": [
] ]
}, },
{ {
"name": "setActor", "name": "setActor",
"parameterTypes": [ "parameterTypes": [
] ]
}, },
{ {
@ -794,7 +781,6 @@
{ {
"name": "<init>", "name": "<init>",
"parameterTypes": [ "parameterTypes": [
] ]
} }
] ]
@ -805,7 +791,6 @@
{ {
"name": "<init>", "name": "<init>",
"parameterTypes": [ "parameterTypes": [
] ]
} }
] ]
@ -819,7 +804,6 @@
{ {
"name": "<init>", "name": "<init>",
"parameterTypes": [ "parameterTypes": [
] ]
}, },
{ {
@ -831,7 +815,6 @@
{ {
"name": "getObject", "name": "getObject",
"parameterTypes": [ "parameterTypes": [
] ]
} }
] ]
@ -845,37 +828,31 @@
{ {
"name": "<init>", "name": "<init>",
"parameterTypes": [ "parameterTypes": [
] ]
}, },
{ {
"name": "<init>", "name": "<init>",
"parameterTypes": [ "parameterTypes": [
] ]
}, },
{ {
"name": "getActor", "name": "getActor",
"parameterTypes": [ "parameterTypes": [
] ]
}, },
{ {
"name": "getObject", "name": "getObject",
"parameterTypes": [ "parameterTypes": [
] ]
}, },
{ {
"name": "setActor", "name": "setActor",
"parameterTypes": [ "parameterTypes": [
] ]
}, },
{ {
"name": "setObject", "name": "setObject",
"parameterTypes": [ "parameterTypes": [
] ]
} }
] ]
@ -889,13 +866,11 @@
{ {
"name": "<init>", "name": "<init>",
"parameterTypes": [ "parameterTypes": [
] ]
}, },
{ {
"name": "<init>", "name": "<init>",
"parameterTypes": [ "parameterTypes": [
] ]
} }
] ]
@ -909,19 +884,16 @@
{ {
"name": "<init>", "name": "<init>",
"parameterTypes": [ "parameterTypes": [
] ]
}, },
{ {
"name": "getContext", "name": "getContext",
"parameterTypes": [ "parameterTypes": [
] ]
}, },
{ {
"name": "setContext", "name": "setContext",
"parameterTypes": [ "parameterTypes": [
] ]
} }
] ]
@ -935,49 +907,41 @@
{ {
"name": "<init>", "name": "<init>",
"parameterTypes": [ "parameterTypes": [
] ]
}, },
{ {
"name": "<init>", "name": "<init>",
"parameterTypes": [ "parameterTypes": [
] ]
}, },
{ {
"name": "getId", "name": "getId",
"parameterTypes": [ "parameterTypes": [
] ]
}, },
{ {
"name": "getOwner", "name": "getOwner",
"parameterTypes": [ "parameterTypes": [
] ]
}, },
{ {
"name": "getPublicKeyPem", "name": "getPublicKeyPem",
"parameterTypes": [ "parameterTypes": [
] ]
}, },
{ {
"name": "setId", "name": "setId",
"parameterTypes": [ "parameterTypes": [
] ]
}, },
{ {
"name": "setOwner", "name": "setOwner",
"parameterTypes": [ "parameterTypes": [
] ]
}, },
{ {
"name": "setPublicKeyPem", "name": "setPublicKeyPem",
"parameterTypes": [ "parameterTypes": [
] ]
} }
] ]
@ -991,67 +955,56 @@
{ {
"name": "<init>", "name": "<init>",
"parameterTypes": [ "parameterTypes": [
] ]
}, },
{ {
"name": "<init>", "name": "<init>",
"parameterTypes": [ "parameterTypes": [
] ]
}, },
{ {
"name": "getAttributedTo", "name": "getAttributedTo",
"parameterTypes": [ "parameterTypes": [
] ]
}, },
{ {
"name": "getContent", "name": "getContent",
"parameterTypes": [ "parameterTypes": [
] ]
}, },
{ {
"name": "getId", "name": "getId",
"parameterTypes": [ "parameterTypes": [
] ]
}, },
{ {
"name": "getPublished", "name": "getPublished",
"parameterTypes": [ "parameterTypes": [
] ]
}, },
{ {
"name": "getTo", "name": "getTo",
"parameterTypes": [ "parameterTypes": [
] ]
}, },
{ {
"name": "setAttributedTo", "name": "setAttributedTo",
"parameterTypes": [ "parameterTypes": [
] ]
}, },
{ {
"name": "setContent", "name": "setContent",
"parameterTypes": [ "parameterTypes": [
] ]
}, },
{ {
"name": "setId", "name": "setId",
"parameterTypes": [ "parameterTypes": [
] ]
}, },
{ {
"name": "setPublished", "name": "setPublished",
"parameterTypes": [ "parameterTypes": [
] ]
} }
] ]
@ -1065,25 +1018,21 @@
{ {
"name": "<init>", "name": "<init>",
"parameterTypes": [ "parameterTypes": [
] ]
}, },
{ {
"name": "<init>", "name": "<init>",
"parameterTypes": [ "parameterTypes": [
] ]
}, },
{ {
"name": "getName", "name": "getName",
"parameterTypes": [ "parameterTypes": [
] ]
}, },
{ {
"name": "setName", "name": "setName",
"parameterTypes": [ "parameterTypes": [
] ]
} }
] ]
@ -1094,7 +1043,6 @@
{ {
"name": "add", "name": "add",
"parameterTypes": [ "parameterTypes": [
] ]
} }
] ]
@ -1108,7 +1056,6 @@
{ {
"name": "<init>", "name": "<init>",
"parameterTypes": [ "parameterTypes": [
] ]
}, },
{ {
@ -1121,49 +1068,41 @@
{ {
"name": "getInbox", "name": "getInbox",
"parameterTypes": [ "parameterTypes": [
] ]
}, },
{ {
"name": "getOutbox", "name": "getOutbox",
"parameterTypes": [ "parameterTypes": [
] ]
}, },
{ {
"name": "getPreferredUsername", "name": "getPreferredUsername",
"parameterTypes": [ "parameterTypes": [
] ]
}, },
{ {
"name": "getPublicKey", "name": "getPublicKey",
"parameterTypes": [ "parameterTypes": [
] ]
}, },
{ {
"name": "getSummary", "name": "getSummary",
"parameterTypes": [ "parameterTypes": [
] ]
}, },
{ {
"name": "setInbox", "name": "setInbox",
"parameterTypes": [ "parameterTypes": [
] ]
}, },
{ {
"name": "setOutbox", "name": "setOutbox",
"parameterTypes": [ "parameterTypes": [
] ]
}, },
{ {
"name": "setPreferredUsername", "name": "setPreferredUsername",
"parameterTypes": [ "parameterTypes": [
] ]
}, },
{ {
@ -1175,7 +1114,6 @@
{ {
"name": "setSummary", "name": "setSummary",
"parameterTypes": [ "parameterTypes": [
] ]
} }
] ]
@ -1186,7 +1124,6 @@
{ {
"name": "<init>", "name": "<init>",
"parameterTypes": [ "parameterTypes": [
] ]
} }
] ]
@ -1199,7 +1136,6 @@
{ {
"name": "<init>", "name": "<init>",
"parameterTypes": [ "parameterTypes": [
] ]
} }
] ]
@ -1228,7 +1164,6 @@
{ {
"name": "parseActivity", "name": "parseActivity",
"parameterTypes": [ "parameterTypes": [
] ]
}, },
{ {
@ -1324,7 +1259,6 @@
{ {
"name": "init", "name": "init",
"parameterTypes": [ "parameterTypes": [
] ]
}, },
{ {

View File

@ -44,11 +44,10 @@
"pattern": "\\Qstatic/index.html\\E" "pattern": "\\Qstatic/index.html\\E"
}, },
{ {
"pattern":"\\Qkotlin/kotlin.kotlin_builtins\\E" "pattern": "\\Qkotlin/kotlin.kotlin_builtins\\E"
} }
] ]
}, },
"bundles": [ "bundles": [
] ]
} }

View File

@ -1,11 +1,8 @@
{ {
"lambdaCapturingTypes": [ "lambdaCapturingTypes": [
], ],
"types": [ "types": [
], ],
"proxies": [ "proxies": [
] ]
} }

View File

@ -2,6 +2,5 @@ package dev.usbharu.hideout
import io.ktor.server.application.* import io.ktor.server.application.*
fun Application.empty(){ fun Application.empty() {
} }

View File

@ -44,7 +44,8 @@ class ContextDeserializerTest {
} }
""" """
val readValue = jacksonObjectMapper().enable(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY).configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES,false).readValue<Follow>(s) val readValue = jacksonObjectMapper().enable(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY)
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false).readValue<Follow>(s)
println(readValue) println(readValue)
println(readValue.actor) println(readValue.actor)
} }

View File

@ -5,7 +5,7 @@ import dev.usbharu.hideout.domain.model.ap.Accept
import dev.usbharu.hideout.domain.model.ap.Follow import dev.usbharu.hideout.domain.model.ap.Follow
import org.junit.jupiter.api.Test import org.junit.jupiter.api.Test
class ContextSerializerTest{ class ContextSerializerTest {
@Test @Test
fun serialize() { fun serialize() {

View File

@ -15,7 +15,6 @@ import java.time.Instant
class ActivityPubKtTest { class ActivityPubKtTest {
@Test @Test
fun HttpSignTest(): Unit = runBlocking { fun HttpSignTest(): Unit = runBlocking {
val ktorKeyMap = KtorKeyMap(object : IUserRepository { val ktorKeyMap = KtorKeyMap(object : IUserRepository {
override suspend fun save(user: User): User { override suspend fun save(user: User): User {
TODO("Not yet implemented") TODO("Not yet implemented")
@ -33,7 +32,7 @@ class ActivityPubKtTest {
TODO() TODO()
} }
override suspend fun findByNameAndDomain(name: String, domain: String): User? { override suspend fun findByNameAndDomain(name: String, domain: String): User {
val keyPairGenerator = KeyPairGenerator.getInstance("RSA") val keyPairGenerator = KeyPairGenerator.getInstance("RSA")
keyPairGenerator.initialize(1024) keyPairGenerator.initialize(1024)
val generateKeyPair = keyPairGenerator.generateKeyPair() val generateKeyPair = keyPairGenerator.generateKeyPair()
@ -96,12 +95,13 @@ class ActivityPubKtTest {
override suspend fun nextId(): Long { override suspend fun nextId(): Long {
TODO("Not yet implemented") TODO("Not yet implemented")
} }
}) })
val httpClient = HttpClient(MockEngine { httpRequestData -> val httpClient = HttpClient(
MockEngine { httpRequestData ->
respondOk() respondOk()
}) { }
) {
install(httpSignaturePlugin) { install(httpSignaturePlugin) {
keyMap = ktorKeyMap keyMap = ktorKeyMap
} }
@ -112,7 +112,5 @@ class ActivityPubKtTest {
} }
httpClient.postAp("https://localhost", "test", JsonLd(emptyList())) httpClient.postAp("https://localhost", "test", JsonLd(emptyList()))
} }
} }

View File

@ -28,7 +28,7 @@ class KtorKeyMapTest {
TODO() TODO()
} }
override suspend fun findByNameAndDomain(name: String, domain: String): User? { override suspend fun findByNameAndDomain(name: String, domain: String): User {
val keyPairGenerator = KeyPairGenerator.getInstance("RSA") val keyPairGenerator = KeyPairGenerator.getInstance("RSA")
keyPairGenerator.initialize(1024) keyPairGenerator.initialize(1024)
val generateKeyPair = keyPairGenerator.generateKeyPair() val generateKeyPair = keyPairGenerator.generateKeyPair()
@ -46,7 +46,6 @@ class KtorKeyMapTest {
generateKeyPair.private.toPem(), generateKeyPair.private.toPem(),
createdAt = Instant.now() createdAt = Instant.now()
) )
} }
override suspend fun findByDomain(domain: String): List<User> { override suspend fun findByDomain(domain: String): List<User> {
@ -92,7 +91,6 @@ class KtorKeyMapTest {
override suspend fun nextId(): Long { override suspend fun nextId(): Long {
TODO("Not yet implemented") TODO("Not yet implemented")
} }
}) })
ktorKeyMap.getPrivateKey("test") ktorKeyMap.getPrivateKey("test")

View File

@ -18,7 +18,6 @@ import java.time.Clock
import java.time.Instant import java.time.Instant
import java.time.ZoneId import java.time.ZoneId
class UserRepositoryTest { class UserRepositoryTest {
lateinit var db: Database lateinit var db: Database
@ -35,7 +34,6 @@ class UserRepositoryTest {
@AfterEach @AfterEach
fun tearDown() { fun tearDown() {
transaction(db) { transaction(db) {
SchemaUtils.drop(UsersFollowers) SchemaUtils.drop(UsersFollowers)
SchemaUtils.drop(Users) SchemaUtils.drop(Users)
} }
@ -43,11 +41,14 @@ class UserRepositoryTest {
@Test @Test
fun `findFollowersById フォロワー一覧を取得`() = runTest { fun `findFollowersById フォロワー一覧を取得`() = runTest {
val userRepository = UserRepository(db, object : IdGenerateService { val userRepository = UserRepository(
db,
object : IdGenerateService {
override suspend fun generateId(): Long { override suspend fun generateId(): Long {
TODO("Not yet implemented") TODO("Not yet implemented")
} }
}) }
)
val user = userRepository.save( val user = userRepository.save(
User( User(
id = 0L, id = 0L,
@ -98,18 +99,21 @@ class UserRepositoryTest {
userRepository.findFollowersById(user.id).let { userRepository.findFollowersById(user.id).let {
assertIterableEquals(listOf(follower, follower2), it) assertIterableEquals(listOf(follower, follower2), it)
} }
} }
@Test @Test
fun `createFollower フォロワー追加`() = runTest { fun `createFollower フォロワー追加`() = runTest {
val userRepository = UserRepository(db, object : IdGenerateService { val userRepository = UserRepository(
db,
object : IdGenerateService {
override suspend fun generateId(): Long { override suspend fun generateId(): Long {
TODO("Not yet implemented") TODO("Not yet implemented")
} }
}) }
)
val user = userRepository.save( val user = userRepository.save(
User(0L, User(
0L,
"test", "test",
"example.com", "example.com",
"testUser", "testUser",
@ -123,7 +127,8 @@ class UserRepositoryTest {
) )
) )
val follower = userRepository.save( val follower = userRepository.save(
User(1L, User(
1L,
"follower", "follower",
"follower.example.com", "follower.example.com",
"followerUser", "followerUser",
@ -138,11 +143,9 @@ class UserRepositoryTest {
) )
userRepository.createFollower(user.id, follower.id) userRepository.createFollower(user.id, follower.id)
transaction { transaction {
val followerIds = val followerIds =
UsersFollowers.select { UsersFollowers.userId eq user.id }.map { it[UsersFollowers.followerId] } UsersFollowers.select { UsersFollowers.userId eq user.id }.map { it[UsersFollowers.followerId] }
assertIterableEquals(listOf(follower.id), followerIds) assertIterableEquals(listOf(follower.id), followerIds)
} }
} }
} }

View File

@ -7,7 +7,6 @@ import dev.usbharu.hideout.plugins.configureStatusPages
import dev.usbharu.hideout.service.activitypub.ActivityPubService import dev.usbharu.hideout.service.activitypub.ActivityPubService
import dev.usbharu.hideout.service.activitypub.ActivityPubUserService import dev.usbharu.hideout.service.activitypub.ActivityPubUserService
import dev.usbharu.hideout.service.impl.IUserService import dev.usbharu.hideout.service.impl.IUserService
import dev.usbharu.hideout.service.impl.UserService
import dev.usbharu.hideout.service.signature.HttpSignatureVerifyService import dev.usbharu.hideout.service.signature.HttpSignatureVerifyService
import io.ktor.client.request.* import io.ktor.client.request.*
import io.ktor.http.* import io.ktor.http.*
@ -28,7 +27,7 @@ class InboxRoutingKtTest {
} }
application { application {
configureSerialization() configureSerialization()
configureRouting(mock(), mock(), mock(), mock(),mock()) configureRouting(mock(), mock(), mock(), mock(), mock())
} }
client.get("/inbox").let { client.get("/inbox").let {
Assertions.assertEquals(HttpStatusCode.MethodNotAllowed, it.status) Assertions.assertEquals(HttpStatusCode.MethodNotAllowed, it.status)
@ -40,10 +39,10 @@ class InboxRoutingKtTest {
environment { environment {
config = ApplicationConfig("empty.conf") config = ApplicationConfig("empty.conf")
} }
val httpSignatureVerifyService = mock<HttpSignatureVerifyService>{ val httpSignatureVerifyService = mock<HttpSignatureVerifyService> {
on { verify(any()) } doReturn true on { verify(any()) } doReturn true
} }
val activityPubService = mock<ActivityPubService>{ val activityPubService = mock<ActivityPubService> {
on { parseActivity(any()) } doThrow JsonParseException() on { parseActivity(any()) } doThrow JsonParseException()
} }
val userService = mock<IUserService>() val userService = mock<IUserService>()
@ -51,7 +50,13 @@ class InboxRoutingKtTest {
application { application {
configureStatusPages() configureStatusPages()
configureSerialization() configureSerialization()
configureRouting(httpSignatureVerifyService, activityPubService, userService, activityPubUserService,mock()) configureRouting(
httpSignatureVerifyService,
activityPubService,
userService,
activityPubUserService,
mock()
)
} }
client.post("/inbox").let { client.post("/inbox").let {
Assertions.assertEquals(HttpStatusCode.BadRequest, it.status) Assertions.assertEquals(HttpStatusCode.BadRequest, it.status)
@ -65,7 +70,7 @@ class InboxRoutingKtTest {
} }
application { application {
configureSerialization() configureSerialization()
configureRouting(mock(), mock(), mock(), mock(),mock()) configureRouting(mock(), mock(), mock(), mock(), mock())
} }
client.get("/users/test/inbox").let { client.get("/users/test/inbox").let {
Assertions.assertEquals(HttpStatusCode.MethodNotAllowed, it.status) Assertions.assertEquals(HttpStatusCode.MethodNotAllowed, it.status)
@ -77,10 +82,10 @@ class InboxRoutingKtTest {
environment { environment {
config = ApplicationConfig("empty.conf") config = ApplicationConfig("empty.conf")
} }
val httpSignatureVerifyService = mock<HttpSignatureVerifyService>{ val httpSignatureVerifyService = mock<HttpSignatureVerifyService> {
on { verify(any()) } doReturn true on { verify(any()) } doReturn true
} }
val activityPubService = mock<ActivityPubService>{ val activityPubService = mock<ActivityPubService> {
on { parseActivity(any()) } doThrow JsonParseException() on { parseActivity(any()) } doThrow JsonParseException()
} }
val userService = mock<IUserService>() val userService = mock<IUserService>()
@ -88,7 +93,13 @@ class InboxRoutingKtTest {
application { application {
configureStatusPages() configureStatusPages()
configureSerialization() configureSerialization()
configureRouting(httpSignatureVerifyService, activityPubService, userService, activityPubUserService,mock()) configureRouting(
httpSignatureVerifyService,
activityPubService,
userService,
activityPubUserService,
mock()
)
} }
client.post("/users/test/inbox").let { client.post("/users/test/inbox").let {
Assertions.assertEquals(HttpStatusCode.BadRequest, it.status) Assertions.assertEquals(HttpStatusCode.BadRequest, it.status)

View File

@ -32,7 +32,6 @@ import java.time.Instant
import kotlin.test.assertEquals import kotlin.test.assertEquals
import kotlin.test.assertTrue import kotlin.test.assertTrue
class UsersAPTest { class UsersAPTest {
@Test() @Test()
@ -100,8 +99,8 @@ class UsersAPTest {
} }
} }
// @Disabled
@Test() @Test()
// @Disabled
fun `ユーザのURLにAcceptヘッダーをActivityとJson-LDにしてアクセスしたときPersonが返ってくる`() = testApplication { fun `ユーザのURLにAcceptヘッダーをActivityとJson-LDにしてアクセスしたときPersonが返ってくる`() = testApplication {
environment { environment {
config = ApplicationConfig("empty.conf") config = ApplicationConfig("empty.conf")
@ -167,16 +166,21 @@ class UsersAPTest {
} }
} }
// @Disabled
@Test @Test
// @Disabled
fun contentType_Test() { fun contentType_Test() {
assertTrue(ContentType.Application.Activity.match("application/activity+json")) assertTrue(ContentType.Application.Activity.match("application/activity+json"))
val listOf = listOf(ContentType.Application.JsonLd, ContentType.Application.Activity) val listOf = listOf(ContentType.Application.JsonLd, ContentType.Application.Activity)
assertTrue(listOf.find { contentType -> assertTrue(
listOf.find { contentType ->
contentType.match("application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\"") contentType.match("application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\"")
}.let { it != null }) }.let { it != null }
assertTrue(ContentType.Application.JsonLd.match("application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\"")) )
assertTrue(
ContentType.Application.JsonLd.match(
"application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\""
)
)
} }
@Test @Test

View File

@ -1,6 +1,6 @@
package dev.usbharu.hideout.service package dev.usbharu.hideout.service
//import kotlinx.coroutines.NonCancellable.message // import kotlinx.coroutines.NonCancellable.message
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@ -16,17 +16,13 @@ class TwitterSnowflakeIdGenerateServiceTest {
val mutex = Mutex() val mutex = Mutex()
val mutableListOf = mutableListOf<Long>() val mutableListOf = mutableListOf<Long>()
coroutineScope { coroutineScope {
repeat(500000) { repeat(500000) {
launch(Dispatchers.IO) { launch(Dispatchers.IO) {
val id = TwitterSnowflakeIdGenerateService.generateId() val id = TwitterSnowflakeIdGenerateService.generateId()
mutex.withLock { mutex.withLock {
mutableListOf.add(id) mutableListOf.add(id)
} }
} }
} }
} }

View File

@ -122,7 +122,8 @@ class ActivityPubFollowServiceImplTest {
mock(), mock(),
activityPubUserService, activityPubUserService,
userService, userService,
HttpClient(MockEngine { httpRequestData -> HttpClient(
MockEngine { httpRequestData ->
assertEquals(person.inbox, httpRequestData.url.toString()) assertEquals(person.inbox, httpRequestData.url.toString())
val accept = Accept( val accept = Accept(
type = emptyList(), type = emptyList(),
@ -143,7 +144,8 @@ class ActivityPubFollowServiceImplTest {
) )
) )
respondOk() respondOk()
}) }
)
) )
activityPubFollowService.receiveFollowJob( activityPubFollowService.receiveFollowJob(
JobProps( JobProps(

View File

@ -73,7 +73,13 @@ class ActivityPubNoteServiceImplTest {
val jobQueueParentService = mock<JobQueueParentService>() val jobQueueParentService = mock<JobQueueParentService>()
val activityPubNoteService = ActivityPubNoteServiceImpl(mock(), jobQueueParentService, userService) val activityPubNoteService = ActivityPubNoteServiceImpl(mock(), jobQueueParentService, userService)
val postEntity = PostEntity( val postEntity = PostEntity(
1L, 1L, null, "test text", 1L, 1, "https://example.com" 1L,
1L,
null,
"test text",
1L,
1,
"https://example.com"
) )
activityPubNoteService.createNote(postEntity) activityPubNoteService.createNote(postEntity)
verify(jobQueueParentService, times(2)).schedule(eq(DeliverPostJob), any()) verify(jobQueueParentService, times(2)).schedule(eq(DeliverPostJob), any())
@ -82,10 +88,12 @@ class ActivityPubNoteServiceImplTest {
@Test @Test
fun `createPostJob 新しい投稿のJob`() = runTest { fun `createPostJob 新しい投稿のJob`() = runTest {
Config.configData = ConfigData(objectMapper = JsonObjectMapper.objectMapper) Config.configData = ConfigData(objectMapper = JsonObjectMapper.objectMapper)
val httpClient = HttpClient(MockEngine { httpRequestData -> val httpClient = HttpClient(
MockEngine { httpRequestData ->
assertEquals("https://follower.example.com/inbox", httpRequestData.url.toString()) assertEquals("https://follower.example.com/inbox", httpRequestData.url.toString())
respondOk() respondOk()
}) }
)
val activityPubNoteService = ActivityPubNoteServiceImpl(httpClient, mock(), mock()) val activityPubNoteService = ActivityPubNoteServiceImpl(httpClient, mock(), mock())
activityPubNoteService.createNoteJob( activityPubNoteService.createNoteJob(
JobProps( JobProps(

View File

@ -17,7 +17,7 @@ import java.security.KeyPairGenerator
import kotlin.test.assertEquals import kotlin.test.assertEquals
import kotlin.test.assertNull import kotlin.test.assertNull
class UserServiceTest{ class UserServiceTest {
@Test @Test
fun `createLocalUser ローカルユーザーを作成できる`() = runTest { fun `createLocalUser ローカルユーザーを作成できる`() = runTest {
Config.configData = ConfigData(domain = "example.com", url = "https://example.com") Config.configData = ConfigData(domain = "example.com", url = "https://example.com")
@ -43,21 +43,19 @@ class UserServiceTest{
assertEquals("example.com", firstValue.domain) assertEquals("example.com", firstValue.domain)
assertEquals("https://example.com/users/test/inbox", firstValue.inbox) assertEquals("https://example.com/users/test/inbox", firstValue.inbox)
assertEquals("https://example.com/users/test/outbox", firstValue.outbox) assertEquals("https://example.com/users/test/outbox", firstValue.outbox)
assertEquals(generateKeyPair.public.toPem(),firstValue.publicKey) assertEquals(generateKeyPair.public.toPem(), firstValue.publicKey)
assertEquals(generateKeyPair.private.toPem(),firstValue.privateKey) assertEquals(generateKeyPair.private.toPem(), firstValue.privateKey)
} }
} }
@Test @Test
fun `createRemoteUser リモートユーザーを作成できる`() = runTest { fun `createRemoteUser リモートユーザーを作成できる`() = runTest {
Config.configData = ConfigData(domain = "example.com", url = "https://example.com") Config.configData = ConfigData(domain = "example.com", url = "https://example.com")
val userRepository = mock<IUserRepository> {
val userRepository = mock<IUserRepository>{
onBlocking { nextId() } doReturn 113345L onBlocking { nextId() } doReturn 113345L
} }
val userService = UserService(userRepository,mock()) val userService = UserService(userRepository, mock())
val user = RemoteUserCreateDto( val user = RemoteUserCreateDto(
"test", "test",
"example.com", "example.com",
@ -81,7 +79,7 @@ class UserServiceTest{
assertEquals("example.com", firstValue.domain) assertEquals("example.com", firstValue.domain)
assertEquals("https://example.com/inbox", firstValue.inbox) assertEquals("https://example.com/inbox", firstValue.inbox)
assertEquals("https://example.com/outbox", firstValue.outbox) assertEquals("https://example.com/outbox", firstValue.outbox)
assertEquals("-----BEGIN PUBLIC KEY-----...-----END PUBLIC KEY-----",firstValue.publicKey) assertEquals("-----BEGIN PUBLIC KEY-----...-----END PUBLIC KEY-----", firstValue.publicKey)
assertNull(firstValue.privateKey) assertNull(firstValue.privateKey)
} }
} }

View File

@ -4,7 +4,7 @@ import org.jetbrains.exposed.sql.Transaction
import org.jetbrains.exposed.sql.transactions.TransactionManager import org.jetbrains.exposed.sql.transactions.TransactionManager
import org.junit.jupiter.api.extension.* import org.junit.jupiter.api.extension.*
class DBResetInterceptor : BeforeAllCallback,AfterAllCallback,BeforeEachCallback,AfterEachCallback { class DBResetInterceptor : BeforeAllCallback, AfterAllCallback, BeforeEachCallback, AfterEachCallback {
private lateinit var transactionAll: Transaction private lateinit var transactionAll: Transaction
private lateinit var transactionEach: Transaction private lateinit var transactionEach: Transaction

View File

@ -4,7 +4,6 @@ import org.jetbrains.exposed.sql.Database
import org.jetbrains.exposed.sql.DatabaseConfig import org.jetbrains.exposed.sql.DatabaseConfig
import org.junit.jupiter.api.extension.ExtendWith import org.junit.jupiter.api.extension.ExtendWith
@ExtendWith(DBResetInterceptor::class) @ExtendWith(DBResetInterceptor::class)
abstract class DatabaseTestBase { abstract class DatabaseTestBase {
companion object { companion object {
@ -12,7 +11,8 @@ abstract class DatabaseTestBase {
Database.connect( Database.connect(
"jdbc:h2:mem:test;DB_CLOSE_DELAY=-1", "jdbc:h2:mem:test;DB_CLOSE_DELAY=-1",
driver = "org.h2.Driver", driver = "org.h2.Driver",
databaseConfig = DatabaseConfig { useNestedTransactions = true }) databaseConfig = DatabaseConfig { useNestedTransactions = true }
)
} }
} }
} }