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")
buildUponDefaultConfig = true
basePath = rootDir.absolutePath
autoCorrect = true
}

View File

@ -9,11 +9,26 @@ style:
active: true
MultilineLambdaItParameter:
active: true
active: false
UseEmptyCounterpart:
active: true
ExpressionBodySyntax:
active: true
WildcardImport:
active: false
ReturnCount:
active: false
MagicNumber:
ignorePropertyDeclaration: true
ForbiddenComment:
active: false
complexity:
CognitiveComplexMethod:
active: true
@ -23,12 +38,15 @@ complexity:
ComplexInterface:
active: true
threshold: 30
LabeledExpression:
active: true
active: false
NamedArguments:
active: true
ignoreArgumentsMatchingNames: true
threshold: 5
NestedBlockDepth:
active: true
@ -40,7 +58,16 @@ complexity:
active: true
StringLiteralDuplication:
active: true
active: false
LongParameterList:
constructorThreshold: 10
TooManyFunctions:
ignoreDeprecated: true
ignoreOverridden: true
ignorePrivate: true
exceptions:
ExceptionRaisedInUnexpectedLocation:
@ -71,6 +98,9 @@ formatting:
Indentation:
indentSize: 4
NoWildcardImports:
active: false
naming:
FunctionMaxLength:
active: true
@ -81,6 +111,15 @@ naming:
LambdaParameterNaming:
active: true
ConstructorParameterNaming:
excludes:
- "**/domain/model/ap/*"
ignoreOverridden: true
VariableNaming:
excludes:
- "**/domain/model/ap/*"
performance:
UnnecessaryPartOfBinaryExpression:
active: true

View File

