style: スタイルを修正

This commit is contained in:
usbharu 2023-04-30 01:25:54 +09:00
parent 5ca049f588
commit fcabce7380
71 changed files with 506 additions and 554 deletions

View File

@ -137,4 +137,5 @@ detekt {
config = files("detekt.yml") config = files("detekt.yml")
buildUponDefaultConfig = true buildUponDefaultConfig = true
basePath = rootDir.absolutePath basePath = rootDir.absolutePath
autoCorrect = true
} }

View File

@ -9,11 +9,26 @@ style:
active: true active: true
MultilineLambdaItParameter: MultilineLambdaItParameter:
active: true active: false
UseEmptyCounterpart: UseEmptyCounterpart:
active: true active: true
ExpressionBodySyntax:
active: true
WildcardImport:
active: false
ReturnCount:
active: false
MagicNumber:
ignorePropertyDeclaration: true
ForbiddenComment:
active: false
complexity: complexity:
CognitiveComplexMethod: CognitiveComplexMethod:
active: true active: true
@ -23,12 +38,15 @@ complexity:
ComplexInterface: ComplexInterface:
active: true active: true
threshold: 30
LabeledExpression: LabeledExpression:
active: true active: false
NamedArguments: NamedArguments:
active: true active: true
ignoreArgumentsMatchingNames: true
threshold: 5
NestedBlockDepth: NestedBlockDepth:
active: true active: true
@ -40,7 +58,16 @@ complexity:
active: true active: true
StringLiteralDuplication: StringLiteralDuplication:
active: true active: false
LongParameterList:
constructorThreshold: 10
TooManyFunctions:
ignoreDeprecated: true
ignoreOverridden: true
ignorePrivate: true
exceptions: exceptions:
ExceptionRaisedInUnexpectedLocation: ExceptionRaisedInUnexpectedLocation:
@ -71,6 +98,9 @@ formatting:
Indentation: Indentation:
indentSize: 4 indentSize: 4
NoWildcardImports:
active: false
naming: naming:
FunctionMaxLength: FunctionMaxLength:
active: true active: true
@ -81,6 +111,15 @@ naming:
LambdaParameterNaming: LambdaParameterNaming:
active: true active: true
ConstructorParameterNaming:
excludes:
- "**/domain/model/ap/*"
ignoreOverridden: true
VariableNaming:
excludes:
- "**/domain/model/ap/*"
performance: performance:
UnnecessaryPartOfBinaryExpression: UnnecessaryPartOfBinaryExpression:
active: true 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)
@ -67,7 +67,7 @@ fun Application.parent() {
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> {
@ -91,7 +91,6 @@ fun Application.parent() {
single<IdGenerateService> { TwitterSnowflakeIdGenerateService } single<IdGenerateService> { TwitterSnowflakeIdGenerateService }
} }
configureKoin(module) configureKoin(module)
configureHTTP() configureHTTP()
configureStaticRouting() configureStaticRouting()

View File

@ -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

@ -12,6 +12,4 @@ open class Follow : Object {
) : 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

@ -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

@ -20,17 +20,6 @@ open class Object : JsonLd {
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,
@ -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

@ -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

@ -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

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

@ -15,7 +15,6 @@ 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,11 +31,14 @@ 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)
} }
@ -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)
@ -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

@ -10,5 +10,4 @@ interface IUserAuthService {
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

@ -5,6 +5,7 @@ import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock import kotlinx.coroutines.sync.withLock
import java.time.Instant import java.time.Instant
@Suppress("MagicNumber")
open class SnowflakeIdGenerateService(private val baseTime: Long) : IdGenerateService { open class SnowflakeIdGenerateService(private val baseTime: Long) : IdGenerateService {
var lastTimeStamp: Long = -1 var lastTimeStamp: Long = -1
var sequenceId: Int = 0 var sequenceId: Int = 0
@ -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,7 +1,7 @@
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

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

@ -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

@ -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,7 +77,6 @@ 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")
@ -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,12 +4,15 @@ 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)

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

@ -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

@ -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

@ -49,6 +49,5 @@
] ]
}, },
"bundles": [ "bundles": [
] ]
} }

View File

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

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

@ -33,7 +33,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()

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

View File

@ -109,7 +109,8 @@ class UserRepositoryTest {
} }
}) })
val user = userRepository.save( val user = userRepository.save(
User(0L, User(
0L,
"test", "test",
"example.com", "example.com",
"testUser", "testUser",
@ -123,7 +124,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",

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.*
@ -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)
@ -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)