@ -42,9 +42,9 @@ val Application.property: Application.(propertyName: String) -> String
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() {
Config.configData = ConfigData(
url = property("hideout.url"),
objectMapper = jacksonObjectMapper().enable(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY)
@ -67,7 +67,7 @@ fun Application.parent() {
single<HttpSignatureVerifyService> { HttpSignatureVerifyServiceImpl(get()) }
single<JobQueueParentService> {
val kJobJobQueueService = KJobJobQueueParentService(get())
kJobJobQueueService.init(listOf())
kJobJobQueueService.init(emptyList())
kJobJobQueueService
}
single<HttpClient> {
@ -91,7 +91,6 @@ fun Application.parent() {
single<IdGenerateService> { TwitterSnowflakeIdGenerateService }
}
configureKoin(module)
configureHTTP()
configureStaticRouting()

View File

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

View File

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

View File

@ -12,6 +12,4 @@ open class Follow : Object {
) : super(add(type, "Follow"), name, actor) {
this.`object` = `object`
}
}

View File

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

View File

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

View File

@ -33,6 +33,4 @@ open class Key : Object {
result = 31 * result + (publicKeyPem?.hashCode() ?: 0)
return result
}
}

View File

@ -48,9 +48,6 @@ open class Note : Object {
return result
}
override fun toString(): String {
return "Note(id=$id, attributedTo=$attributedTo, content=$content, published=$published, to=$to) ${super.toString()}"
}
override fun toString(): String =
"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
}
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 {
if (this === other) return true
if (other !is Object) return false
@ -49,11 +38,16 @@ open class Object : JsonLd {
return result
}
override fun toString(): String {
return "Object(type=$type, name=$name, actor=$actor) ${super.toString()}"
override fun toString(): String = "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>>() {
@ -69,5 +63,4 @@ class TypeSerializer : JsonSerializer<List<String>>() {
gen?.writeEndArray()
}
}
}

View File

@ -10,6 +10,8 @@ open class Person : Object {
var publicKey: Key? = null
protected constructor() : super()
@Suppress("LongParameterList")
constructor(
type: List<String> = emptyList(),
name: String,
@ -56,6 +58,4 @@ open class Person : Object {
result = 31 * result + (publicKey?.hashCode() ?: 0)
return result
}
}

View File

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

View File

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

View File

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

View File

@ -2,6 +2,7 @@ package dev.usbharu.hideout.repository
import dev.usbharu.hideout.domain.model.hideout.entity.User
@Suppress("TooManyFunctions")
interface IUserRepository {
suspend fun save(user: User): User

View File

@ -1,7 +1,10 @@
package dev.usbharu.hideout.repository
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 kotlinx.coroutines.Dispatchers
import org.jetbrains.exposed.sql.*
@ -22,7 +25,6 @@ class PostRepositoryImpl(database: Database, private val idGenerateService: IdGe
override suspend fun insert(post: Post): PostEntity {
return query {
val generateId = idGenerateService.generateId()
val name = Users.select { Users.id eq post.userId }.single().toUser().name
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
}
return@query PostEntity(
generateId,
post.userId,
post.overview,
post.text,
post.createdAt,
post.visibility,
postUrl,
post.repostId,
post.replyId
id = generateId,
userId = post.userId,
overview = post.overview,
text = post.text,
createdAt = post.createdAt,
visibility = post.visibility,
url = postUrl,
repostId = post.repostId,
replyId = post.replyId
)
}
}

View File

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

View File

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

View File

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

View File

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

View File

@ -34,7 +34,6 @@ fun Routing.usersAP(activityPubUserService: ActivityPubUserService, userService:
class ContentTypeRouteSelector(private vararg val contentType: ContentType) : RouteSelector() {
override fun evaluate(context: RoutingResolveContext, segmentIndex: Int): RouteSelectorEvaluation {
context.call.application.log.debug("Accept: ${context.call.request.accept()}")
val requestContentType = context.call.request.accept() ?: return RouteSelectorEvaluation.FailedParameter
return if (requestContentType.split(",")
@ -45,5 +44,4 @@ class ContentTypeRouteSelector(private vararg val contentType: ContentType) : Ro
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.api.StatusForPost
import dev.usbharu.hideout.service.IPostService
import dev.usbharu.hideout.service.impl.PostService
import io.ktor.server.application.*
import io.ktor.server.request.*
import io.ktor.server.response.*

View File

@ -10,5 +10,4 @@ interface IUserAuthService {
suspend fun generateKeyPair(): KeyPair
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 java.time.Instant
@Suppress("MagicNumber")
open class SnowflakeIdGenerateService(private val baseTime: Long) : IdGenerateService {
var lastTimeStamp: Long = -1
var sequenceId: Int = 0
@ -13,22 +14,15 @@ open class SnowflakeIdGenerateService(private val baseTime:Long) : IdGenerateSer
@Throws(IllegalStateException::class)
override suspend fun generateId(): Long {
return mutex.withLock {
var timestamp = getTime()
if (timestamp < lastTimeStamp) {
while (timestamp <= lastTimeStamp) {
delay(1L)
timestamp = getTime()
}
timestamp = wait(timestamp)
// throw IllegalStateException(" $lastTimeStamp $timestamp ${lastTimeStamp-timestamp} ")
}
if (timestamp == lastTimeStamp) {
sequenceId++
if (sequenceId >= 4096) {
while (timestamp <= lastTimeStamp) {
delay(1L)
timestamp = getTime()
}
timestamp = wait(timestamp)
sequenceId = 0
}
} else {
@ -37,11 +31,16 @@ open class SnowflakeIdGenerateService(private val baseTime:Long) : IdGenerateSer
lastTimeStamp = timestamp
return@withLock (timestamp - baseTime).shl(22).or(1L.shl(12)).or(sequenceId.toLong())
}
}
private fun getTime(): Long {
return Instant.now().toEpochMilli()
private suspend fun wait(timestamp: Long): Long {
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
// 2010-11-04T01:42:54.657
@Suppress("MagicNumber")
object TwitterSnowflakeIdGenerateService : SnowflakeIdGenerateService(1288834974657L)

View File

@ -1,7 +1,7 @@
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.ap.Follow
import dev.usbharu.hideout.domain.model.job.ReceiveFollowJob
import kjob.core.job.JobProps

View File

@ -35,7 +35,6 @@ class ActivityPubNoteServiceImpl(
}
}
override suspend fun createNoteJob(props: JobProps<DeliverPostJob>) {
val actor = props[DeliverPostJob.actor]
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 kjob.core.dsl.JobContextWithProps
import kjob.core.job.JobProps
import org.slf4j.Logger
import org.slf4j.LoggerFactory
class ActivityPubServiceImpl(
@ -17,7 +18,7 @@ class ActivityPubServiceImpl(
private val activityPubNoteService: ActivityPubNoteService
) : ActivityPubService {
val logger = LoggerFactory.getLogger(this::class.java)
val logger: Logger = LoggerFactory.getLogger(this::class.java)
override fun parseActivity(json: String): ActivityType {
val readTree = Config.configData.objectMapper.readTree(json)
logger.debug("readTree: {}", readTree)
@ -33,6 +34,7 @@ class ActivityPubServiceImpl(
return ActivityType.values().first { it.name.equals(type.asText(), true) }
}
@Suppress("CyclomaticComplexMethod", "NotImplementedDeclaration")
override suspend fun processActivity(json: String, type: ActivityType): ActivityPubResponse {
return when (type) {
ActivityType.Accept -> TODO()
@ -80,6 +82,4 @@ class ActivityPubServiceImpl(
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.statement.*
import io.ktor.http.*
import org.slf4j.LoggerFactory
class ActivityPubUserServiceImpl(
private val userService: IUserService,
@ -23,7 +22,6 @@ class ActivityPubUserServiceImpl(
) :
ActivityPubUserService {
private val logger = LoggerFactory.getLogger(this::class.java)
override suspend fun getPersonByName(name: String): Person {
// TODO: JOINで書き直し
val userEntity = userService.findByNameLocalUser(name)
@ -79,7 +77,6 @@ class ActivityPubUserServiceImpl(
publicKeyPem = userEntity.publicKey
)
)
} catch (e: UserNotFoundException) {
val httpResponse = if (targetActor != null) {
httpClient.getAp(url, "$targetActor#pubkey")
@ -95,16 +92,17 @@ class ActivityPubUserServiceImpl(
name = person.preferredUsername
?: throw IllegalActivityPubObjectException("preferredUsername is null"),
domain = url.substringAfter("://").substringBeforeLast("/"),
screenName = (person.name ?: person.preferredUsername) ?: throw IllegalActivityPubObjectException("preferredUsername is null"),
description = person.summary ?: "",
screenName = (person.name ?: person.preferredUsername)
?: throw IllegalActivityPubObjectException("preferredUsername is null"),
description = person.summary.orEmpty(),
inbox = person.inbox ?: throw IllegalActivityPubObjectException("inbox is null"),
outbox = person.outbox ?: throw IllegalActivityPubObjectException("outbox is null"),
url = url,
publicKey = person.publicKey?.publicKeyPem ?: throw IllegalActivityPubObjectException("publicKey is null"),
publicKey = person.publicKey?.publicKeyPem
?: throw IllegalActivityPubObjectException("publicKey is null"),
)
)
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.entity.User
@Suppress("TooManyFunctions")
interface IUserService {
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.service.IPostService
import dev.usbharu.hideout.service.activitypub.ActivityPubNoteService
import dev.usbharu.hideout.service.job.JobQueueParentService
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)
override suspend fun create(post: Post) {
logger.debug("create post={}", post)
val postEntity = postRepository.insert(post)

View File

@ -12,14 +12,13 @@ class UserAuthService(
val userRepository: IUserRepository
) : IUserAuthService {
override fun hash(password: String): String {
val digest = sha256.digest(password.toByteArray(Charsets.UTF_8))
return hex(digest)
}
override suspend fun usernameAlreadyUse(username: String): Boolean {
userRepository.findByName(username) ?: return false
userRepository.findByName(username)
return true
}
@ -31,24 +30,25 @@ class UserAuthService(
override suspend fun generateKeyPair(): KeyPair {
val keyPairGenerator = KeyPairGenerator.getInstance("RSA")
keyPairGenerator.initialize(2048)
keyPairGenerator.initialize(keySize)
return keyPairGenerator.generateKeyPair()
}
companion object {
val sha256: MessageDigest = MessageDigest.getInstance("SHA-256")
const val keySize = 2048
const val pemSize = 64
}
}
fun PublicKey.toPem(): String {
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"
}
fun PrivateKey.toPem(): String {
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"
}

View File

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

View File

@ -1,10 +1,10 @@
package dev.usbharu.hideout.service.job
import kjob.core.Job
import kjob.core.dsl.JobContextWithProps
import kjob.core.dsl.JobRegisterContext
import kjob.core.dsl.KJobFunctions
import kjob.core.dsl.JobContextWithProps as JCWP
import kjob.core.dsl.JobRegisterContext as JRC
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
}.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) {
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 kjob.core.Job
import kjob.core.dsl.JobContextWithProps
import kjob.core.dsl.JobRegisterContext
import kjob.core.dsl.KJobFunctions
import kjob.core.kjob
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 {
@ -19,10 +19,11 @@ class KJobJobQueueWorkerService(private val database: Database) : JobQueueWorker
}.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 ->
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.repository.IUserRepository
import dev.usbharu.hideout.service.IUserAuthService
import io.ktor.http.*
import tech.barbero.http.message.signing.HttpMessage
import tech.barbero.http.message.signing.SignatureHeaderVerifier
class HttpSignatureVerifyServiceImpl(private val userAuthService: IUserRepository) : HttpSignatureVerifyService {
override fun verify(headers: Headers): Boolean {
val build = SignatureHeaderVerifier.builder().keyMap(KtorKeyMap(userAuthService)).build()
return true
build.verify(object : HttpMessage {
override fun headerValues(name: String?): MutableList<String> {
return name?.let { headers.getAll(it) }?.toMutableList() ?: mutableListOf()
}
override fun addHeader(name: String?, value: String?) {
TODO()
}
})
// build.verify(object : HttpMessage {
// override fun headerValues(name: String?): MutableList<String> {
// return name?.let { headers.getAll(it) }?.toMutableList() ?: mutableListOf()
// }
//
// override fun addHeader(name: String?, value: String?) {
// TODO()
// }
//
// })
}
}

View File

@ -3,6 +3,18 @@ package dev.usbharu.hideout.util
import io.ktor.http.*
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(
contentType: String,
subType: String,
@ -25,15 +37,5 @@ object HttpUtil {
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
}

View File

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

View File

@ -9,24 +9,6 @@ import java.time.Clock
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(
requireNotNull(config.connectionString),
requireNotNull(config.driverClassName)
@ -34,6 +16,7 @@ class ExposedKJob(config: Configuration) : BaseKJob<ExposedKJob.Configuration>(c
override val jobRepository: ExposedJobRepository
get() = ExposedJobRepository(database, config.jobTableName, Clock.systemUTC(), config.json)
override val lockRepository: ExposedLockRepository
get() = ExposedLockRepository(database, config, clock)
@ -47,4 +30,20 @@ class ExposedKJob(config: Configuration) : BaseKJob<ExposedKJob.Configuration>(c
super.shutdown()
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",
"classes": [
]
}
]

View File

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

View File

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

View File

@ -1,11 +1,8 @@
{
"lambdaCapturingTypes": [
],
"types": [
],
"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.actor)
}

View File

@ -33,7 +33,7 @@ class ActivityPubKtTest {
TODO()
}
override suspend fun findByNameAndDomain(name: String, domain: String): User? {
override suspend fun findByNameAndDomain(name: String, domain: String): User {
val keyPairGenerator = KeyPairGenerator.getInstance("RSA")
keyPairGenerator.initialize(1024)
val generateKeyPair = keyPairGenerator.generateKeyPair()

View File

@ -28,7 +28,7 @@ class KtorKeyMapTest {
TODO()
}
override suspend fun findByNameAndDomain(name: String, domain: String): User? {
override suspend fun findByNameAndDomain(name: String, domain: String): User {
val keyPairGenerator = KeyPairGenerator.getInstance("RSA")
keyPairGenerator.initialize(1024)
val generateKeyPair = keyPairGenerator.generateKeyPair()

View File

@ -109,7 +109,8 @@ class UserRepositoryTest {
}
})
val user = userRepository.save(
User(0L,
User(
0L,
"test",
"example.com",
"testUser",
@ -123,7 +124,8 @@ class UserRepositoryTest {
)
)
val follower = userRepository.save(
User(1L,
User(
1L,
"follower",
"follower.example.com",
"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.ActivityPubUserService
import dev.usbharu.hideout.service.impl.IUserService
import dev.usbharu.hideout.service.impl.UserService
import dev.usbharu.hideout.service.signature.HttpSignatureVerifyService
import io.ktor.client.request.*
import io.ktor.http.*
@ -51,7 +50,13 @@ class InboxRoutingKtTest {
application {
configureStatusPages()
configureSerialization()
configureRouting(httpSignatureVerifyService, activityPubService, userService, activityPubUserService,mock())
configureRouting(
httpSignatureVerifyService,
activityPubService,
userService,
activityPubUserService,
mock()
)
}
client.post("/inbox").let {
Assertions.assertEquals(HttpStatusCode.BadRequest, it.status)
@ -88,7 +93,13 @@ class InboxRoutingKtTest {
application {
configureStatusPages()
configureSerialization()
configureRouting(httpSignatureVerifyService, activityPubService, userService, activityPubUserService,mock())
configureRouting(
httpSignatureVerifyService,
activityPubService,
userService,
activityPubUserService,
mock()
)
}
client.post("/users/test/inbox").let {
Assertions.assertEquals(HttpStatusCode.BadRequest, it.status)