From db362fb48b28a73e799859c613446f46e1abfd15 Mon Sep 17 00:00:00 2001 From: usbharu <64310155+usbharu@users.noreply.github.com> Date: Sat, 1 Apr 2023 21:23:09 +0900 Subject: [PATCH 01/40] =?UTF-8?q?refactor:=20ActivityPub=E3=81=AE=E3=83=AB?= =?UTF-8?q?=E3=83=BC=E3=83=86=E3=82=A3=E3=83=B3=E3=82=B0=E5=AE=9A=E7=BE=A9?= =?UTF-8?q?=E3=82=92=E7=A7=BB=E5=8B=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../routing/activitypub/InboxRouting.kt | 25 +++++++++++++++++++ .../routing/activitypub/OutboxRouting.kt | 25 +++++++++++++++++++ .../routing/activitypub/UserRouting.kt | 25 +++++++++++++++++++ 3 files changed, 75 insertions(+) create mode 100644 src/main/kotlin/dev/usbharu/hideout/routing/activitypub/InboxRouting.kt create mode 100644 src/main/kotlin/dev/usbharu/hideout/routing/activitypub/OutboxRouting.kt create mode 100644 src/main/kotlin/dev/usbharu/hideout/routing/activitypub/UserRouting.kt diff --git a/src/main/kotlin/dev/usbharu/hideout/routing/activitypub/InboxRouting.kt b/src/main/kotlin/dev/usbharu/hideout/routing/activitypub/InboxRouting.kt new file mode 100644 index 00000000..d82ea289 --- /dev/null +++ b/src/main/kotlin/dev/usbharu/hideout/routing/activitypub/InboxRouting.kt @@ -0,0 +1,25 @@ +package dev.usbharu.hideout.routing.activitypub + +import io.ktor.server.application.* +import io.ktor.server.routing.* + +fun Routing.inbox(){ + + route("/inbox") { + get { + + } + post { + + } + } + route("/users/{name}/inbox"){ + get { + + } + post { + + } + } + +} diff --git a/src/main/kotlin/dev/usbharu/hideout/routing/activitypub/OutboxRouting.kt b/src/main/kotlin/dev/usbharu/hideout/routing/activitypub/OutboxRouting.kt new file mode 100644 index 00000000..cc8b133d --- /dev/null +++ b/src/main/kotlin/dev/usbharu/hideout/routing/activitypub/OutboxRouting.kt @@ -0,0 +1,25 @@ +package dev.usbharu.hideout.routing.activitypub + +import io.ktor.server.application.* +import io.ktor.server.routing.* + +fun Routing.outbox() { + + route("/outbox") { + get { + + } + post { + + } + } + route("/users/{name}/outbox"){ + get { + + } + post { + + } + } + +} diff --git a/src/main/kotlin/dev/usbharu/hideout/routing/activitypub/UserRouting.kt b/src/main/kotlin/dev/usbharu/hideout/routing/activitypub/UserRouting.kt new file mode 100644 index 00000000..a071c5f7 --- /dev/null +++ b/src/main/kotlin/dev/usbharu/hideout/routing/activitypub/UserRouting.kt @@ -0,0 +1,25 @@ +package dev.usbharu.hideout.routing.activitypub + +import dev.usbharu.hideout.util.HttpUtil.Activity +import io.ktor.http.* +import io.ktor.server.application.* +import io.ktor.server.request.* +import io.ktor.server.routing.* + +fun Routing.users(){ + route("/users/{name}"){ + createChild(ContentTypeRouteSelector(ContentType.Application.Activity)).handle { + } + } +} + +class ContentTypeRouteSelector(private val contentType: ContentType) : RouteSelector() { + override fun evaluate(context: RoutingResolveContext, segmentIndex: Int): RouteSelectorEvaluation { + return if (context.call.request.contentType() == contentType) { + RouteSelectorEvaluation.Constant + } else { + RouteSelectorEvaluation.Failed + } + } + +} From d93c8af0683695bff93b017830bd997c1bcf989c Mon Sep 17 00:00:00 2001 From: usbharu <64310155+usbharu@users.noreply.github.com> Date: Sat, 1 Apr 2023 21:23:27 +0900 Subject: [PATCH 02/40] =?UTF-8?q?refactor:=20Webfinger=E3=81=AE=E3=83=AB?= =?UTF-8?q?=E3=83=BC=E3=83=86=E3=82=A3=E3=83=B3=E3=82=B0=E5=AE=9A=E7=BE=A9?= =?UTF-8?q?=E3=82=92=E7=A7=BB=E5=8B=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../hideout/routing/wellknown/WebfingerRouting.kt | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 src/main/kotlin/dev/usbharu/hideout/routing/wellknown/WebfingerRouting.kt diff --git a/src/main/kotlin/dev/usbharu/hideout/routing/wellknown/WebfingerRouting.kt b/src/main/kotlin/dev/usbharu/hideout/routing/wellknown/WebfingerRouting.kt new file mode 100644 index 00000000..62bf0c81 --- /dev/null +++ b/src/main/kotlin/dev/usbharu/hideout/routing/wellknown/WebfingerRouting.kt @@ -0,0 +1,11 @@ +package dev.usbharu.hideout.routing.wellknown + +import io.ktor.server.routing.* + +fun Routing.webfinger(){ + route("/.well-known/webfinger"){ + get { + + } + } +} From 07500651ae054939e15338e523e132d761651b2f Mon Sep 17 00:00:00 2001 From: usbharu <64310155+usbharu@users.noreply.github.com> Date: Wed, 5 Apr 2023 16:59:16 +0900 Subject: [PATCH 03/40] =?UTF-8?q?feat:=20KJob=E3=82=92=E5=B0=8E=E5=85=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle.kts | 10 + .../hideout/service/job/JobQueueService.kt | 10 + .../hideout/service/job/JobWorkerService.kt | 10 + .../service/job/KJobJobQueueService.kt | 24 ++ .../service/job/KJobJobWorkerService.kt | 28 ++ .../kjob/exposed/ExposedJobRepository.kt | 290 ++++++++++++++++++ .../dev/usbharu/kjob/exposed/ExposedKJob.kt | 50 +++ .../kjob/exposed/ExposedLockRepository.kt | 74 +++++ 8 files changed, 496 insertions(+) create mode 100644 src/main/kotlin/dev/usbharu/hideout/service/job/JobQueueService.kt create mode 100644 src/main/kotlin/dev/usbharu/hideout/service/job/JobWorkerService.kt create mode 100644 src/main/kotlin/dev/usbharu/hideout/service/job/KJobJobQueueService.kt create mode 100644 src/main/kotlin/dev/usbharu/hideout/service/job/KJobJobWorkerService.kt create mode 100644 src/main/kotlin/dev/usbharu/kjob/exposed/ExposedJobRepository.kt create mode 100644 src/main/kotlin/dev/usbharu/kjob/exposed/ExposedKJob.kt create mode 100644 src/main/kotlin/dev/usbharu/kjob/exposed/ExposedLockRepository.kt diff --git a/build.gradle.kts b/build.gradle.kts index 54780dc5..b7e81f91 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -28,6 +28,14 @@ repositories { mavenCentral() } +kotlin { + target { + compilations.all { + kotlinOptions.jvmTarget = JavaVersion.VERSION_11.toString() + } + } +} + dependencies { implementation("io.ktor:ktor-server-core-jvm:$ktor_version") implementation("io.ktor:ktor-server-auth-jvm:$ktor_version") @@ -63,6 +71,8 @@ dependencies { implementation("tech.barbero.http-messages-signing:http-messages-signing-core:1.0.0") testImplementation("org.junit.jupiter:junit-jupiter:5.8.1") + + implementation("org.drewcarlson:kjob-core:0.6.0") } jib { diff --git a/src/main/kotlin/dev/usbharu/hideout/service/job/JobQueueService.kt b/src/main/kotlin/dev/usbharu/hideout/service/job/JobQueueService.kt new file mode 100644 index 00000000..94486f45 --- /dev/null +++ b/src/main/kotlin/dev/usbharu/hideout/service/job/JobQueueService.kt @@ -0,0 +1,10 @@ +package dev.usbharu.hideout.service.job + +import kjob.core.Job +import kjob.core.dsl.ScheduleContext + +interface JobQueueService { + + fun init(jobDefines:List) + suspend fun schedule(job: J, block: ScheduleContext.(J) -> Unit) +} diff --git a/src/main/kotlin/dev/usbharu/hideout/service/job/JobWorkerService.kt b/src/main/kotlin/dev/usbharu/hideout/service/job/JobWorkerService.kt new file mode 100644 index 00000000..86e8d69c --- /dev/null +++ b/src/main/kotlin/dev/usbharu/hideout/service/job/JobWorkerService.kt @@ -0,0 +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 + +interface JobWorkerService { + fun init(defines: List>.(Job) -> KJobFunctions>>>) +} diff --git a/src/main/kotlin/dev/usbharu/hideout/service/job/KJobJobQueueService.kt b/src/main/kotlin/dev/usbharu/hideout/service/job/KJobJobQueueService.kt new file mode 100644 index 00000000..f09d22d0 --- /dev/null +++ b/src/main/kotlin/dev/usbharu/hideout/service/job/KJobJobQueueService.kt @@ -0,0 +1,24 @@ +package dev.usbharu.hideout.service.job + +import dev.usbharu.kjob.exposed.ExposedKJob +import kjob.core.Job +import kjob.core.KJob +import kjob.core.dsl.ScheduleContext +import kjob.core.kjob +import org.jetbrains.exposed.sql.Database + +class KJobJobQueueService(private val database: Database) : JobQueueService { + + val kjob: KJob = kjob(ExposedKJob) { + connectionDatabase = database + isWorker = false + }.start() + + override fun init(jobDefines: List) { + + } + + override suspend fun schedule(job: J,block:ScheduleContext.(J)->Unit) { + kjob.schedule(job,block) + } +} diff --git a/src/main/kotlin/dev/usbharu/hideout/service/job/KJobJobWorkerService.kt b/src/main/kotlin/dev/usbharu/hideout/service/job/KJobJobWorkerService.kt new file mode 100644 index 00000000..5ee5a6ba --- /dev/null +++ b/src/main/kotlin/dev/usbharu/hideout/service/job/KJobJobWorkerService.kt @@ -0,0 +1,28 @@ +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 + +class KJobJobWorkerService(private val database: Database) : JobWorkerService { + + val kjob by lazy { + kjob(ExposedKJob) { + connectionDatabase = database + nonBlockingMaxJobs = 10 + blockingMaxJobs = 10 + jobExecutionPeriodInSeconds = 10 + }.start() + } + + override fun init(defines: List>.(Job) -> KJobFunctions>>>) { + defines.forEach { job -> + kjob.register(job.first, job.second) + } + } + +} diff --git a/src/main/kotlin/dev/usbharu/kjob/exposed/ExposedJobRepository.kt b/src/main/kotlin/dev/usbharu/kjob/exposed/ExposedJobRepository.kt new file mode 100644 index 00000000..6533358a --- /dev/null +++ b/src/main/kotlin/dev/usbharu/kjob/exposed/ExposedJobRepository.kt @@ -0,0 +1,290 @@ +package dev.usbharu.kjob.exposed + +import kjob.core.job.JobProgress +import kjob.core.job.JobSettings +import kjob.core.job.JobStatus +import kjob.core.job.ScheduledJob +import kjob.core.repository.JobRepository +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.asFlow +import kotlinx.serialization.encodeToString +import kotlinx.serialization.json.* +import org.jetbrains.exposed.dao.id.LongIdTable +import org.jetbrains.exposed.sql.* +import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq +import org.jetbrains.exposed.sql.SqlExpressionBuilder.inList +import org.jetbrains.exposed.sql.SqlExpressionBuilder.isNull +import org.jetbrains.exposed.sql.SqlExpressionBuilder.plus +import org.jetbrains.exposed.sql.transactions.experimental.newSuspendedTransaction +import org.jetbrains.exposed.sql.transactions.transaction +import java.time.Clock +import java.time.Instant +import java.util.* + +class ExposedJobRepository( + private val database: Database, + private val tableName: String, + private val clock: Clock, + private val json: Json +) : + JobRepository { + + class Jobs(tableName: String) : LongIdTable(tableName) { + val status = text("status") + val runAt = long("runAt").nullable() + val statusMessage = text("statusMessage").nullable() + val retries = integer("retries") + val kjobId = char("kjobId", 36).nullable() + val createdAt = long("createdAt") + val updatedAt = long("updatedAt") + val jobId = text("jobId") + val name = text("name") + val properties = text("properties").nullable() + val step = integer("step") + val max = integer("max").nullable() + val startedAt = long("startedAt").nullable() + val completedAt = long("completedAt").nullable() + } + + val jobs: Jobs = Jobs(tableName) + + fun createTable() { + transaction(database) { + SchemaUtils.create(jobs) + } + } + + suspend fun query(block: suspend () -> T): T = newSuspendedTransaction(Dispatchers.IO) { block() } + + + override suspend fun completeProgress(id: String): Boolean { + val now = Instant.now(clock).toEpochMilli() + return query { + jobs.update({ jobs.id eq id.toLong() }) { + it[jobs.completedAt] = now + it[jobs.updatedAt] = now + } == 1 + } + } + + override suspend fun exist(jobId: String): Boolean { + return query { + jobs.select(jobs.jobId eq jobId).empty().not() + } + } + + override suspend fun findNext(names: Set, status: Set, limit: Int): Flow { + return query { + jobs.select( + jobs.status.inList(list = status.map { it.name }) + .and(if (names.isEmpty()) Op.TRUE else jobs.name.inList(names)) + ).limit(limit) + .map { it.toScheduledJob() }.asFlow() + } + } + + 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() }) { + it[jobs.status] = JobStatus.CREATED.name + it[jobs.statusMessage] = null + it[jobs.kjobId] = null + it[jobs.step] = 0 + it[jobs.max] = null + it[jobs.startedAt] = null + it[jobs.completedAt] = null + it[jobs.updatedAt] = Instant.now(clock).toEpochMilli() + } == 1 + } + } + + 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)) + val id = query { + jobs.insert { + it[jobs.status] = scheduledJob.status.name + it[jobs.createdAt] = scheduledJob.createdAt.toEpochMilli() + it[jobs.updatedAt] = scheduledJob.updatedAt.toEpochMilli() + it[jobs.jobId] = scheduledJob.settings.id + it[jobs.name] = scheduledJob.settings.name + it[jobs.properties] = scheduledJob.settings.properties.stringify() + it[jobs.runAt] = scheduledJob.runAt?.toEpochMilli() + it[jobs.statusMessage] = null + it[jobs.retries] = 0 + it[jobs.kjobId] = null + it[jobs.step] = 0 + it[jobs.max] = null + it[jobs.startedAt] = null + it[jobs.completedAt] = null + }[jobs.id].value + } + return scheduledJob.copy(id = id.toString()) + } + + override suspend fun setProgressMax(id: String, max: Long): Boolean { + val now = Instant.now(clock).toEpochMilli() + return query { + jobs.update({ jobs.id eq id.toLong() }) { + it[jobs.max] = max.toInt() + it[jobs.updatedAt] = now + } == 1 + } + } + + override suspend fun startProgress(id: String): Boolean { + val now = Instant.now(clock).toEpochMilli() + return query { + jobs.update({ jobs.id eq id.toLong() }) { + it[jobs.startedAt] = now + it[jobs.updatedAt] = now + } == 1 + } + } + + override suspend fun stepProgress(id: String, step: Long): Boolean { + val now = Instant.now(clock).toEpochMilli() + return query { + jobs.update({ jobs.id eq id.toLong() }) { + it[jobs.step] = jobs.step + step.toInt() + it[jobs.updatedAt] = now + } == 1 + } + } + + override suspend fun update( + id: String, + oldKjobId: UUID?, + kjobId: UUID?, + status: JobStatus, + statusMessage: String?, + 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() }) { + it[jobs.status] = status.name + it[jobs.retries] = retries + it[jobs.updatedAt] = Instant.now(clock).toEpochMilli() + it[jobs.id] = id.toLong() + it[jobs.statusMessage] = statusMessage + it[jobs.kjobId] = kjobId.toString() + } == 1 + } + } + + private fun String?.parseJsonMap(): Map { + this ?: return emptyMap() + return json.parseToJsonElement(this).jsonObject.mapValues { (_, el) -> + if (el is JsonObject) { + val t = el["t"]?.jsonPrimitive?.content ?: error("Cannot get jsonPrimitive") + val value = el["v"]?.jsonArray ?: error("Cannot get jsonArray") + when (t) { + "s" -> value.map { it.jsonPrimitive.content } + "d" -> value.map { it.jsonPrimitive.double } + "l" -> value.map { it.jsonPrimitive.long } + "i" -> value.map { it.jsonPrimitive.int } + "b" -> value.map { it.jsonPrimitive.boolean } + else -> error("Unknown type prefix '$t'") + }.toList() + } else { + val content = el.jsonPrimitive.content + val t = content.substringBefore(':') + val value = content.substringAfter(':') + when (t) { + "s" -> value + "d" -> value.toDouble() + "l" -> value.toLong() + "i" -> value.toInt() + "b" -> value.toBoolean() + else -> error("Unknown type prefix '$t'") + } + } + } + } + + private fun Map.stringify(): String? { + if (isEmpty()) { + return null + } + + fun listSerialize(value: List<*>): JsonElement { + return if (value.isEmpty()) { + buildJsonObject { + put("t", "s") + putJsonArray("v") {} + } + } else { + val (t, values) = when (val item = value.first()) { + is Double -> "d" to (value as List).map(::JsonPrimitive) + is Long -> "l" to (value as List).map(::JsonPrimitive) + is Int -> "i" to (value as List).map(::JsonPrimitive) + is String -> "s" to (value as List).map(::JsonPrimitive) + is Boolean -> "b" to (value as List).map(::JsonPrimitive) + else -> error("Cannot serialize unsupported list property value: $value") + } + buildJsonObject { + put("t", t) + put("v", JsonArray(values)) + } + } + } + + fun createJsonPrimitive(string: String, value: Any) = JsonPrimitive("$string:$value") + + val jsonObject = JsonObject( + mapValues { (_, value) -> + when (value) { + is List<*> -> listSerialize(value) + is Double -> createJsonPrimitive("d", value) + is Long -> createJsonPrimitive("l", value) + is Int -> createJsonPrimitive("i", value) + is String -> createJsonPrimitive("s", value) + is Boolean -> createJsonPrimitive("b", value) + else -> error("Cannot serialize unsupported property value: $value") + } + } + ) + return json.encodeToString(jsonObject) + } + + private fun ResultRow.toScheduledJob(): ScheduledJob { + val single = this + jobs.run { + return ScheduledJob( + id = single[this.id].value.toString(), + status = JobStatus.valueOf(single[status]), + runAt = single[runAt]?.let { Instant.ofEpochMilli(it) }, + statusMessage = single[statusMessage], + retries = single[retries], + kjobId = single[kjobId]?.let { + try { + UUID.fromString(it) + } catch (e: IllegalArgumentException) { + null + } + }, + createdAt = Instant.ofEpochMilli(single[createdAt]), + updatedAt = Instant.ofEpochMilli(single[updatedAt]), + settings = JobSettings( + id = single[jobId], + name = single[name], + properties = single[properties].parseJsonMap() + ), + progress = JobProgress( + step = single[step].toLong(), + max = single[max]?.toLong(), + startedAt = single[startedAt]?.let { Instant.ofEpochMilli(it) }, + completedAt = single[completedAt]?.let { Instant.ofEpochMilli(it) } + ) + ) + } + } +} diff --git a/src/main/kotlin/dev/usbharu/kjob/exposed/ExposedKJob.kt b/src/main/kotlin/dev/usbharu/kjob/exposed/ExposedKJob.kt new file mode 100644 index 00000000..76d00008 --- /dev/null +++ b/src/main/kotlin/dev/usbharu/kjob/exposed/ExposedKJob.kt @@ -0,0 +1,50 @@ +package dev.usbharu.kjob.exposed + +import kjob.core.BaseKJob +import kjob.core.KJob +import kjob.core.KJobFactory +import kotlinx.coroutines.runBlocking +import org.jetbrains.exposed.sql.Database +import java.time.Clock + +class ExposedKJob(config: Configuration) : BaseKJob(config) { + + companion object : KJobFactory { + 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) + ) + + override val jobRepository: ExposedJobRepository + get() = ExposedJobRepository(database, config.jobTableName, Clock.systemUTC(), config.json) + override val lockRepository: ExposedLockRepository + get() = ExposedLockRepository(database, config, clock) + + override fun start(): KJob { + jobRepository.createTable() + lockRepository.createTable() + return super.start() + } + + override fun shutdown() = runBlocking { + super.shutdown() + lockRepository.clearExpired() + } +} diff --git a/src/main/kotlin/dev/usbharu/kjob/exposed/ExposedLockRepository.kt b/src/main/kotlin/dev/usbharu/kjob/exposed/ExposedLockRepository.kt new file mode 100644 index 00000000..76d6fa44 --- /dev/null +++ b/src/main/kotlin/dev/usbharu/kjob/exposed/ExposedLockRepository.kt @@ -0,0 +1,74 @@ +package dev.usbharu.kjob.exposed + +import kjob.core.job.Lock +import kjob.core.repository.LockRepository +import kotlinx.coroutines.Dispatchers +import org.jetbrains.exposed.dao.id.UUIDTable +import org.jetbrains.exposed.sql.* +import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq +import org.jetbrains.exposed.sql.SqlExpressionBuilder.greater +import org.jetbrains.exposed.sql.transactions.experimental.newSuspendedTransaction +import org.jetbrains.exposed.sql.transactions.transaction +import java.time.Clock +import java.time.Instant +import java.util.* +import kotlin.time.Duration.Companion.minutes + +class ExposedLockRepository( + private val database: Database, + private val config: ExposedKJob.Configuration, + private val clock: Clock +) : LockRepository { + + class Locks(tableName: String) : UUIDTable(tableName) { + val updatedAt = long("updatedAt") + val expiresAt = long("expiresAt") + } + + val locks: Locks = Locks(config.lockTableName) + + fun createTable() { + transaction(database) { + SchemaUtils.create(locks) + } + } + + suspend fun query(block: suspend () -> T): T = newSuspendedTransaction(Dispatchers.IO) { block() } + + override suspend fun exists(id: UUID): Boolean { + val now = Instant.now(clock) + return query { + locks.select(locks.id eq id and locks.expiresAt.greater(now.toEpochMilli())).empty().not() + } + } + + override suspend fun ping(id: UUID): Lock { + val now = Instant.now(clock) + val expiresAt = now.plusSeconds(config.expireLockInMinutes.minutes.inWholeSeconds) + val lock = Lock(id, now) + query { + if (locks.select(locks.id eq id).limit(1) + .map { Lock(it[locks.id].value, Instant.ofEpochMilli(it[locks.expiresAt])) }.isEmpty() + ) { + locks.insert { + it[locks.id] = id + it[locks.updatedAt] = now.toEpochMilli() + it[locks.expiresAt] = expiresAt.toEpochMilli() + } + } else { + locks.update({ locks.id eq id }) { + it[locks.updatedAt] = now.toEpochMilli() + it[locks.expiresAt] = expiresAt.toEpochMilli() + } + } + } + return lock + } + + suspend fun clearExpired() { + val now = Instant.now(clock).toEpochMilli() + query { + locks.deleteWhere { locks.expiresAt greater now } + } + } +} From dd408f86dbc6ffa1dad64295bd0d2ca077fc651a Mon Sep 17 00:00:00 2001 From: usbharu <64310155+usbharu@users.noreply.github.com> Date: Wed, 5 Apr 2023 16:59:59 +0900 Subject: [PATCH 04/40] =?UTF-8?q?refactor:=20=E5=88=A9=E7=94=A8=E3=81=97?= =?UTF-8?q?=E3=81=AA=E3=81=84=E4=BA=88=E5=AE=9A=E3=81=AE=E3=82=AF=E3=83=A9?= =?UTF-8?q?=E3=82=B9=E3=82=92=E7=A7=BB=E5=8B=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../kotlin/dev/usbharu/hideout/Application.kt | 1 + .../usbharu/hideout/plugins/ActivityPub.kt | 3 +- .../usbharu/hideout/routing/UserRouting.kt | 5 +-- .../hideout/routing/WellKnownRouting.kt | 2 +- .../hideout/routing/userActivityPubRouting.kt | 4 +- .../service/activitypub/ActivityPubService.kt | 39 +++++++++++++++++++ .../service/{ => impl}/ActivityPubService.kt | 5 +-- .../{ => impl}/ActivityPubUserService.kt | 4 +- .../service/{ => impl}/HttpSignService.kt | 2 +- .../service/{ => impl}/UserAuthService.kt | 4 +- .../hideout/service/{ => impl}/UserService.kt | 2 +- .../service/{ => impl}/WebFingerService.kt | 3 +- .../hideout/plugins/ActivityPubKtTest.kt | 5 +-- .../usbharu/hideout/plugins/KtorKeyMapTest.kt | 5 +-- 14 files changed, 61 insertions(+), 23 deletions(-) create mode 100644 src/main/kotlin/dev/usbharu/hideout/service/activitypub/ActivityPubService.kt rename src/main/kotlin/dev/usbharu/hideout/service/{ => impl}/ActivityPubService.kt (74%) rename src/main/kotlin/dev/usbharu/hideout/service/{ => impl}/ActivityPubUserService.kt (93%) rename src/main/kotlin/dev/usbharu/hideout/service/{ => impl}/HttpSignService.kt (68%) rename src/main/kotlin/dev/usbharu/hideout/service/{ => impl}/UserAuthService.kt (97%) rename src/main/kotlin/dev/usbharu/hideout/service/{ => impl}/UserService.kt (96%) rename src/main/kotlin/dev/usbharu/hideout/service/{ => impl}/WebFingerService.kt (96%) diff --git a/src/main/kotlin/dev/usbharu/hideout/Application.kt b/src/main/kotlin/dev/usbharu/hideout/Application.kt index 56f3f1d8..e99bef82 100644 --- a/src/main/kotlin/dev/usbharu/hideout/Application.kt +++ b/src/main/kotlin/dev/usbharu/hideout/Application.kt @@ -12,6 +12,7 @@ import dev.usbharu.hideout.repository.UserAuthRepository import dev.usbharu.hideout.repository.UserRepository import dev.usbharu.hideout.routing.* import dev.usbharu.hideout.service.* +import dev.usbharu.hideout.service.impl.* import io.ktor.client.* import io.ktor.client.engine.cio.* import io.ktor.client.plugins.contentnegotiation.* diff --git a/src/main/kotlin/dev/usbharu/hideout/plugins/ActivityPub.kt b/src/main/kotlin/dev/usbharu/hideout/plugins/ActivityPub.kt index 7d0a41d2..2c8d1d52 100644 --- a/src/main/kotlin/dev/usbharu/hideout/plugins/ActivityPub.kt +++ b/src/main/kotlin/dev/usbharu/hideout/plugins/ActivityPub.kt @@ -3,7 +3,7 @@ package dev.usbharu.hideout.plugins import dev.usbharu.hideout.ap.JsonLd import dev.usbharu.hideout.config.Config import dev.usbharu.hideout.service.IUserAuthService -import dev.usbharu.hideout.service.UserAuthService +import dev.usbharu.hideout.service.impl.UserAuthService import dev.usbharu.hideout.util.HttpUtil.Activity import io.ktor.client.* import io.ktor.client.plugins.api.* @@ -12,7 +12,6 @@ import io.ktor.client.statement.* import io.ktor.http.* import io.ktor.server.application.* import io.ktor.server.response.* -import io.ktor.util.* import kotlinx.coroutines.runBlocking import tech.barbero.http.message.signing.HttpMessage import tech.barbero.http.message.signing.HttpMessageSigner diff --git a/src/main/kotlin/dev/usbharu/hideout/routing/UserRouting.kt b/src/main/kotlin/dev/usbharu/hideout/routing/UserRouting.kt index d73c8ad0..d43b070d 100644 --- a/src/main/kotlin/dev/usbharu/hideout/routing/UserRouting.kt +++ b/src/main/kotlin/dev/usbharu/hideout/routing/UserRouting.kt @@ -4,8 +4,8 @@ import dev.usbharu.hideout.domain.model.User import dev.usbharu.hideout.plugins.UserSession import dev.usbharu.hideout.plugins.respondAp import dev.usbharu.hideout.plugins.tokenAuth -import dev.usbharu.hideout.service.ActivityPubUserService -import dev.usbharu.hideout.service.UserService +import dev.usbharu.hideout.service.impl.ActivityPubUserService +import dev.usbharu.hideout.service.impl.UserService import dev.usbharu.hideout.util.HttpUtil import io.ktor.http.* import io.ktor.server.application.* @@ -13,7 +13,6 @@ import io.ktor.server.auth.* import io.ktor.server.request.* import io.ktor.server.response.* import io.ktor.server.routing.* -import io.ktor.server.sessions.* @Suppress("unused") fun Application.user(userService: UserService, activityPubUserService: ActivityPubUserService) { diff --git a/src/main/kotlin/dev/usbharu/hideout/routing/WellKnownRouting.kt b/src/main/kotlin/dev/usbharu/hideout/routing/WellKnownRouting.kt index 41fc6fd1..f1e6a7a6 100644 --- a/src/main/kotlin/dev/usbharu/hideout/routing/WellKnownRouting.kt +++ b/src/main/kotlin/dev/usbharu/hideout/routing/WellKnownRouting.kt @@ -1,7 +1,7 @@ package dev.usbharu.hideout.routing import dev.usbharu.hideout.config.Config -import dev.usbharu.hideout.service.UserService +import dev.usbharu.hideout.service.impl.UserService import dev.usbharu.hideout.util.HttpUtil.Activity import io.ktor.http.* import io.ktor.server.application.* diff --git a/src/main/kotlin/dev/usbharu/hideout/routing/userActivityPubRouting.kt b/src/main/kotlin/dev/usbharu/hideout/routing/userActivityPubRouting.kt index 8993ecda..73d54fa8 100644 --- a/src/main/kotlin/dev/usbharu/hideout/routing/userActivityPubRouting.kt +++ b/src/main/kotlin/dev/usbharu/hideout/routing/userActivityPubRouting.kt @@ -3,8 +3,8 @@ package dev.usbharu.hideout.routing import com.fasterxml.jackson.module.kotlin.readValue import dev.usbharu.hideout.ap.Follow import dev.usbharu.hideout.config.Config -import dev.usbharu.hideout.service.ActivityPubService -import dev.usbharu.hideout.service.ActivityPubUserService +import dev.usbharu.hideout.service.impl.ActivityPubService +import dev.usbharu.hideout.service.impl.ActivityPubUserService import dev.usbharu.hideout.util.HttpUtil import io.ktor.http.* import io.ktor.server.application.* diff --git a/src/main/kotlin/dev/usbharu/hideout/service/activitypub/ActivityPubService.kt b/src/main/kotlin/dev/usbharu/hideout/service/activitypub/ActivityPubService.kt new file mode 100644 index 00000000..070046a3 --- /dev/null +++ b/src/main/kotlin/dev/usbharu/hideout/service/activitypub/ActivityPubService.kt @@ -0,0 +1,39 @@ +package dev.usbharu.hideout.service.activitypub + +interface ActivityPubService { + fun parseActivity(json:String): ActivityType + + fun processActivity(json:String, type: ActivityType) +} + +enum class ActivityType { + Accept, + Add, + Announce, + Arrive, + Block, + Create, + Delete, + Dislike, + Flag, + Follow, + Ignore, + Invite, + Join, + Leave, + Like, + Listen, + Move, + Offer, + Question, + Reject, + Read, + Remove, + TentativeReject, + TentativeAccept, + Travel, + Undo, + Update, + View, + Other +} diff --git a/src/main/kotlin/dev/usbharu/hideout/service/ActivityPubService.kt b/src/main/kotlin/dev/usbharu/hideout/service/impl/ActivityPubService.kt similarity index 74% rename from src/main/kotlin/dev/usbharu/hideout/service/ActivityPubService.kt rename to src/main/kotlin/dev/usbharu/hideout/service/impl/ActivityPubService.kt index bc6c9f17..e693bd57 100644 --- a/src/main/kotlin/dev/usbharu/hideout/service/ActivityPubService.kt +++ b/src/main/kotlin/dev/usbharu/hideout/service/impl/ActivityPubService.kt @@ -1,6 +1,5 @@ -package dev.usbharu.hideout.service +package dev.usbharu.hideout.service.impl -import com.fasterxml.jackson.databind.ObjectMapper import dev.usbharu.hideout.config.Config class ActivityPubService() { @@ -10,7 +9,7 @@ class ActivityPubService() { Undo } - fun switchApType(json:String):ActivityType{ + fun switchApType(json:String): ActivityType { val typeAsText = Config.configData.objectMapper.readTree(json).get("type").asText() return when(typeAsText){ "Follow" -> ActivityType.Follow diff --git a/src/main/kotlin/dev/usbharu/hideout/service/ActivityPubUserService.kt b/src/main/kotlin/dev/usbharu/hideout/service/impl/ActivityPubUserService.kt similarity index 93% rename from src/main/kotlin/dev/usbharu/hideout/service/ActivityPubUserService.kt rename to src/main/kotlin/dev/usbharu/hideout/service/impl/ActivityPubUserService.kt index 4c2b0f82..fd26123c 100644 --- a/src/main/kotlin/dev/usbharu/hideout/service/ActivityPubUserService.kt +++ b/src/main/kotlin/dev/usbharu/hideout/service/impl/ActivityPubUserService.kt @@ -1,8 +1,10 @@ -package dev.usbharu.hideout.service +package dev.usbharu.hideout.service.impl import dev.usbharu.hideout.ap.* import dev.usbharu.hideout.config.Config import dev.usbharu.hideout.plugins.postAp +import dev.usbharu.hideout.service.IUserAuthService +import dev.usbharu.hideout.service.IWebFingerService import io.ktor.client.* class ActivityPubUserService( diff --git a/src/main/kotlin/dev/usbharu/hideout/service/HttpSignService.kt b/src/main/kotlin/dev/usbharu/hideout/service/impl/HttpSignService.kt similarity index 68% rename from src/main/kotlin/dev/usbharu/hideout/service/HttpSignService.kt rename to src/main/kotlin/dev/usbharu/hideout/service/impl/HttpSignService.kt index 440ee426..07cf8941 100644 --- a/src/main/kotlin/dev/usbharu/hideout/service/HttpSignService.kt +++ b/src/main/kotlin/dev/usbharu/hideout/service/impl/HttpSignService.kt @@ -1,4 +1,4 @@ -package dev.usbharu.hideout.service +package dev.usbharu.hideout.service.impl import java.security.PrivateKey diff --git a/src/main/kotlin/dev/usbharu/hideout/service/UserAuthService.kt b/src/main/kotlin/dev/usbharu/hideout/service/impl/UserAuthService.kt similarity index 97% rename from src/main/kotlin/dev/usbharu/hideout/service/UserAuthService.kt rename to src/main/kotlin/dev/usbharu/hideout/service/impl/UserAuthService.kt index ac60a847..cf5b0a48 100644 --- a/src/main/kotlin/dev/usbharu/hideout/service/UserAuthService.kt +++ b/src/main/kotlin/dev/usbharu/hideout/service/impl/UserAuthService.kt @@ -1,13 +1,13 @@ -package dev.usbharu.hideout.service +package dev.usbharu.hideout.service.impl import dev.usbharu.hideout.config.Config import dev.usbharu.hideout.domain.model.User import dev.usbharu.hideout.domain.model.UserAuthentication import dev.usbharu.hideout.domain.model.UserAuthenticationEntity -import dev.usbharu.hideout.domain.model.Users.screenName import dev.usbharu.hideout.exception.UserNotFoundException import dev.usbharu.hideout.repository.IUserAuthRepository import dev.usbharu.hideout.repository.IUserRepository +import dev.usbharu.hideout.service.IUserAuthService import io.ktor.util.* import java.security.KeyPair import java.security.KeyPairGenerator diff --git a/src/main/kotlin/dev/usbharu/hideout/service/UserService.kt b/src/main/kotlin/dev/usbharu/hideout/service/impl/UserService.kt similarity index 96% rename from src/main/kotlin/dev/usbharu/hideout/service/UserService.kt rename to src/main/kotlin/dev/usbharu/hideout/service/impl/UserService.kt index 3183b212..4736338a 100644 --- a/src/main/kotlin/dev/usbharu/hideout/service/UserService.kt +++ b/src/main/kotlin/dev/usbharu/hideout/service/impl/UserService.kt @@ -1,4 +1,4 @@ -package dev.usbharu.hideout.service +package dev.usbharu.hideout.service.impl import dev.usbharu.hideout.domain.model.User import dev.usbharu.hideout.domain.model.UserEntity diff --git a/src/main/kotlin/dev/usbharu/hideout/service/WebFingerService.kt b/src/main/kotlin/dev/usbharu/hideout/service/impl/WebFingerService.kt similarity index 96% rename from src/main/kotlin/dev/usbharu/hideout/service/WebFingerService.kt rename to src/main/kotlin/dev/usbharu/hideout/service/impl/WebFingerService.kt index 9cc2cf52..8bf1c420 100644 --- a/src/main/kotlin/dev/usbharu/hideout/service/WebFingerService.kt +++ b/src/main/kotlin/dev/usbharu/hideout/service/impl/WebFingerService.kt @@ -1,8 +1,9 @@ -package dev.usbharu.hideout.service +package dev.usbharu.hideout.service.impl import dev.usbharu.hideout.ap.Person import dev.usbharu.hideout.domain.model.User import dev.usbharu.hideout.domain.model.UserEntity +import dev.usbharu.hideout.service.IWebFingerService import dev.usbharu.hideout.util.HttpUtil import dev.usbharu.hideout.webfinger.WebFinger import io.ktor.client.* diff --git a/src/test/kotlin/dev/usbharu/hideout/plugins/ActivityPubKtTest.kt b/src/test/kotlin/dev/usbharu/hideout/plugins/ActivityPubKtTest.kt index 8c7e7d76..36cefbbd 100644 --- a/src/test/kotlin/dev/usbharu/hideout/plugins/ActivityPubKtTest.kt +++ b/src/test/kotlin/dev/usbharu/hideout/plugins/ActivityPubKtTest.kt @@ -7,8 +7,8 @@ import dev.usbharu.hideout.domain.model.UserAuthenticationEntity import dev.usbharu.hideout.domain.model.UserEntity import dev.usbharu.hideout.repository.IUserAuthRepository import dev.usbharu.hideout.repository.IUserRepository -import dev.usbharu.hideout.service.UserAuthService -import dev.usbharu.hideout.service.toPem +import dev.usbharu.hideout.service.impl.UserAuthService +import dev.usbharu.hideout.service.impl.toPem import io.ktor.client.* import io.ktor.client.engine.mock.* import io.ktor.client.plugins.logging.* @@ -17,7 +17,6 @@ import org.junit.jupiter.api.Test import java.security.KeyPairGenerator import java.security.interfaces.RSAPrivateKey import java.security.interfaces.RSAPublicKey -import java.util.* class ActivityPubKtTest { @Test diff --git a/src/test/kotlin/dev/usbharu/hideout/plugins/KtorKeyMapTest.kt b/src/test/kotlin/dev/usbharu/hideout/plugins/KtorKeyMapTest.kt index 9f9a7cfe..3f56dd92 100644 --- a/src/test/kotlin/dev/usbharu/hideout/plugins/KtorKeyMapTest.kt +++ b/src/test/kotlin/dev/usbharu/hideout/plugins/KtorKeyMapTest.kt @@ -6,13 +6,12 @@ import dev.usbharu.hideout.domain.model.UserAuthenticationEntity import dev.usbharu.hideout.domain.model.UserEntity import dev.usbharu.hideout.repository.IUserAuthRepository import dev.usbharu.hideout.repository.IUserRepository -import dev.usbharu.hideout.service.UserAuthService -import dev.usbharu.hideout.service.toPem +import dev.usbharu.hideout.service.impl.UserAuthService +import dev.usbharu.hideout.service.impl.toPem import org.junit.jupiter.api.Test import java.security.KeyPairGenerator import java.security.interfaces.RSAPrivateKey import java.security.interfaces.RSAPublicKey -import java.util.* class KtorKeyMapTest { From a0ad41c5e9fdbb41980469684b12883b121e43b2 Mon Sep 17 00:00:00 2001 From: usbharu <64310155+usbharu@users.noreply.github.com> Date: Wed, 5 Apr 2023 17:00:20 +0900 Subject: [PATCH 05/40] =?UTF-8?q?test:=20Job=E5=AE=9A=E7=BE=A9=E8=BF=BD?= =?UTF-8?q?=E5=8A=A0=E3=81=AE=E3=83=86=E3=82=B9=E3=83=88=E3=82=92=E8=BF=BD?= =?UTF-8?q?=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../service/job/KJobJobWorkerServiceTest.kt | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 src/test/kotlin/dev/usbharu/hideout/service/job/KJobJobWorkerServiceTest.kt diff --git a/src/test/kotlin/dev/usbharu/hideout/service/job/KJobJobWorkerServiceTest.kt b/src/test/kotlin/dev/usbharu/hideout/service/job/KJobJobWorkerServiceTest.kt new file mode 100644 index 00000000..8ba24871 --- /dev/null +++ b/src/test/kotlin/dev/usbharu/hideout/service/job/KJobJobWorkerServiceTest.kt @@ -0,0 +1,16 @@ +package dev.usbharu.hideout.service.job + +import kjob.core.Job +import org.jetbrains.exposed.sql.Database +import org.junit.jupiter.api.Test + +class KJobJobWorkerServiceTest { + + object TestJob : Job("test-job") + + @Test + fun init() { + val kJobJobWorkerService = KJobJobWorkerService(Database.connect("jdbc:h2:mem:")) + kJobJobWorkerService.init(listOf(TestJob to { it -> execute { it as TestJob;println(it.propNames) } })) + } +} From 2e7cecdd13267baa4b605df7c1a1318952bdeb7c Mon Sep 17 00:00:00 2001 From: usbharu <64310155+usbharu@users.noreply.github.com> Date: Wed, 5 Apr 2023 17:54:00 +0900 Subject: [PATCH 06/40] =?UTF-8?q?feat:=20ActivityPub=E3=81=AEType=E3=83=91?= =?UTF-8?q?=E3=83=BC=E3=82=B5=E3=82=92=E4=BD=9C=E6=88=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../exception/ap/JsonParseException.kt | 8 +++ .../service/activitypub/ActivityPubService.kt | 2 +- .../activitypub/ActivityPubServiceImpl.kt | 55 +++++++++++++++++++ 3 files changed, 64 insertions(+), 1 deletion(-) create mode 100644 src/main/kotlin/dev/usbharu/hideout/exception/ap/JsonParseException.kt create mode 100644 src/main/kotlin/dev/usbharu/hideout/service/activitypub/ActivityPubServiceImpl.kt diff --git a/src/main/kotlin/dev/usbharu/hideout/exception/ap/JsonParseException.kt b/src/main/kotlin/dev/usbharu/hideout/exception/ap/JsonParseException.kt new file mode 100644 index 00000000..61208029 --- /dev/null +++ b/src/main/kotlin/dev/usbharu/hideout/exception/ap/JsonParseException.kt @@ -0,0 +1,8 @@ +package dev.usbharu.hideout.exception.ap + +class JsonParseException : IllegalArgumentException { + constructor() : super() + constructor(s: String?) : super(s) + constructor(message: String?, cause: Throwable?) : super(message, cause) + constructor(cause: Throwable?) : super(cause) +} diff --git a/src/main/kotlin/dev/usbharu/hideout/service/activitypub/ActivityPubService.kt b/src/main/kotlin/dev/usbharu/hideout/service/activitypub/ActivityPubService.kt index 070046a3..55d3eec4 100644 --- a/src/main/kotlin/dev/usbharu/hideout/service/activitypub/ActivityPubService.kt +++ b/src/main/kotlin/dev/usbharu/hideout/service/activitypub/ActivityPubService.kt @@ -1,7 +1,7 @@ package dev.usbharu.hideout.service.activitypub interface ActivityPubService { - fun parseActivity(json:String): ActivityType + fun parseActivity(json:String): List fun processActivity(json:String, type: ActivityType) } diff --git a/src/main/kotlin/dev/usbharu/hideout/service/activitypub/ActivityPubServiceImpl.kt b/src/main/kotlin/dev/usbharu/hideout/service/activitypub/ActivityPubServiceImpl.kt new file mode 100644 index 00000000..6e407bb3 --- /dev/null +++ b/src/main/kotlin/dev/usbharu/hideout/service/activitypub/ActivityPubServiceImpl.kt @@ -0,0 +1,55 @@ +package dev.usbharu.hideout.service.activitypub + +import com.fasterxml.jackson.databind.JsonNode +import dev.usbharu.hideout.config.Config +import dev.usbharu.hideout.exception.ap.JsonParseException + +class ActivityPubServiceImpl : ActivityPubService { + override fun parseActivity(json: String): List { + val readTree = Config.configData.objectMapper.readTree(json) + if (readTree.isObject.not()) { + throw JsonParseException("Json is not object.") + } + val type = readTree["type"] + if (type.isArray) { + return type.mapNotNull { jsonNode: JsonNode -> + ActivityType.values().filter { it.name.equals(jsonNode.toPrettyString(), true) } + }.flatten() + } + return ActivityType.values().filter { it.name.equals(type.toPrettyString(), true) } + } + + override fun processActivity(json: String, type: ActivityType) { + when (type) { + ActivityType.Accept -> TODO() + ActivityType.Add -> TODO() + ActivityType.Announce -> TODO() + ActivityType.Arrive -> TODO() + ActivityType.Block -> TODO() + ActivityType.Create -> TODO() + ActivityType.Delete -> TODO() + ActivityType.Dislike -> TODO() + ActivityType.Flag -> TODO() + ActivityType.Follow -> TODO() + ActivityType.Ignore -> TODO() + ActivityType.Invite -> TODO() + ActivityType.Join -> TODO() + ActivityType.Leave -> TODO() + ActivityType.Like -> TODO() + ActivityType.Listen -> TODO() + ActivityType.Move -> TODO() + ActivityType.Offer -> TODO() + ActivityType.Question -> TODO() + ActivityType.Reject -> TODO() + ActivityType.Read -> TODO() + ActivityType.Remove -> TODO() + ActivityType.TentativeReject -> TODO() + ActivityType.TentativeAccept -> TODO() + ActivityType.Travel -> TODO() + ActivityType.Undo -> TODO() + ActivityType.Update -> TODO() + ActivityType.View -> TODO() + ActivityType.Other -> TODO() + } + } +} From f02ca9e5b04263be17e7b407fefdaeb514304e68 Mon Sep 17 00:00:00 2001 From: usbharu <64310155+usbharu@users.noreply.github.com> Date: Wed, 5 Apr 2023 21:47:29 +0900 Subject: [PATCH 07/40] =?UTF-8?q?feat:=20ActivityPub=E9=96=A2=E4=BF=82?= =?UTF-8?q?=E3=81=AE=E3=83=AB=E3=83=BC=E3=83=86=E3=82=A3=E3=83=B3=E3=82=B0?= =?UTF-8?q?=E3=82=92=E6=95=B4=E5=82=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../exception/HttpSignatureVerifyException.kt | 8 +++++++ .../exception/{ap => }/JsonParseException.kt | 2 +- .../dev/usbharu/hideout/plugins/Routing.kt | 14 +++++++---- .../routing/activitypub/InboxRouting.kt | 21 +++++++++++++---- .../routing/activitypub/OutboxRouting.kt | 10 ++++---- .../routing/activitypub/UserRouting.kt | 8 ++++++- .../service/activitypub/ActivityPubService.kt | 2 +- .../activitypub/ActivityPubServiceImpl.kt | 10 ++++---- .../signature/HttpSignatureVerifyService.kt | 7 ++++++ .../HttpSignatureVerifyServiceImpl.kt | 23 +++++++++++++++++++ 10 files changed, 83 insertions(+), 22 deletions(-) create mode 100644 src/main/kotlin/dev/usbharu/hideout/exception/HttpSignatureVerifyException.kt rename src/main/kotlin/dev/usbharu/hideout/exception/{ap => }/JsonParseException.kt (85%) create mode 100644 src/main/kotlin/dev/usbharu/hideout/service/signature/HttpSignatureVerifyService.kt create mode 100644 src/main/kotlin/dev/usbharu/hideout/service/signature/HttpSignatureVerifyServiceImpl.kt diff --git a/src/main/kotlin/dev/usbharu/hideout/exception/HttpSignatureVerifyException.kt b/src/main/kotlin/dev/usbharu/hideout/exception/HttpSignatureVerifyException.kt new file mode 100644 index 00000000..d123025f --- /dev/null +++ b/src/main/kotlin/dev/usbharu/hideout/exception/HttpSignatureVerifyException.kt @@ -0,0 +1,8 @@ +package dev.usbharu.hideout.exception + +class HttpSignatureVerifyException : IllegalArgumentException { + constructor() : super() + constructor(s: String?) : super(s) + constructor(message: String?, cause: Throwable?) : super(message, cause) + constructor(cause: Throwable?) : super(cause) +} diff --git a/src/main/kotlin/dev/usbharu/hideout/exception/ap/JsonParseException.kt b/src/main/kotlin/dev/usbharu/hideout/exception/JsonParseException.kt similarity index 85% rename from src/main/kotlin/dev/usbharu/hideout/exception/ap/JsonParseException.kt rename to src/main/kotlin/dev/usbharu/hideout/exception/JsonParseException.kt index 61208029..d5749f75 100644 --- a/src/main/kotlin/dev/usbharu/hideout/exception/ap/JsonParseException.kt +++ b/src/main/kotlin/dev/usbharu/hideout/exception/JsonParseException.kt @@ -1,4 +1,4 @@ -package dev.usbharu.hideout.exception.ap +package dev.usbharu.hideout.exception class JsonParseException : IllegalArgumentException { constructor() : super() diff --git a/src/main/kotlin/dev/usbharu/hideout/plugins/Routing.kt b/src/main/kotlin/dev/usbharu/hideout/plugins/Routing.kt index 44f097ad..3710262d 100644 --- a/src/main/kotlin/dev/usbharu/hideout/plugins/Routing.kt +++ b/src/main/kotlin/dev/usbharu/hideout/plugins/Routing.kt @@ -1,15 +1,19 @@ package dev.usbharu.hideout.plugins +import dev.usbharu.hideout.routing.activitypub.inbox +import dev.usbharu.hideout.routing.activitypub.outbox +import dev.usbharu.hideout.routing.activitypub.usersAP +import dev.usbharu.hideout.service.activitypub.ActivityPubService +import dev.usbharu.hideout.service.signature.HttpSignatureVerifyService import io.ktor.server.routing.* -import io.ktor.server.response.* import io.ktor.server.plugins.autohead.* import io.ktor.server.application.* -fun Application.configureRouting() { +fun Application.configureRouting(httpSignatureVerifyService: HttpSignatureVerifyService,activityPubService: ActivityPubService) { install(AutoHeadResponse) routing { - get("/") { - call.respondText("Hello World!") - } + inbox(httpSignatureVerifyService,activityPubService) + outbox() + usersAP(activityPubService) } } diff --git a/src/main/kotlin/dev/usbharu/hideout/routing/activitypub/InboxRouting.kt b/src/main/kotlin/dev/usbharu/hideout/routing/activitypub/InboxRouting.kt index d82ea289..fc08e892 100644 --- a/src/main/kotlin/dev/usbharu/hideout/routing/activitypub/InboxRouting.kt +++ b/src/main/kotlin/dev/usbharu/hideout/routing/activitypub/InboxRouting.kt @@ -1,24 +1,35 @@ package dev.usbharu.hideout.routing.activitypub +import dev.usbharu.hideout.exception.HttpSignatureVerifyException +import dev.usbharu.hideout.service.signature.HttpSignatureVerifyService +import io.ktor.http.* import io.ktor.server.application.* +import io.ktor.server.request.* +import io.ktor.server.response.* import io.ktor.server.routing.* -fun Routing.inbox(){ +fun Routing.inbox(httpSignatureVerifyService: HttpSignatureVerifyService,activityPubService: dev.usbharu.hideout.service.activitypub.ActivityPubService){ route("/inbox") { get { - + call.respond(HttpStatusCode.MethodNotAllowed) } post { - + if (httpSignatureVerifyService.verify(call.request.headers).not()) { + throw HttpSignatureVerifyException() + } + val json = call.receiveText() + val activityTypes = activityPubService.parseActivity(json) + activityPubService.processActivity(json,activityTypes) + call.respond(HttpStatusCode.NotImplemented) } } route("/users/{name}/inbox"){ get { - + call.respond(HttpStatusCode.NotImplemented) } post { - + call.respond(HttpStatusCode.MethodNotAllowed) } } diff --git a/src/main/kotlin/dev/usbharu/hideout/routing/activitypub/OutboxRouting.kt b/src/main/kotlin/dev/usbharu/hideout/routing/activitypub/OutboxRouting.kt index cc8b133d..7bfced91 100644 --- a/src/main/kotlin/dev/usbharu/hideout/routing/activitypub/OutboxRouting.kt +++ b/src/main/kotlin/dev/usbharu/hideout/routing/activitypub/OutboxRouting.kt @@ -1,24 +1,26 @@ package dev.usbharu.hideout.routing.activitypub +import io.ktor.http.* import io.ktor.server.application.* +import io.ktor.server.response.* import io.ktor.server.routing.* fun Routing.outbox() { route("/outbox") { get { - + call.respond(HttpStatusCode.NotImplemented) } post { - + call.respond(HttpStatusCode.NotImplemented) } } route("/users/{name}/outbox"){ get { - + call.respond(HttpStatusCode.NotImplemented) } post { - + call.respond(HttpStatusCode.NotImplemented) } } diff --git a/src/main/kotlin/dev/usbharu/hideout/routing/activitypub/UserRouting.kt b/src/main/kotlin/dev/usbharu/hideout/routing/activitypub/UserRouting.kt index a071c5f7..9c73fbbc 100644 --- a/src/main/kotlin/dev/usbharu/hideout/routing/activitypub/UserRouting.kt +++ b/src/main/kotlin/dev/usbharu/hideout/routing/activitypub/UserRouting.kt @@ -1,14 +1,20 @@ package dev.usbharu.hideout.routing.activitypub +import dev.usbharu.hideout.service.activitypub.ActivityPubService import dev.usbharu.hideout.util.HttpUtil.Activity import io.ktor.http.* import io.ktor.server.application.* import io.ktor.server.request.* +import io.ktor.server.response.* import io.ktor.server.routing.* -fun Routing.users(){ +fun Routing.usersAP(activityPubService: ActivityPubService){ route("/users/{name}"){ createChild(ContentTypeRouteSelector(ContentType.Application.Activity)).handle { + val json = call.receiveText() + val activityTypes = activityPubService.parseActivity(json) + activityPubService.processActivity(json,activityTypes) + call.respond(HttpStatusCode.NotImplemented) } } } diff --git a/src/main/kotlin/dev/usbharu/hideout/service/activitypub/ActivityPubService.kt b/src/main/kotlin/dev/usbharu/hideout/service/activitypub/ActivityPubService.kt index 55d3eec4..070046a3 100644 --- a/src/main/kotlin/dev/usbharu/hideout/service/activitypub/ActivityPubService.kt +++ b/src/main/kotlin/dev/usbharu/hideout/service/activitypub/ActivityPubService.kt @@ -1,7 +1,7 @@ package dev.usbharu.hideout.service.activitypub interface ActivityPubService { - fun parseActivity(json:String): List + fun parseActivity(json:String): ActivityType fun processActivity(json:String, type: ActivityType) } diff --git a/src/main/kotlin/dev/usbharu/hideout/service/activitypub/ActivityPubServiceImpl.kt b/src/main/kotlin/dev/usbharu/hideout/service/activitypub/ActivityPubServiceImpl.kt index 6e407bb3..9e238c7f 100644 --- a/src/main/kotlin/dev/usbharu/hideout/service/activitypub/ActivityPubServiceImpl.kt +++ b/src/main/kotlin/dev/usbharu/hideout/service/activitypub/ActivityPubServiceImpl.kt @@ -2,10 +2,10 @@ package dev.usbharu.hideout.service.activitypub import com.fasterxml.jackson.databind.JsonNode import dev.usbharu.hideout.config.Config -import dev.usbharu.hideout.exception.ap.JsonParseException +import dev.usbharu.hideout.exception.JsonParseException class ActivityPubServiceImpl : ActivityPubService { - override fun parseActivity(json: String): List { + override fun parseActivity(json: String): ActivityType { val readTree = Config.configData.objectMapper.readTree(json) if (readTree.isObject.not()) { throw JsonParseException("Json is not object.") @@ -13,10 +13,10 @@ class ActivityPubServiceImpl : ActivityPubService { val type = readTree["type"] if (type.isArray) { return type.mapNotNull { jsonNode: JsonNode -> - ActivityType.values().filter { it.name.equals(jsonNode.toPrettyString(), true) } - }.flatten() + ActivityType.values().firstOrNull { it.name.equals(jsonNode.asText(), true) } + }.first() } - return ActivityType.values().filter { it.name.equals(type.toPrettyString(), true) } + return ActivityType.values().first { it.name.equals(type.asText(), true) } } override fun processActivity(json: String, type: ActivityType) { diff --git a/src/main/kotlin/dev/usbharu/hideout/service/signature/HttpSignatureVerifyService.kt b/src/main/kotlin/dev/usbharu/hideout/service/signature/HttpSignatureVerifyService.kt new file mode 100644 index 00000000..34706206 --- /dev/null +++ b/src/main/kotlin/dev/usbharu/hideout/service/signature/HttpSignatureVerifyService.kt @@ -0,0 +1,7 @@ +package dev.usbharu.hideout.service.signature + +import io.ktor.http.* + +interface HttpSignatureVerifyService { + fun verify(headers:Headers):Boolean +} diff --git a/src/main/kotlin/dev/usbharu/hideout/service/signature/HttpSignatureVerifyServiceImpl.kt b/src/main/kotlin/dev/usbharu/hideout/service/signature/HttpSignatureVerifyServiceImpl.kt new file mode 100644 index 00000000..5f18f09e --- /dev/null +++ b/src/main/kotlin/dev/usbharu/hideout/service/signature/HttpSignatureVerifyServiceImpl.kt @@ -0,0 +1,23 @@ +package dev.usbharu.hideout.service.signature + +import dev.usbharu.hideout.plugins.KtorKeyMap +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: IUserAuthService) : HttpSignatureVerifyService { + override fun verify(headers: Headers): Boolean { + val build = SignatureHeaderVerifier.builder().keyMap(KtorKeyMap(userAuthService)).build() + return build.verify(object : HttpMessage { + override fun headerValues(name: String?): MutableList { + return name?.let { headers.getAll(it) }?.toMutableList() ?: mutableListOf() + } + + override fun addHeader(name: String?, value: String?) { + TODO() + } + + }) + } +} From 469b886caf0b27409389ebec3291f7a6eed256e9 Mon Sep 17 00:00:00 2001 From: usbharu <64310155+usbharu@users.noreply.github.com> Date: Fri, 7 Apr 2023 13:00:29 +0900 Subject: [PATCH 08/40] =?UTF-8?q?feat:=20ActivityPub=E3=81=AEusers?= =?UTF-8?q?=E3=81=AE=E3=83=AB=E3=83=BC=E3=83=86=E3=82=A3=E3=83=B3=E3=82=B0?= =?UTF-8?q?=E3=82=92=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle.kts | 4 ++ .../routing/activitypub/UserRouting.kt | 7 +-- src/test/kotlin/dev/usbharu/hideout/Empty.kt | 7 +++ .../routing/activitypub/UsersAPTest.kt | 44 +++++++++++++++++++ src/test/resources/empty.conf | 22 ++++++++++ 5 files changed, 79 insertions(+), 5 deletions(-) create mode 100644 src/test/kotlin/dev/usbharu/hideout/Empty.kt create mode 100644 src/test/kotlin/dev/usbharu/hideout/routing/activitypub/UsersAPTest.kt create mode 100644 src/test/resources/empty.conf diff --git a/build.gradle.kts b/build.gradle.kts index b7e81f91..146f725f 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -73,6 +73,10 @@ dependencies { testImplementation("org.junit.jupiter:junit-jupiter:5.8.1") implementation("org.drewcarlson:kjob-core:0.6.0") + testImplementation("io.ktor:ktor-server-test-host-jvm:2.2.4") + + testImplementation("org.slf4j:slf4j-simple:2.0.7") + } jib { diff --git a/src/main/kotlin/dev/usbharu/hideout/routing/activitypub/UserRouting.kt b/src/main/kotlin/dev/usbharu/hideout/routing/activitypub/UserRouting.kt index 9c73fbbc..f879a400 100644 --- a/src/main/kotlin/dev/usbharu/hideout/routing/activitypub/UserRouting.kt +++ b/src/main/kotlin/dev/usbharu/hideout/routing/activitypub/UserRouting.kt @@ -11,9 +11,6 @@ import io.ktor.server.routing.* fun Routing.usersAP(activityPubService: ActivityPubService){ route("/users/{name}"){ createChild(ContentTypeRouteSelector(ContentType.Application.Activity)).handle { - val json = call.receiveText() - val activityTypes = activityPubService.parseActivity(json) - activityPubService.processActivity(json,activityTypes) call.respond(HttpStatusCode.NotImplemented) } } @@ -21,10 +18,10 @@ fun Routing.usersAP(activityPubService: ActivityPubService){ class ContentTypeRouteSelector(private val contentType: ContentType) : RouteSelector() { override fun evaluate(context: RoutingResolveContext, segmentIndex: Int): RouteSelectorEvaluation { - return if (context.call.request.contentType() == contentType) { + return if (ContentType.parse(context.call.request.accept() ?: return RouteSelectorEvaluation.FailedParameter) == contentType) { RouteSelectorEvaluation.Constant } else { - RouteSelectorEvaluation.Failed + RouteSelectorEvaluation.FailedParameter } } diff --git a/src/test/kotlin/dev/usbharu/hideout/Empty.kt b/src/test/kotlin/dev/usbharu/hideout/Empty.kt new file mode 100644 index 00000000..0203d877 --- /dev/null +++ b/src/test/kotlin/dev/usbharu/hideout/Empty.kt @@ -0,0 +1,7 @@ +package dev.usbharu.hideout + +import io.ktor.server.application.* + +fun Application.empty(){ + +} diff --git a/src/test/kotlin/dev/usbharu/hideout/routing/activitypub/UsersAPTest.kt b/src/test/kotlin/dev/usbharu/hideout/routing/activitypub/UsersAPTest.kt new file mode 100644 index 00000000..7eca6e24 --- /dev/null +++ b/src/test/kotlin/dev/usbharu/hideout/routing/activitypub/UsersAPTest.kt @@ -0,0 +1,44 @@ +package dev.usbharu.hideout.routing.activitypub + +import dev.usbharu.hideout.plugins.configureRouting +import dev.usbharu.hideout.service.activitypub.ActivityPubService +import dev.usbharu.hideout.service.activitypub.ActivityType +import dev.usbharu.hideout.service.signature.HttpSignatureVerifyService +import dev.usbharu.hideout.util.HttpUtil.Activity +import io.ktor.client.request.* +import io.ktor.http.* +import io.ktor.server.config.* +import io.ktor.server.testing.* +import org.junit.jupiter.api.Test +import kotlin.test.assertEquals + + +class UsersAPTest { + + @Test + fun testHandleUsersName() = testApplication { + environment { + config = ApplicationConfig("empty.conf") + } + application { + configureRouting(object : HttpSignatureVerifyService { + override fun verify(headers: Headers): Boolean { + return true + } + }, object : ActivityPubService { + override fun parseActivity(json: String): ActivityType { + TODO("Not yet implemented") + } + + override fun processActivity(json: String, type: ActivityType) { + TODO("Not yet implemented") + } + }) + } + client.get("/users/test"){ + accept(ContentType.Application.Activity) + }.let { + assertEquals(HttpStatusCode.NotImplemented, it.status) + } + } +} diff --git a/src/test/resources/empty.conf b/src/test/resources/empty.conf new file mode 100644 index 00000000..861f37bc --- /dev/null +++ b/src/test/resources/empty.conf @@ -0,0 +1,22 @@ +ktor { + development = true + deployment { + port = 8080 + port = ${?PORT} + watch = [classes, resources] + } + application { + modules = [dev.usbharu.hideout.EmptyKt.empty] + } +} + +hideout { + hostname = "https://localhost:8080" + hostname = ${?HOSTNAME} + database { + url = "jdbc:h2:./test;MODE=POSTGRESQL" + driver = "org.h2.Driver" + username = "" + password = "" + } +} From ab01b61f5549a3946a80785c22760a3d874b7edb Mon Sep 17 00:00:00 2001 From: usbharu <64310155+usbharu@users.noreply.github.com> Date: Fri, 7 Apr 2023 14:50:49 +0900 Subject: [PATCH 09/40] =?UTF-8?q?feat:=20=E3=83=AC=E3=82=B9=E3=83=9D?= =?UTF-8?q?=E3=83=B3=E3=82=B9=E3=81=AE=E3=82=AF=E3=83=A9=E3=82=B9=E3=82=92?= =?UTF-8?q?=E4=BD=9C=E6=88=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../usbharu/hideout/domain/model/ActivityPubResponse.kt | 5 +++++ .../usbharu/hideout/routing/activitypub/InboxRouting.kt | 8 ++++++-- .../hideout/service/activitypub/ActivityPubService.kt | 4 +++- .../hideout/service/activitypub/ActivityPubServiceImpl.kt | 3 ++- .../usbharu/hideout/routing/activitypub/UsersAPTest.kt | 3 ++- 5 files changed, 18 insertions(+), 5 deletions(-) create mode 100644 src/main/kotlin/dev/usbharu/hideout/domain/model/ActivityPubResponse.kt diff --git a/src/main/kotlin/dev/usbharu/hideout/domain/model/ActivityPubResponse.kt b/src/main/kotlin/dev/usbharu/hideout/domain/model/ActivityPubResponse.kt new file mode 100644 index 00000000..8e487b7c --- /dev/null +++ b/src/main/kotlin/dev/usbharu/hideout/domain/model/ActivityPubResponse.kt @@ -0,0 +1,5 @@ +package dev.usbharu.hideout.domain.model + +import io.ktor.http.* + +data class ActivityPubResponse(val httpStatusCode: HttpStatusCode, val message:String) diff --git a/src/main/kotlin/dev/usbharu/hideout/routing/activitypub/InboxRouting.kt b/src/main/kotlin/dev/usbharu/hideout/routing/activitypub/InboxRouting.kt index fc08e892..079ddb98 100644 --- a/src/main/kotlin/dev/usbharu/hideout/routing/activitypub/InboxRouting.kt +++ b/src/main/kotlin/dev/usbharu/hideout/routing/activitypub/InboxRouting.kt @@ -20,8 +20,12 @@ fun Routing.inbox(httpSignatureVerifyService: HttpSignatureVerifyService,activit } val json = call.receiveText() val activityTypes = activityPubService.parseActivity(json) - activityPubService.processActivity(json,activityTypes) - call.respond(HttpStatusCode.NotImplemented) + val response = activityPubService.processActivity(json, activityTypes) + return@post if (response != null) { + call.respond(response.httpStatusCode, response.message) + }else { + call.respond(HttpStatusCode.InternalServerError) + } } } route("/users/{name}/inbox"){ diff --git a/src/main/kotlin/dev/usbharu/hideout/service/activitypub/ActivityPubService.kt b/src/main/kotlin/dev/usbharu/hideout/service/activitypub/ActivityPubService.kt index 070046a3..7723ca1b 100644 --- a/src/main/kotlin/dev/usbharu/hideout/service/activitypub/ActivityPubService.kt +++ b/src/main/kotlin/dev/usbharu/hideout/service/activitypub/ActivityPubService.kt @@ -1,9 +1,11 @@ package dev.usbharu.hideout.service.activitypub +import dev.usbharu.hideout.domain.model.ActivityPubResponse + interface ActivityPubService { fun parseActivity(json:String): ActivityType - fun processActivity(json:String, type: ActivityType) + fun processActivity(json:String, type: ActivityType):ActivityPubResponse? } enum class ActivityType { diff --git a/src/main/kotlin/dev/usbharu/hideout/service/activitypub/ActivityPubServiceImpl.kt b/src/main/kotlin/dev/usbharu/hideout/service/activitypub/ActivityPubServiceImpl.kt index 9e238c7f..a9ea3b7f 100644 --- a/src/main/kotlin/dev/usbharu/hideout/service/activitypub/ActivityPubServiceImpl.kt +++ b/src/main/kotlin/dev/usbharu/hideout/service/activitypub/ActivityPubServiceImpl.kt @@ -2,6 +2,7 @@ package dev.usbharu.hideout.service.activitypub import com.fasterxml.jackson.databind.JsonNode import dev.usbharu.hideout.config.Config +import dev.usbharu.hideout.domain.model.ActivityPubResponse import dev.usbharu.hideout.exception.JsonParseException class ActivityPubServiceImpl : ActivityPubService { @@ -19,7 +20,7 @@ class ActivityPubServiceImpl : ActivityPubService { return ActivityType.values().first { it.name.equals(type.asText(), true) } } - override fun processActivity(json: String, type: ActivityType) { + override fun processActivity(json: String, type: ActivityType): ActivityPubResponse? { when (type) { ActivityType.Accept -> TODO() ActivityType.Add -> TODO() diff --git a/src/test/kotlin/dev/usbharu/hideout/routing/activitypub/UsersAPTest.kt b/src/test/kotlin/dev/usbharu/hideout/routing/activitypub/UsersAPTest.kt index 7eca6e24..fee20a11 100644 --- a/src/test/kotlin/dev/usbharu/hideout/routing/activitypub/UsersAPTest.kt +++ b/src/test/kotlin/dev/usbharu/hideout/routing/activitypub/UsersAPTest.kt @@ -1,5 +1,6 @@ package dev.usbharu.hideout.routing.activitypub +import dev.usbharu.hideout.domain.model.ActivityPubResponse import dev.usbharu.hideout.plugins.configureRouting import dev.usbharu.hideout.service.activitypub.ActivityPubService import dev.usbharu.hideout.service.activitypub.ActivityType @@ -30,7 +31,7 @@ class UsersAPTest { TODO("Not yet implemented") } - override fun processActivity(json: String, type: ActivityType) { + override fun processActivity(json: String, type: ActivityType): ActivityPubResponse? { TODO("Not yet implemented") } }) From 41ed7ef193ace6f47f26cd730c9c1328c8df5131 Mon Sep 17 00:00:00 2001 From: usbharu <64310155+usbharu@users.noreply.github.com> Date: Fri, 7 Apr 2023 17:22:06 +0900 Subject: [PATCH 10/40] =?UTF-8?q?refactor:=20=E4=B8=8D=E8=A6=81=E3=81=AA?= =?UTF-8?q?=E3=83=A2=E3=82=B8=E3=83=A5=E3=83=BC=E3=83=AB=E3=82=92=E5=89=8A?= =?UTF-8?q?=E9=99=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- gradle.properties | 2 +- .../kotlin/dev/usbharu/hideout/Application.kt | 86 +++++-------------- .../dev/usbharu/hideout/plugins/Routing.kt | 13 ++- .../usbharu/hideout/plugins/Serialization.kt | 5 -- 4 files changed, 31 insertions(+), 75 deletions(-) diff --git a/gradle.properties b/gradle.properties index 23ea2ded..73c5f36a 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,6 +1,6 @@ ktor_version=2.2.4 kotlin_version=1.8.10 -logback_version=1.2.11 +logback_version=1.4.6 kotlin.code.style=official exposed_version=0.41.1 h2_version=2.1.214 diff --git a/src/main/kotlin/dev/usbharu/hideout/Application.kt b/src/main/kotlin/dev/usbharu/hideout/Application.kt index e99bef82..9be1bf1d 100644 --- a/src/main/kotlin/dev/usbharu/hideout/Application.kt +++ b/src/main/kotlin/dev/usbharu/hideout/Application.kt @@ -1,95 +1,51 @@ package dev.usbharu.hideout -import com.fasterxml.jackson.annotation.JsonInclude -import com.fasterxml.jackson.databind.DeserializationFeature -import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper -import dev.usbharu.hideout.config.Config -import dev.usbharu.hideout.config.ConfigData import dev.usbharu.hideout.plugins.* import dev.usbharu.hideout.repository.IUserAuthRepository import dev.usbharu.hideout.repository.IUserRepository import dev.usbharu.hideout.repository.UserAuthRepository import dev.usbharu.hideout.repository.UserRepository -import dev.usbharu.hideout.routing.* -import dev.usbharu.hideout.service.* -import dev.usbharu.hideout.service.impl.* -import io.ktor.client.* -import io.ktor.client.engine.cio.* -import io.ktor.client.plugins.contentnegotiation.* -import io.ktor.client.plugins.logging.* -import io.ktor.serialization.jackson.* +import dev.usbharu.hideout.service.IUserAuthService +import dev.usbharu.hideout.service.activitypub.ActivityPubService +import dev.usbharu.hideout.service.activitypub.ActivityPubServiceImpl +import dev.usbharu.hideout.service.impl.UserAuthService +import dev.usbharu.hideout.service.signature.HttpSignatureVerifyService +import dev.usbharu.hideout.service.signature.HttpSignatureVerifyServiceImpl import io.ktor.server.application.* import org.jetbrains.exposed.sql.Database import org.koin.ktor.ext.inject -import java.util.* fun main(args: Array): Unit = io.ktor.server.netty.EngineMain.main(args) +val Application.property: Application.(propertyName: String) -> String + get() = { + environment.config.property(it).getString() + } + @Suppress("unused") // application.conf references the main function. This annotation prevents the IDE from marking it as unused. fun Application.module() { - val module = org.koin.dsl.module { + val module = org.koin.dsl.module { single { Database.connect( - url = environment.config.property("hideout.database.url").getString(), - driver = environment.config.property("hideout.database.driver").getString(), + url = property("hideout.database.url"), + driver = property("hideout.database.driver"), + user = property("hideout.database.username"), + password = property("hideout.database.password") ) } - single { - ConfigData( - url = environment.config.propertyOrNull("hideout.url")?.getString() - ?: environment.config.property("hideout.hostname").getString(), - objectMapper = jacksonObjectMapper().enable(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY) - .setSerializationInclusion(JsonInclude.Include.NON_EMPTY) - .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false) - ) - } - single { - HttpClient(CIO) { - install(ContentNegotiation) { - jackson { - enable(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY) - setSerializationInclusion(JsonInclude.Include.NON_EMPTY) - configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false) - } - } - install(Logging) { - logger = Logger.DEFAULT - level = LogLevel.ALL - } - install(httpSignaturePlugin){ - keyMap = KtorKeyMap(get()) - } - } - } single { UserRepository(get()) } single { UserAuthRepository(get()) } single { UserAuthService(get(), get()) } - single { UserService(get()) } - single { ActivityPubService() } - single { ActivityPubUserService(get(), get(), get(), get()) } - single { WebFingerService(get(), get()) } + single { HttpSignatureVerifyServiceImpl(get()) } + single { ActivityPubServiceImpl() } } - configureKoin(module) - val configData by inject() - Config.configData = configData - val decode = Base64.getDecoder().decode("76pc9N9hspQqapj30kCaLJA14O/50ptCg50zCA1oxjA=") - val pair = "admin" to decode - println(pair) - val userAuthService by inject() - val userService by inject() - configureSecurity(userAuthService) + configureKoin(module) configureHTTP() + configureSockets() configureMonitoring() configureSerialization() - configureSockets() - val activityPubUserService by inject() - user(userService, activityPubUserService) - login() - register(userAuthService) - wellKnown(userService) - val activityPubService by inject() - userActivityPubRouting(activityPubService, activityPubUserService) + configureRouting(inject().value, inject().value) } diff --git a/src/main/kotlin/dev/usbharu/hideout/plugins/Routing.kt b/src/main/kotlin/dev/usbharu/hideout/plugins/Routing.kt index 3710262d..dabc0984 100644 --- a/src/main/kotlin/dev/usbharu/hideout/plugins/Routing.kt +++ b/src/main/kotlin/dev/usbharu/hideout/plugins/Routing.kt @@ -3,17 +3,22 @@ package dev.usbharu.hideout.plugins import dev.usbharu.hideout.routing.activitypub.inbox import dev.usbharu.hideout.routing.activitypub.outbox import dev.usbharu.hideout.routing.activitypub.usersAP +import dev.usbharu.hideout.routing.wellknown.webfinger import dev.usbharu.hideout.service.activitypub.ActivityPubService import dev.usbharu.hideout.service.signature.HttpSignatureVerifyService -import io.ktor.server.routing.* -import io.ktor.server.plugins.autohead.* import io.ktor.server.application.* +import io.ktor.server.plugins.autohead.* +import io.ktor.server.routing.* -fun Application.configureRouting(httpSignatureVerifyService: HttpSignatureVerifyService,activityPubService: ActivityPubService) { +fun Application.configureRouting( + httpSignatureVerifyService: HttpSignatureVerifyService, + activityPubService: ActivityPubService +) { install(AutoHeadResponse) routing { - inbox(httpSignatureVerifyService,activityPubService) + inbox(httpSignatureVerifyService, activityPubService) outbox() usersAP(activityPubService) + webfinger() } } diff --git a/src/main/kotlin/dev/usbharu/hideout/plugins/Serialization.kt b/src/main/kotlin/dev/usbharu/hideout/plugins/Serialization.kt index de3c0a54..c07bc345 100644 --- a/src/main/kotlin/dev/usbharu/hideout/plugins/Serialization.kt +++ b/src/main/kotlin/dev/usbharu/hideout/plugins/Serialization.kt @@ -10,9 +10,4 @@ fun Application.configureSerialization() { install(ContentNegotiation) { jackson() } - routing { - get("/json/kotlinx-serialization") { - call.respond(mapOf("hello" to "world")) - } - } } From e988d9d2b71d60393c83782787227e146c92d8ae Mon Sep 17 00:00:00 2001 From: usbharu <64310155+usbharu@users.noreply.github.com> Date: Fri, 7 Apr 2023 17:45:53 +0900 Subject: [PATCH 11/40] =?UTF-8?q?feat:=20webfinger=E3=82=92=E4=BD=9C?= =?UTF-8?q?=E3=82=8A=E7=9B=B4=E3=81=97=E3=81=9F=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle.kts | 2 ++ .../domain/model/wellknown/WebFinger.kt | 5 +++ .../exception/IllegalParameterException.kt | 8 +++++ .../exception/ParameterNotExistException.kt | 8 +++++ .../usbharu/hideout/plugins/StatusPages.kt | 18 ++++++++++ .../routing/wellknown/WebfingerRouting.kt | 35 ++++++++++++++++++- 6 files changed, 75 insertions(+), 1 deletion(-) create mode 100644 src/main/kotlin/dev/usbharu/hideout/domain/model/wellknown/WebFinger.kt create mode 100644 src/main/kotlin/dev/usbharu/hideout/exception/IllegalParameterException.kt create mode 100644 src/main/kotlin/dev/usbharu/hideout/exception/ParameterNotExistException.kt create mode 100644 src/main/kotlin/dev/usbharu/hideout/plugins/StatusPages.kt diff --git a/build.gradle.kts b/build.gradle.kts index 146f725f..9afedfb0 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -59,6 +59,8 @@ dependencies { implementation("io.insert-koin:koin-ktor:$koin_version") implementation("io.insert-koin:koin-logger-slf4j:$koin_version") implementation("io.ktor:ktor-client-logging-jvm:2.2.4") + implementation("io.ktor:ktor-server-host-common-jvm:2.2.4") + implementation("io.ktor:ktor-server-status-pages-jvm:2.2.4") testImplementation("io.ktor:ktor-server-tests-jvm:$ktor_version") testImplementation("org.jetbrains.kotlin:kotlin-test-junit:$kotlin_version") diff --git a/src/main/kotlin/dev/usbharu/hideout/domain/model/wellknown/WebFinger.kt b/src/main/kotlin/dev/usbharu/hideout/domain/model/wellknown/WebFinger.kt new file mode 100644 index 00000000..b8542f23 --- /dev/null +++ b/src/main/kotlin/dev/usbharu/hideout/domain/model/wellknown/WebFinger.kt @@ -0,0 +1,5 @@ +package dev.usbharu.hideout.domain.model.wellknown + +data class WebFinger(val subject:String,val links:List){ + data class Link(val rel:String,val type:String,val href:String) +} diff --git a/src/main/kotlin/dev/usbharu/hideout/exception/IllegalParameterException.kt b/src/main/kotlin/dev/usbharu/hideout/exception/IllegalParameterException.kt new file mode 100644 index 00000000..dd94d127 --- /dev/null +++ b/src/main/kotlin/dev/usbharu/hideout/exception/IllegalParameterException.kt @@ -0,0 +1,8 @@ +package dev.usbharu.hideout.exception + +class IllegalParameterException : IllegalArgumentException { + constructor() : super() + constructor(s: String?) : super(s) + constructor(message: String?, cause: Throwable?) : super(message, cause) + constructor(cause: Throwable?) : super(cause) +} diff --git a/src/main/kotlin/dev/usbharu/hideout/exception/ParameterNotExistException.kt b/src/main/kotlin/dev/usbharu/hideout/exception/ParameterNotExistException.kt new file mode 100644 index 00000000..d3ca6693 --- /dev/null +++ b/src/main/kotlin/dev/usbharu/hideout/exception/ParameterNotExistException.kt @@ -0,0 +1,8 @@ +package dev.usbharu.hideout.exception + +class ParameterNotExistException : IllegalArgumentException { + constructor() : super() + constructor(s: String?) : super(s) + constructor(message: String?, cause: Throwable?) : super(message, cause) + constructor(cause: Throwable?) : super(cause) +} diff --git a/src/main/kotlin/dev/usbharu/hideout/plugins/StatusPages.kt b/src/main/kotlin/dev/usbharu/hideout/plugins/StatusPages.kt new file mode 100644 index 00000000..990a7acb --- /dev/null +++ b/src/main/kotlin/dev/usbharu/hideout/plugins/StatusPages.kt @@ -0,0 +1,18 @@ +package dev.usbharu.hideout.plugins + +import io.ktor.http.* +import io.ktor.server.application.* +import io.ktor.server.plugins.statuspages.* +import io.ktor.server.response.* +import io.ktor.server.routing.* + +fun Application.configureStatusPages() { + install(StatusPages) { + exception { call, cause -> + call.respondText(text = "500: $cause", status = HttpStatusCode.InternalServerError) + } + exception { call, cause -> + call.respondText(text = "400: $cause", status = HttpStatusCode.BadRequest) + } + } +} diff --git a/src/main/kotlin/dev/usbharu/hideout/routing/wellknown/WebfingerRouting.kt b/src/main/kotlin/dev/usbharu/hideout/routing/wellknown/WebfingerRouting.kt index 62bf0c81..f862f745 100644 --- a/src/main/kotlin/dev/usbharu/hideout/routing/wellknown/WebfingerRouting.kt +++ b/src/main/kotlin/dev/usbharu/hideout/routing/wellknown/WebfingerRouting.kt @@ -1,11 +1,44 @@ package dev.usbharu.hideout.routing.wellknown +import dev.usbharu.hideout.config.Config +import dev.usbharu.hideout.domain.model.wellknown.WebFinger +import dev.usbharu.hideout.exception.IllegalParameterException +import dev.usbharu.hideout.exception.ParameterNotExistException +import dev.usbharu.hideout.service.impl.UserService +import dev.usbharu.hideout.util.HttpUtil.Activity +import io.ktor.http.* +import io.ktor.server.application.* +import io.ktor.server.response.* import io.ktor.server.routing.* -fun Routing.webfinger(){ +fun Routing.webfinger(userService:UserService){ route("/.well-known/webfinger"){ get { + val acct = call.request.queryParameters["resource"]?.decodeURLPart() + ?: throw ParameterNotExistException("Parameter(name='resource') does not exist.") + if (acct.startsWith("acct:").not()) { + throw IllegalParameterException("Parameter(name='resource') is not start with 'acct:'.") + } + + val accountName = acct.substringBeforeLast("@") + .substringAfter("acct:") + .trimStart('@') + + val userEntity = userService.findByName(accountName) + + val webFinger = WebFinger( + subject = acct, + links = listOf( + WebFinger.Link( + rel = "self", + type = ContentType.Application.Activity.toString(), + href = "${Config.configData.url}/users/${userEntity.name}" + ) + ) + ) + + return@get call.respond(webFinger) } } } From 9d9c9aa16f3b06bbcd12daaf0ae022db90031393 Mon Sep 17 00:00:00 2001 From: usbharu <64310155+usbharu@users.noreply.github.com> Date: Fri, 7 Apr 2023 18:26:58 +0900 Subject: [PATCH 12/40] =?UTF-8?q?feat:=20ActivityPub=E9=96=A2=E4=BF=82?= =?UTF-8?q?=E3=81=AE=E8=BF=94=E3=82=8A=E5=80=A4=E3=82=92JsonLD=E3=81=AE?= =?UTF-8?q?=E3=82=AA=E3=83=96=E3=82=B8=E3=82=A7=E3=82=AF=E3=83=88=E3=81=A7?= =?UTF-8?q?=E3=82=82=E8=BF=94=E3=81=9B=E3=82=8B=E3=82=88=E3=81=86=E3=81=AB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../kotlin/dev/usbharu/hideout/Application.kt | 8 +++++++- .../domain/model/ActivityPubResponse.kt | 5 ----- .../domain/model/ActivityPubStringResponse.kt | 11 +++++++++++ .../dev/usbharu/hideout/plugins/Routing.kt | 6 ++++-- .../routing/activitypub/InboxRouting.kt | 18 +++++++++++++----- .../hideout/routing/activitypub/UserRouting.kt | 1 + .../service/activitypub/ActivityPubService.kt | 2 +- 7 files changed, 37 insertions(+), 14 deletions(-) delete mode 100644 src/main/kotlin/dev/usbharu/hideout/domain/model/ActivityPubResponse.kt create mode 100644 src/main/kotlin/dev/usbharu/hideout/domain/model/ActivityPubStringResponse.kt diff --git a/src/main/kotlin/dev/usbharu/hideout/Application.kt b/src/main/kotlin/dev/usbharu/hideout/Application.kt index 9be1bf1d..3770dd5d 100644 --- a/src/main/kotlin/dev/usbharu/hideout/Application.kt +++ b/src/main/kotlin/dev/usbharu/hideout/Application.kt @@ -9,6 +9,7 @@ import dev.usbharu.hideout.service.IUserAuthService import dev.usbharu.hideout.service.activitypub.ActivityPubService import dev.usbharu.hideout.service.activitypub.ActivityPubServiceImpl import dev.usbharu.hideout.service.impl.UserAuthService +import dev.usbharu.hideout.service.impl.UserService import dev.usbharu.hideout.service.signature.HttpSignatureVerifyService import dev.usbharu.hideout.service.signature.HttpSignatureVerifyServiceImpl import io.ktor.server.application.* @@ -40,6 +41,7 @@ fun Application.module() { single { UserAuthService(get(), get()) } single { HttpSignatureVerifyServiceImpl(get()) } single { ActivityPubServiceImpl() } + single { UserService(get()) } } configureKoin(module) @@ -47,5 +49,9 @@ fun Application.module() { configureSockets() configureMonitoring() configureSerialization() - configureRouting(inject().value, inject().value) + configureRouting( + inject().value, + inject().value, + inject().value + ) } diff --git a/src/main/kotlin/dev/usbharu/hideout/domain/model/ActivityPubResponse.kt b/src/main/kotlin/dev/usbharu/hideout/domain/model/ActivityPubResponse.kt deleted file mode 100644 index 8e487b7c..00000000 --- a/src/main/kotlin/dev/usbharu/hideout/domain/model/ActivityPubResponse.kt +++ /dev/null @@ -1,5 +0,0 @@ -package dev.usbharu.hideout.domain.model - -import io.ktor.http.* - -data class ActivityPubResponse(val httpStatusCode: HttpStatusCode, val message:String) diff --git a/src/main/kotlin/dev/usbharu/hideout/domain/model/ActivityPubStringResponse.kt b/src/main/kotlin/dev/usbharu/hideout/domain/model/ActivityPubStringResponse.kt new file mode 100644 index 00000000..643502a6 --- /dev/null +++ b/src/main/kotlin/dev/usbharu/hideout/domain/model/ActivityPubStringResponse.kt @@ -0,0 +1,11 @@ +package dev.usbharu.hideout.domain.model + +import dev.usbharu.hideout.ap.JsonLd +import io.ktor.http.* + +sealed class ActivityPubResponse(val httpStatusCode: HttpStatusCode) +class ActivityPubStringResponse(httpStatusCode: HttpStatusCode, val message: String) : + ActivityPubResponse(httpStatusCode) + +class ActivityPubObjectResponse(httpStatusCode: HttpStatusCode, val message: JsonLd) : + ActivityPubResponse(httpStatusCode) diff --git a/src/main/kotlin/dev/usbharu/hideout/plugins/Routing.kt b/src/main/kotlin/dev/usbharu/hideout/plugins/Routing.kt index dabc0984..bddeb896 100644 --- a/src/main/kotlin/dev/usbharu/hideout/plugins/Routing.kt +++ b/src/main/kotlin/dev/usbharu/hideout/plugins/Routing.kt @@ -5,6 +5,7 @@ import dev.usbharu.hideout.routing.activitypub.outbox import dev.usbharu.hideout.routing.activitypub.usersAP import dev.usbharu.hideout.routing.wellknown.webfinger import dev.usbharu.hideout.service.activitypub.ActivityPubService +import dev.usbharu.hideout.service.impl.UserService import dev.usbharu.hideout.service.signature.HttpSignatureVerifyService import io.ktor.server.application.* import io.ktor.server.plugins.autohead.* @@ -12,13 +13,14 @@ import io.ktor.server.routing.* fun Application.configureRouting( httpSignatureVerifyService: HttpSignatureVerifyService, - activityPubService: ActivityPubService + activityPubService: ActivityPubService, + userService:UserService ) { install(AutoHeadResponse) routing { inbox(httpSignatureVerifyService, activityPubService) outbox() usersAP(activityPubService) - webfinger() + webfinger(userService) } } diff --git a/src/main/kotlin/dev/usbharu/hideout/routing/activitypub/InboxRouting.kt b/src/main/kotlin/dev/usbharu/hideout/routing/activitypub/InboxRouting.kt index 079ddb98..a1e3b6a9 100644 --- a/src/main/kotlin/dev/usbharu/hideout/routing/activitypub/InboxRouting.kt +++ b/src/main/kotlin/dev/usbharu/hideout/routing/activitypub/InboxRouting.kt @@ -1,5 +1,8 @@ package dev.usbharu.hideout.routing.activitypub +import dev.usbharu.hideout.config.Config +import dev.usbharu.hideout.domain.model.ActivityPubObjectResponse +import dev.usbharu.hideout.domain.model.ActivityPubStringResponse import dev.usbharu.hideout.exception.HttpSignatureVerifyException import dev.usbharu.hideout.service.signature.HttpSignatureVerifyService import io.ktor.http.* @@ -8,7 +11,10 @@ import io.ktor.server.request.* import io.ktor.server.response.* import io.ktor.server.routing.* -fun Routing.inbox(httpSignatureVerifyService: HttpSignatureVerifyService,activityPubService: dev.usbharu.hideout.service.activitypub.ActivityPubService){ +fun Routing.inbox( + httpSignatureVerifyService: HttpSignatureVerifyService, + activityPubService: dev.usbharu.hideout.service.activitypub.ActivityPubService +){ route("/inbox") { get { @@ -21,10 +27,12 @@ fun Routing.inbox(httpSignatureVerifyService: HttpSignatureVerifyService,activit val json = call.receiveText() val activityTypes = activityPubService.parseActivity(json) val response = activityPubService.processActivity(json, activityTypes) - return@post if (response != null) { - call.respond(response.httpStatusCode, response.message) - }else { - call.respond(HttpStatusCode.InternalServerError) + when (response) { + is ActivityPubObjectResponse -> call.respond(response.httpStatusCode, 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) } } } diff --git a/src/main/kotlin/dev/usbharu/hideout/routing/activitypub/UserRouting.kt b/src/main/kotlin/dev/usbharu/hideout/routing/activitypub/UserRouting.kt index f879a400..8848840a 100644 --- a/src/main/kotlin/dev/usbharu/hideout/routing/activitypub/UserRouting.kt +++ b/src/main/kotlin/dev/usbharu/hideout/routing/activitypub/UserRouting.kt @@ -11,6 +11,7 @@ import io.ktor.server.routing.* fun Routing.usersAP(activityPubService: ActivityPubService){ route("/users/{name}"){ createChild(ContentTypeRouteSelector(ContentType.Application.Activity)).handle { + call.respond(HttpStatusCode.NotImplemented) } } diff --git a/src/main/kotlin/dev/usbharu/hideout/service/activitypub/ActivityPubService.kt b/src/main/kotlin/dev/usbharu/hideout/service/activitypub/ActivityPubService.kt index 7723ca1b..7558f2c3 100644 --- a/src/main/kotlin/dev/usbharu/hideout/service/activitypub/ActivityPubService.kt +++ b/src/main/kotlin/dev/usbharu/hideout/service/activitypub/ActivityPubService.kt @@ -5,7 +5,7 @@ import dev.usbharu.hideout.domain.model.ActivityPubResponse interface ActivityPubService { fun parseActivity(json:String): ActivityType - fun processActivity(json:String, type: ActivityType):ActivityPubResponse? + fun processActivity(json:String, type: ActivityType): ActivityPubResponse? } enum class ActivityType { From 2a1824edbf08b8b9333123d8b444015184fe9c83 Mon Sep 17 00:00:00 2001 From: usbharu <64310155+usbharu@users.noreply.github.com> Date: Sat, 8 Apr 2023 14:59:59 +0900 Subject: [PATCH 13/40] =?UTF-8?q?test:=20=E3=83=86=E3=82=B9=E3=83=88?= =?UTF-8?q?=E3=81=AE=E3=82=B3=E3=83=B3=E3=83=91=E3=82=A4=E3=83=AB=E3=81=AB?= =?UTF-8?q?=E5=A4=B1=E6=95=97=E3=81=99=E3=82=8B=E3=81=AE=E3=82=92=E4=BF=AE?= =?UTF-8?q?=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../routing/activitypub/UsersAPTest.kt | 46 ++++++++++++++++++- 1 file changed, 45 insertions(+), 1 deletion(-) diff --git a/src/test/kotlin/dev/usbharu/hideout/routing/activitypub/UsersAPTest.kt b/src/test/kotlin/dev/usbharu/hideout/routing/activitypub/UsersAPTest.kt index fee20a11..a0d0723e 100644 --- a/src/test/kotlin/dev/usbharu/hideout/routing/activitypub/UsersAPTest.kt +++ b/src/test/kotlin/dev/usbharu/hideout/routing/activitypub/UsersAPTest.kt @@ -1,9 +1,13 @@ package dev.usbharu.hideout.routing.activitypub import dev.usbharu.hideout.domain.model.ActivityPubResponse +import dev.usbharu.hideout.domain.model.User +import dev.usbharu.hideout.domain.model.UserEntity import dev.usbharu.hideout.plugins.configureRouting +import dev.usbharu.hideout.repository.IUserRepository import dev.usbharu.hideout.service.activitypub.ActivityPubService import dev.usbharu.hideout.service.activitypub.ActivityType +import dev.usbharu.hideout.service.impl.UserService import dev.usbharu.hideout.service.signature.HttpSignatureVerifyService import dev.usbharu.hideout.util.HttpUtil.Activity import io.ktor.client.request.* @@ -34,7 +38,47 @@ class UsersAPTest { override fun processActivity(json: String, type: ActivityType): ActivityPubResponse? { TODO("Not yet implemented") } - }) + },UserService(object : IUserRepository { + override suspend fun create(user: User): UserEntity { + TODO("Not yet implemented") + } + + override suspend fun findById(id: Long): UserEntity? { + TODO("Not yet implemented") + } + + override suspend fun findByName(name: String): UserEntity? { + TODO("Not yet implemented") + } + + override suspend fun update(userEntity: UserEntity) { + TODO("Not yet implemented") + } + + override suspend fun delete(id: Long) { + TODO("Not yet implemented") + } + + override suspend fun findAll(): List { + TODO("Not yet implemented") + } + + override suspend fun findAllByLimitAndByOffset(limit: Int, offset: Long): List { + TODO("Not yet implemented") + } + + override suspend fun createFollower(id: Long, follower: Long) { + TODO("Not yet implemented") + } + + override suspend fun deleteFollower(id: Long, follower: Long) { + TODO("Not yet implemented") + } + + override suspend fun findFollowersById(id: Long): List { + TODO("Not yet implemented") + } + })) } client.get("/users/test"){ accept(ContentType.Application.Activity) From e77c28c6c203072086e2301a3d074c07d1989d8c Mon Sep 17 00:00:00 2001 From: usbharu <64310155+usbharu@users.noreply.github.com> Date: Sat, 8 Apr 2023 16:00:54 +0900 Subject: [PATCH 14/40] =?UTF-8?q?feat:=20=E3=83=A6=E3=83=BC=E3=82=B6?= =?UTF-8?q?=E3=83=BC=E3=83=9A=E3=83=BC=E3=82=B8=E3=82=92accept=E3=83=98?= =?UTF-8?q?=E3=83=83=E3=83=80=E3=83=BC=E3=81=8Cactivity=E3=81=AE=E7=8A=B6?= =?UTF-8?q?=E6=85=8B=E3=81=A7=E3=82=A2=E3=82=AF=E3=82=BB=E3=82=B9=E3=81=99?= =?UTF-8?q?=E3=82=8B=E3=81=A8person=E3=82=92=E8=BF=94=E3=81=99=E3=82=88?= =?UTF-8?q?=E3=81=86=E3=81=AB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../kotlin/dev/usbharu/hideout/Application.kt | 6 +- .../kotlin/dev/usbharu/hideout/ap/Image.kt | 17 ++++++ .../kotlin/dev/usbharu/hideout/ap/JsonLd.kt | 13 ++++ src/main/kotlin/dev/usbharu/hideout/ap/Key.kt | 18 ++++++ .../kotlin/dev/usbharu/hideout/ap/Object.kt | 16 +++++ .../kotlin/dev/usbharu/hideout/ap/Person.kt | 27 ++++++++ .../dev/usbharu/hideout/plugins/Routing.kt | 6 +- .../usbharu/hideout/plugins/Serialization.kt | 13 +++- .../routing/activitypub/UserRouting.kt | 11 +++- .../activitypub/ActivityPubUserService.kt | 7 +++ .../activitypub/ActivityPubUserServiceImpl.kt | 40 ++++++++++++ .../routing/activitypub/UsersAPTest.kt | 61 +++++++++++++++++-- 12 files changed, 224 insertions(+), 11 deletions(-) create mode 100644 src/main/kotlin/dev/usbharu/hideout/service/activitypub/ActivityPubUserService.kt create mode 100644 src/main/kotlin/dev/usbharu/hideout/service/activitypub/ActivityPubUserServiceImpl.kt diff --git a/src/main/kotlin/dev/usbharu/hideout/Application.kt b/src/main/kotlin/dev/usbharu/hideout/Application.kt index 3770dd5d..02938ee8 100644 --- a/src/main/kotlin/dev/usbharu/hideout/Application.kt +++ b/src/main/kotlin/dev/usbharu/hideout/Application.kt @@ -8,6 +8,8 @@ import dev.usbharu.hideout.repository.UserRepository import dev.usbharu.hideout.service.IUserAuthService import dev.usbharu.hideout.service.activitypub.ActivityPubService import dev.usbharu.hideout.service.activitypub.ActivityPubServiceImpl +import dev.usbharu.hideout.service.activitypub.ActivityPubUserService +import dev.usbharu.hideout.service.activitypub.ActivityPubUserServiceImpl import dev.usbharu.hideout.service.impl.UserAuthService import dev.usbharu.hideout.service.impl.UserService import dev.usbharu.hideout.service.signature.HttpSignatureVerifyService @@ -42,6 +44,7 @@ fun Application.module() { single { HttpSignatureVerifyServiceImpl(get()) } single { ActivityPubServiceImpl() } single { UserService(get()) } + single { ActivityPubUserServiceImpl(get(),get()) } } configureKoin(module) @@ -52,6 +55,7 @@ fun Application.module() { configureRouting( inject().value, inject().value, - inject().value + inject().value, + inject().value ) } diff --git a/src/main/kotlin/dev/usbharu/hideout/ap/Image.kt b/src/main/kotlin/dev/usbharu/hideout/ap/Image.kt index 38d3cbbc..29639e20 100644 --- a/src/main/kotlin/dev/usbharu/hideout/ap/Image.kt +++ b/src/main/kotlin/dev/usbharu/hideout/ap/Image.kt @@ -13,4 +13,21 @@ open class Image : Object { this.url = url } + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other !is Image) return false + if (!super.equals(other)) return false + + if (mediaType != other.mediaType) return false + return url == other.url + } + + override fun hashCode(): Int { + var result = super.hashCode() + result = 31 * result + (mediaType?.hashCode() ?: 0) + result = 31 * result + (url?.hashCode() ?: 0) + return result + } + + } diff --git a/src/main/kotlin/dev/usbharu/hideout/ap/JsonLd.kt b/src/main/kotlin/dev/usbharu/hideout/ap/JsonLd.kt index 51985b34..bef45e70 100644 --- a/src/main/kotlin/dev/usbharu/hideout/ap/JsonLd.kt +++ b/src/main/kotlin/dev/usbharu/hideout/ap/JsonLd.kt @@ -25,6 +25,19 @@ open class JsonLd { } protected constructor() + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other !is JsonLd) return false + + return context == other.context + } + + override fun hashCode(): Int { + return context.hashCode() + } + + } public class ContextDeserializer : JsonDeserializer() { diff --git a/src/main/kotlin/dev/usbharu/hideout/ap/Key.kt b/src/main/kotlin/dev/usbharu/hideout/ap/Key.kt index 0c7470da..ab3ae13b 100644 --- a/src/main/kotlin/dev/usbharu/hideout/ap/Key.kt +++ b/src/main/kotlin/dev/usbharu/hideout/ap/Key.kt @@ -17,5 +17,23 @@ open class Key : Object{ this.publicKeyPem = publicKeyPem } + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other !is Key) return false + if (!super.equals(other)) return false + + if (id != other.id) return false + if (owner != other.owner) return false + return publicKeyPem == other.publicKeyPem + } + + override fun hashCode(): Int { + var result = super.hashCode() + result = 31 * result + (id?.hashCode() ?: 0) + result = 31 * result + (owner?.hashCode() ?: 0) + result = 31 * result + (publicKeyPem?.hashCode() ?: 0) + return result + } + } diff --git a/src/main/kotlin/dev/usbharu/hideout/ap/Object.kt b/src/main/kotlin/dev/usbharu/hideout/ap/Object.kt index 76d609e5..1a05564d 100644 --- a/src/main/kotlin/dev/usbharu/hideout/ap/Object.kt +++ b/src/main/kotlin/dev/usbharu/hideout/ap/Object.kt @@ -24,6 +24,22 @@ open class Object : JsonLd { return toMutableList.distinct() } } + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other !is Object) return false + + if (type != other.type) return false + return name == other.name + } + + override fun hashCode(): Int { + var result = type.hashCode() + result = 31 * result + (name?.hashCode() ?: 0) + return result + } + + } public class TypeSerializer : JsonSerializer>() { diff --git a/src/main/kotlin/dev/usbharu/hideout/ap/Person.kt b/src/main/kotlin/dev/usbharu/hideout/ap/Person.kt index 2a1f29f7..270eba23 100644 --- a/src/main/kotlin/dev/usbharu/hideout/ap/Person.kt +++ b/src/main/kotlin/dev/usbharu/hideout/ap/Person.kt @@ -32,4 +32,31 @@ open class Person : Object { this.publicKey = publicKey } + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other !is Person) return false + + if (id != other.id) return false + if (preferredUsername != other.preferredUsername) return false + if (summary != other.summary) return false + if (inbox != other.inbox) return false + if (outbox != other.outbox) return false + if (url != other.url) return false + if (icon != other.icon) return false + return publicKey == other.publicKey + } + + override fun hashCode(): Int { + var result = id?.hashCode() ?: 0 + result = 31 * result + (preferredUsername?.hashCode() ?: 0) + result = 31 * result + (summary?.hashCode() ?: 0) + result = 31 * result + (inbox?.hashCode() ?: 0) + result = 31 * result + (outbox?.hashCode() ?: 0) + result = 31 * result + (url?.hashCode() ?: 0) + result = 31 * result + (icon?.hashCode() ?: 0) + result = 31 * result + (publicKey?.hashCode() ?: 0) + return result + } + + } diff --git a/src/main/kotlin/dev/usbharu/hideout/plugins/Routing.kt b/src/main/kotlin/dev/usbharu/hideout/plugins/Routing.kt index bddeb896..6b44c0fd 100644 --- a/src/main/kotlin/dev/usbharu/hideout/plugins/Routing.kt +++ b/src/main/kotlin/dev/usbharu/hideout/plugins/Routing.kt @@ -5,6 +5,7 @@ import dev.usbharu.hideout.routing.activitypub.outbox import dev.usbharu.hideout.routing.activitypub.usersAP import dev.usbharu.hideout.routing.wellknown.webfinger import dev.usbharu.hideout.service.activitypub.ActivityPubService +import dev.usbharu.hideout.service.activitypub.ActivityPubUserService import dev.usbharu.hideout.service.impl.UserService import dev.usbharu.hideout.service.signature.HttpSignatureVerifyService import io.ktor.server.application.* @@ -14,13 +15,14 @@ import io.ktor.server.routing.* fun Application.configureRouting( httpSignatureVerifyService: HttpSignatureVerifyService, activityPubService: ActivityPubService, - userService:UserService + userService:UserService, + activityPubUserService: ActivityPubUserService ) { install(AutoHeadResponse) routing { inbox(httpSignatureVerifyService, activityPubService) outbox() - usersAP(activityPubService) + usersAP(activityPubUserService) webfinger(userService) } } diff --git a/src/main/kotlin/dev/usbharu/hideout/plugins/Serialization.kt b/src/main/kotlin/dev/usbharu/hideout/plugins/Serialization.kt index c07bc345..c8b71a7d 100644 --- a/src/main/kotlin/dev/usbharu/hideout/plugins/Serialization.kt +++ b/src/main/kotlin/dev/usbharu/hideout/plugins/Serialization.kt @@ -1,5 +1,11 @@ package dev.usbharu.hideout.plugins +import com.fasterxml.jackson.annotation.JsonInclude +import com.fasterxml.jackson.annotation.JsonSetter +import com.fasterxml.jackson.annotation.Nulls +import com.fasterxml.jackson.databind.DeserializationFeature +import dev.usbharu.hideout.util.HttpUtil.Activity +import io.ktor.http.* import io.ktor.serialization.jackson.* import io.ktor.server.application.* import io.ktor.server.plugins.contentnegotiation.* @@ -8,6 +14,11 @@ import io.ktor.server.routing.* fun Application.configureSerialization() { install(ContentNegotiation) { - jackson() + jackson { + enable(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY) + setSerializationInclusion(JsonInclude.Include.NON_EMPTY) + configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false) + configOverride(List::class.java).setSetterInfo(JsonSetter.Value.forContentNulls(Nulls.AS_EMPTY)) + } } } diff --git a/src/main/kotlin/dev/usbharu/hideout/routing/activitypub/UserRouting.kt b/src/main/kotlin/dev/usbharu/hideout/routing/activitypub/UserRouting.kt index 8848840a..05ab3f2c 100644 --- a/src/main/kotlin/dev/usbharu/hideout/routing/activitypub/UserRouting.kt +++ b/src/main/kotlin/dev/usbharu/hideout/routing/activitypub/UserRouting.kt @@ -1,6 +1,9 @@ package dev.usbharu.hideout.routing.activitypub +import dev.usbharu.hideout.config.Config +import dev.usbharu.hideout.exception.ParameterNotExistException import dev.usbharu.hideout.service.activitypub.ActivityPubService +import dev.usbharu.hideout.service.activitypub.ActivityPubUserService import dev.usbharu.hideout.util.HttpUtil.Activity import io.ktor.http.* import io.ktor.server.application.* @@ -8,11 +11,13 @@ import io.ktor.server.request.* import io.ktor.server.response.* import io.ktor.server.routing.* -fun Routing.usersAP(activityPubService: ActivityPubService){ +fun Routing.usersAP(activityPubUserService:ActivityPubUserService){ route("/users/{name}"){ createChild(ContentTypeRouteSelector(ContentType.Application.Activity)).handle { - - call.respond(HttpStatusCode.NotImplemented) + val name = call.parameters["name"] ?: throw ParameterNotExistException("Parameter(name='name') does not exist.") + val person = activityPubUserService.getPersonByName(name) + call.response.header("Content-Type", ContentType.Application.Activity.toString()) + call.respond(HttpStatusCode.OK,Config.configData.objectMapper.writeValueAsString(person)) } } } diff --git a/src/main/kotlin/dev/usbharu/hideout/service/activitypub/ActivityPubUserService.kt b/src/main/kotlin/dev/usbharu/hideout/service/activitypub/ActivityPubUserService.kt new file mode 100644 index 00000000..55033e8c --- /dev/null +++ b/src/main/kotlin/dev/usbharu/hideout/service/activitypub/ActivityPubUserService.kt @@ -0,0 +1,7 @@ +package dev.usbharu.hideout.service.activitypub + +import dev.usbharu.hideout.ap.Person + +interface ActivityPubUserService { + suspend fun getPersonByName(name:String):Person +} diff --git a/src/main/kotlin/dev/usbharu/hideout/service/activitypub/ActivityPubUserServiceImpl.kt b/src/main/kotlin/dev/usbharu/hideout/service/activitypub/ActivityPubUserServiceImpl.kt new file mode 100644 index 00000000..830359d7 --- /dev/null +++ b/src/main/kotlin/dev/usbharu/hideout/service/activitypub/ActivityPubUserServiceImpl.kt @@ -0,0 +1,40 @@ +package dev.usbharu.hideout.service.activitypub + +import dev.usbharu.hideout.ap.Image +import dev.usbharu.hideout.ap.Key +import dev.usbharu.hideout.ap.Person +import dev.usbharu.hideout.config.Config +import dev.usbharu.hideout.service.IUserAuthService +import dev.usbharu.hideout.service.impl.UserService + +class ActivityPubUserServiceImpl(private val userService: UserService, private val userAuthService: IUserAuthService) : + ActivityPubUserService { + override suspend fun getPersonByName(name: String): Person { + val userEntity = userService.findByName(name) + val userAuthEntity = userAuthService.findByUserId(userEntity.id) + val userUrl = "${Config.configData.url}/users$name" + return Person( + type = emptyList(), + name = userEntity.name, + id = userUrl, + preferredUsername = name, + summary = userEntity.description, + inbox = "$userUrl/inbox", + outbox = "$userUrl/outbox", + url = userUrl, + icon = Image( + type = emptyList(), + name = "$userUrl/icon.png", + mediaType = "image/png", + url = "$userUrl/icon.png" + ), + publicKey = Key( + type = emptyList(), + name = "Public Key", + id = "$userUrl#pubkey", + owner = userUrl, + publicKeyPem = userAuthEntity.publicKey + ) + ) + } +} diff --git a/src/test/kotlin/dev/usbharu/hideout/routing/activitypub/UsersAPTest.kt b/src/test/kotlin/dev/usbharu/hideout/routing/activitypub/UsersAPTest.kt index a0d0723e..226a5e51 100644 --- a/src/test/kotlin/dev/usbharu/hideout/routing/activitypub/UsersAPTest.kt +++ b/src/test/kotlin/dev/usbharu/hideout/routing/activitypub/UsersAPTest.kt @@ -1,16 +1,30 @@ package dev.usbharu.hideout.routing.activitypub +import com.fasterxml.jackson.annotation.JsonInclude +import com.fasterxml.jackson.annotation.JsonSetter +import com.fasterxml.jackson.annotation.Nulls +import com.fasterxml.jackson.databind.DeserializationFeature +import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper +import com.fasterxml.jackson.module.kotlin.readValue +import dev.usbharu.hideout.ap.Image +import dev.usbharu.hideout.ap.Key +import dev.usbharu.hideout.ap.Person +import dev.usbharu.hideout.config.Config import dev.usbharu.hideout.domain.model.ActivityPubResponse import dev.usbharu.hideout.domain.model.User import dev.usbharu.hideout.domain.model.UserEntity import dev.usbharu.hideout.plugins.configureRouting +import dev.usbharu.hideout.plugins.configureSerialization import dev.usbharu.hideout.repository.IUserRepository import dev.usbharu.hideout.service.activitypub.ActivityPubService +import dev.usbharu.hideout.service.activitypub.ActivityPubUserService import dev.usbharu.hideout.service.activitypub.ActivityType import dev.usbharu.hideout.service.impl.UserService import dev.usbharu.hideout.service.signature.HttpSignatureVerifyService import dev.usbharu.hideout.util.HttpUtil.Activity +import io.ktor.client.call.* import io.ktor.client.request.* +import io.ktor.client.statement.* import io.ktor.http.* import io.ktor.server.config.* import io.ktor.server.testing.* @@ -25,7 +39,32 @@ class UsersAPTest { environment { config = ApplicationConfig("empty.conf") } + val person = Person( + type = emptyList(), + name = "test", + id = "http://example.com/users/test", + preferredUsername = "test", + summary = "test user", + inbox = "http://example.com/users/test/inbox", + outbox = "http://example.com/users/test/outbox", + url = "http://example.com/users/test", + icon = Image( + type = emptyList(), + name = "http://example.com/users/test/icon.png", + mediaType = "image/png", + url = "http://example.com/users/test/icon.png" + ), + publicKey = Key( + type = emptyList(), + name = "Public Key", + id = "http://example.com/users/test#pubkey", + owner = "https://example.com/users/test", + publicKeyPem = "-----BEGIN PUBLIC KEY-----\n\n-----END PUBLIC KEY-----" + ) + ) + person.context = listOf("https://www.w3.org/ns/activitystreams") application { + configureSerialization() configureRouting(object : HttpSignatureVerifyService { override fun verify(headers: Headers): Boolean { return true @@ -38,7 +77,7 @@ class UsersAPTest { override fun processActivity(json: String, type: ActivityType): ActivityPubResponse? { TODO("Not yet implemented") } - },UserService(object : IUserRepository { + }, UserService(object : IUserRepository { override suspend fun create(user: User): UserEntity { TODO("Not yet implemented") } @@ -78,12 +117,26 @@ class UsersAPTest { override suspend fun findFollowersById(id: Long): List { TODO("Not yet implemented") } - })) + }), object : ActivityPubUserService { + override suspend fun getPersonByName(name: String): Person { + return person + } + + }) } - client.get("/users/test"){ + client.get("/users/test") { accept(ContentType.Application.Activity) }.let { - assertEquals(HttpStatusCode.NotImplemented, it.status) + val objectMapper = jacksonObjectMapper().enable(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY) + .setSerializationInclusion(JsonInclude.Include.NON_EMPTY) + .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false) + objectMapper.configOverride(List::class.java).setSetterInfo(JsonSetter.Value.forValueNulls( + Nulls.AS_EMPTY)) + val actual = it.bodyAsText() + val readValue = objectMapper.readValue(actual) + println(objectMapper.writeValueAsString(person)) + println(objectMapper.writeValueAsString(readValue)) + assertEquals(person, readValue) } } } From 527750b3a63d9de73283cfa9158732701adf4727 Mon Sep 17 00:00:00 2001 From: usbharu <64310155+usbharu@users.noreply.github.com> Date: Sat, 8 Apr 2023 16:19:58 +0900 Subject: [PATCH 15/40] =?UTF-8?q?fix:=20=E8=A8=AD=E5=AE=9A=E3=83=95?= =?UTF-8?q?=E3=82=A1=E3=82=A4=E3=83=AB=E3=81=8C=E8=AA=AD=E3=81=BF=E8=BE=BC?= =?UTF-8?q?=E3=81=BE=E3=82=8C=E3=81=A6=E3=81=84=E3=81=AA=E3=81=8B=E3=81=A3?= =?UTF-8?q?=E3=81=9F=E3=81=AE=E3=82=92=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/kotlin/dev/usbharu/hideout/Application.kt | 14 +++++++++++++- src/main/resources/application.conf | 4 ++-- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/src/main/kotlin/dev/usbharu/hideout/Application.kt b/src/main/kotlin/dev/usbharu/hideout/Application.kt index 02938ee8..9fe8da22 100644 --- a/src/main/kotlin/dev/usbharu/hideout/Application.kt +++ b/src/main/kotlin/dev/usbharu/hideout/Application.kt @@ -1,5 +1,10 @@ package dev.usbharu.hideout +import com.fasterxml.jackson.annotation.JsonInclude +import com.fasterxml.jackson.databind.DeserializationFeature +import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper +import dev.usbharu.hideout.config.Config +import dev.usbharu.hideout.config.ConfigData import dev.usbharu.hideout.plugins.* import dev.usbharu.hideout.repository.IUserAuthRepository import dev.usbharu.hideout.repository.IUserRepository @@ -28,6 +33,13 @@ val Application.property: Application.(propertyName: String) -> String @Suppress("unused") // application.conf references the main function. This annotation prevents the IDE from marking it as unused. fun Application.module() { + Config.configData = ConfigData( + url = property("hideout.url"), + objectMapper = jacksonObjectMapper().enable(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY) + .setSerializationInclusion(JsonInclude.Include.NON_EMPTY) + .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false) + ) + val module = org.koin.dsl.module { single { Database.connect( @@ -44,7 +56,7 @@ fun Application.module() { single { HttpSignatureVerifyServiceImpl(get()) } single { ActivityPubServiceImpl() } single { UserService(get()) } - single { ActivityPubUserServiceImpl(get(),get()) } + single { ActivityPubUserServiceImpl(get(), get()) } } configureKoin(module) diff --git a/src/main/resources/application.conf b/src/main/resources/application.conf index 0a1d02eb..dd12df2f 100644 --- a/src/main/resources/application.conf +++ b/src/main/resources/application.conf @@ -11,8 +11,8 @@ ktor { } hideout { - hostname = "https://localhost:8080" - hostname = ${?HOSTNAME} + url = "http://localhost:8080" + database { url = "jdbc:h2:./test;MODE=POSTGRESQL" driver = "org.h2.Driver" From 2f15b0e3e4c45137e39b5ccaf3f41f2a93f73f46 Mon Sep 17 00:00:00 2001 From: usbharu <64310155+usbharu@users.noreply.github.com> Date: Sat, 8 Apr 2023 16:28:26 +0900 Subject: [PATCH 16/40] =?UTF-8?q?feat:=20ActivityPub=E3=81=AE=E5=88=A4?= =?UTF-8?q?=E5=AE=9A=E3=82=92=E3=82=86=E3=82=8B=E3=81=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../usbharu/hideout/plugins/StatusPages.kt | 1 - .../routing/activitypub/UserRouting.kt | 20 +++++++++++-------- .../dev/usbharu/hideout/util/HttpUtil.kt | 3 +++ 3 files changed, 15 insertions(+), 9 deletions(-) diff --git a/src/main/kotlin/dev/usbharu/hideout/plugins/StatusPages.kt b/src/main/kotlin/dev/usbharu/hideout/plugins/StatusPages.kt index 990a7acb..cd078e28 100644 --- a/src/main/kotlin/dev/usbharu/hideout/plugins/StatusPages.kt +++ b/src/main/kotlin/dev/usbharu/hideout/plugins/StatusPages.kt @@ -4,7 +4,6 @@ import io.ktor.http.* import io.ktor.server.application.* import io.ktor.server.plugins.statuspages.* import io.ktor.server.response.* -import io.ktor.server.routing.* fun Application.configureStatusPages() { install(StatusPages) { diff --git a/src/main/kotlin/dev/usbharu/hideout/routing/activitypub/UserRouting.kt b/src/main/kotlin/dev/usbharu/hideout/routing/activitypub/UserRouting.kt index 05ab3f2c..37ac3dc4 100644 --- a/src/main/kotlin/dev/usbharu/hideout/routing/activitypub/UserRouting.kt +++ b/src/main/kotlin/dev/usbharu/hideout/routing/activitypub/UserRouting.kt @@ -2,29 +2,33 @@ package dev.usbharu.hideout.routing.activitypub import dev.usbharu.hideout.config.Config import dev.usbharu.hideout.exception.ParameterNotExistException -import dev.usbharu.hideout.service.activitypub.ActivityPubService import dev.usbharu.hideout.service.activitypub.ActivityPubUserService import dev.usbharu.hideout.util.HttpUtil.Activity +import dev.usbharu.hideout.util.HttpUtil.JsonLd import io.ktor.http.* import io.ktor.server.application.* import io.ktor.server.request.* import io.ktor.server.response.* import io.ktor.server.routing.* -fun Routing.usersAP(activityPubUserService:ActivityPubUserService){ - route("/users/{name}"){ - createChild(ContentTypeRouteSelector(ContentType.Application.Activity)).handle { - val name = call.parameters["name"] ?: throw ParameterNotExistException("Parameter(name='name') does not exist.") +fun Routing.usersAP(activityPubUserService: ActivityPubUserService) { + route("/users/{name}") { + createChild(ContentTypeRouteSelector(ContentType.Application.Activity, ContentType.Application.JsonLd)).handle { + val name = + call.parameters["name"] ?: throw ParameterNotExistException("Parameter(name='name') does not exist.") val person = activityPubUserService.getPersonByName(name) call.response.header("Content-Type", ContentType.Application.Activity.toString()) - call.respond(HttpStatusCode.OK,Config.configData.objectMapper.writeValueAsString(person)) + call.respond(HttpStatusCode.OK, Config.configData.objectMapper.writeValueAsString(person)) } } } -class ContentTypeRouteSelector(private val contentType: ContentType) : RouteSelector() { +class ContentTypeRouteSelector(private vararg val contentType: ContentType) : RouteSelector() { override fun evaluate(context: RoutingResolveContext, segmentIndex: Int): RouteSelectorEvaluation { - return if (ContentType.parse(context.call.request.accept() ?: return RouteSelectorEvaluation.FailedParameter) == contentType) { + + val requestContentType = + ContentType.parse(context.call.request.accept() ?: return RouteSelectorEvaluation.FailedParameter) + return if (contentType.any { contentType -> contentType.match(requestContentType) }) { RouteSelectorEvaluation.Constant } else { RouteSelectorEvaluation.FailedParameter diff --git a/src/main/kotlin/dev/usbharu/hideout/util/HttpUtil.kt b/src/main/kotlin/dev/usbharu/hideout/util/HttpUtil.kt index 05c827d3..979b8302 100644 --- a/src/main/kotlin/dev/usbharu/hideout/util/HttpUtil.kt +++ b/src/main/kotlin/dev/usbharu/hideout/util/HttpUtil.kt @@ -28,5 +28,8 @@ object HttpUtil { 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 } From 88fd245d3f8d180ca5552f5d1ea88d8bf94a32df Mon Sep 17 00:00:00 2001 From: usbharu <64310155+usbharu@users.noreply.github.com> Date: Sat, 8 Apr 2023 16:39:47 +0900 Subject: [PATCH 17/40] =?UTF-8?q?feat:=20=E3=83=AD=E3=82=B0=E3=82=92?= =?UTF-8?q?=E8=A9=B3=E7=B4=B0=E3=81=AB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/kotlin/dev/usbharu/hideout/plugins/Monitoring.kt | 1 - .../dev/usbharu/hideout/routing/activitypub/UserRouting.kt | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/main/kotlin/dev/usbharu/hideout/plugins/Monitoring.kt b/src/main/kotlin/dev/usbharu/hideout/plugins/Monitoring.kt index 861880ee..3ce065b7 100644 --- a/src/main/kotlin/dev/usbharu/hideout/plugins/Monitoring.kt +++ b/src/main/kotlin/dev/usbharu/hideout/plugins/Monitoring.kt @@ -8,6 +8,5 @@ import io.ktor.server.application.* fun Application.configureMonitoring() { install(CallLogging) { level = Level.INFO - filter { call -> call.request.path().startsWith("/") } } } diff --git a/src/main/kotlin/dev/usbharu/hideout/routing/activitypub/UserRouting.kt b/src/main/kotlin/dev/usbharu/hideout/routing/activitypub/UserRouting.kt index 37ac3dc4..03583fbf 100644 --- a/src/main/kotlin/dev/usbharu/hideout/routing/activitypub/UserRouting.kt +++ b/src/main/kotlin/dev/usbharu/hideout/routing/activitypub/UserRouting.kt @@ -18,7 +18,7 @@ fun Routing.usersAP(activityPubUserService: ActivityPubUserService) { call.parameters["name"] ?: throw ParameterNotExistException("Parameter(name='name') does not exist.") val person = activityPubUserService.getPersonByName(name) call.response.header("Content-Type", ContentType.Application.Activity.toString()) - call.respond(HttpStatusCode.OK, Config.configData.objectMapper.writeValueAsString(person)) + return@handle call.respond(HttpStatusCode.OK, Config.configData.objectMapper.writeValueAsString(person)) } } } From bdb538afd91de1e2a06fc805dafbcba3c73d5bc1 Mon Sep 17 00:00:00 2001 From: usbharu <64310155+usbharu@users.noreply.github.com> Date: Sat, 8 Apr 2023 16:59:35 +0900 Subject: [PATCH 18/40] =?UTF-8?q?feat:=20=E3=83=AC=E3=82=B9=E3=83=9D?= =?UTF-8?q?=E3=83=B3=E3=82=B9=E3=82=92util=E3=81=A7=E4=BD=9C=E6=88=90?= =?UTF-8?q?=E3=81=99=E3=82=8B=E3=82=88=E3=81=86=E3=81=AB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../dev/usbharu/hideout/routing/activitypub/UserRouting.kt | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/main/kotlin/dev/usbharu/hideout/routing/activitypub/UserRouting.kt b/src/main/kotlin/dev/usbharu/hideout/routing/activitypub/UserRouting.kt index 03583fbf..97280087 100644 --- a/src/main/kotlin/dev/usbharu/hideout/routing/activitypub/UserRouting.kt +++ b/src/main/kotlin/dev/usbharu/hideout/routing/activitypub/UserRouting.kt @@ -2,6 +2,7 @@ package dev.usbharu.hideout.routing.activitypub import dev.usbharu.hideout.config.Config import dev.usbharu.hideout.exception.ParameterNotExistException +import dev.usbharu.hideout.plugins.respondAp import dev.usbharu.hideout.service.activitypub.ActivityPubUserService import dev.usbharu.hideout.util.HttpUtil.Activity import dev.usbharu.hideout.util.HttpUtil.JsonLd @@ -17,8 +18,10 @@ fun Routing.usersAP(activityPubUserService: ActivityPubUserService) { val name = call.parameters["name"] ?: throw ParameterNotExistException("Parameter(name='name') does not exist.") val person = activityPubUserService.getPersonByName(name) - call.response.header("Content-Type", ContentType.Application.Activity.toString()) - return@handle call.respond(HttpStatusCode.OK, Config.configData.objectMapper.writeValueAsString(person)) + return@handle call.respondAp( + person, + HttpStatusCode.OK + ) } } } From a23ffde594e73819e6b07446f93bf6160239a60a Mon Sep 17 00:00:00 2001 From: usbharu <64310155+usbharu@users.noreply.github.com> Date: Sat, 8 Apr 2023 17:08:51 +0900 Subject: [PATCH 19/40] =?UTF-8?q?fix:=20userURl=E3=82=92=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../hideout/service/activitypub/ActivityPubUserServiceImpl.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/kotlin/dev/usbharu/hideout/service/activitypub/ActivityPubUserServiceImpl.kt b/src/main/kotlin/dev/usbharu/hideout/service/activitypub/ActivityPubUserServiceImpl.kt index 830359d7..2b79799e 100644 --- a/src/main/kotlin/dev/usbharu/hideout/service/activitypub/ActivityPubUserServiceImpl.kt +++ b/src/main/kotlin/dev/usbharu/hideout/service/activitypub/ActivityPubUserServiceImpl.kt @@ -12,7 +12,7 @@ class ActivityPubUserServiceImpl(private val userService: UserService, private v override suspend fun getPersonByName(name: String): Person { val userEntity = userService.findByName(name) val userAuthEntity = userAuthService.findByUserId(userEntity.id) - val userUrl = "${Config.configData.url}/users$name" + val userUrl = "${Config.configData.url}/users/$name" return Person( type = emptyList(), name = userEntity.name, From 7eed0e270d8b36e7c26c93e3671bb0f5d702cc1a Mon Sep 17 00:00:00 2001 From: usbharu <64310155+usbharu@users.noreply.github.com> Date: Sat, 8 Apr 2023 17:19:37 +0900 Subject: [PATCH 20/40] =?UTF-8?q?feat:=20register=E3=83=9A=E3=83=BC?= =?UTF-8?q?=E3=82=B8=E3=82=92=E5=BE=A9=E6=B4=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/kotlin/dev/usbharu/hideout/Application.kt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/main/kotlin/dev/usbharu/hideout/Application.kt b/src/main/kotlin/dev/usbharu/hideout/Application.kt index 9fe8da22..5e386662 100644 --- a/src/main/kotlin/dev/usbharu/hideout/Application.kt +++ b/src/main/kotlin/dev/usbharu/hideout/Application.kt @@ -10,6 +10,7 @@ import dev.usbharu.hideout.repository.IUserAuthRepository import dev.usbharu.hideout.repository.IUserRepository import dev.usbharu.hideout.repository.UserAuthRepository import dev.usbharu.hideout.repository.UserRepository +import dev.usbharu.hideout.routing.register import dev.usbharu.hideout.service.IUserAuthService import dev.usbharu.hideout.service.activitypub.ActivityPubService import dev.usbharu.hideout.service.activitypub.ActivityPubServiceImpl @@ -59,11 +60,13 @@ fun Application.module() { single { ActivityPubUserServiceImpl(get(), get()) } } + configureKoin(module) configureHTTP() configureSockets() configureMonitoring() configureSerialization() + register(inject().value) configureRouting( inject().value, inject().value, From 4b0484df64291b1f0690c3fea137af2fdbe1f4e2 Mon Sep 17 00:00:00 2001 From: usbharu <64310155+usbharu@users.noreply.github.com> Date: Sun, 9 Apr 2023 23:05:06 +0900 Subject: [PATCH 21/40] =?UTF-8?q?feat:=20Follow=E3=82=92=E5=8F=97=E3=81=91?= =?UTF-8?q?=E5=8F=96=E3=81=A3=E3=81=9F=E3=81=A8=E3=81=8D=E3=81=AE=E5=87=A6?= =?UTF-8?q?=E7=90=86=E3=82=92=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../kotlin/dev/usbharu/hideout/Application.kt | 14 +++++++---- .../domain/model/ActivityPubStringResponse.kt | 23 +++++++++++++++---- .../domain/model/job/AcceptFollowJob.kt | 5 ++++ .../activitypub/ActivityPubFollowService.kt | 8 +++++++ .../ActivityPubFollowServiceImpl.kt | 16 +++++++++++++ .../service/activitypub/ActivityPubService.kt | 2 +- .../activitypub/ActivityPubServiceImpl.kt | 15 ++++++++---- .../hideout/service/job/JobQueueService.kt | 2 +- .../routing/activitypub/UsersAPTest.kt | 2 +- 9 files changed, 70 insertions(+), 17 deletions(-) create mode 100644 src/main/kotlin/dev/usbharu/hideout/domain/model/job/AcceptFollowJob.kt create mode 100644 src/main/kotlin/dev/usbharu/hideout/service/activitypub/ActivityPubFollowService.kt create mode 100644 src/main/kotlin/dev/usbharu/hideout/service/activitypub/ActivityPubFollowServiceImpl.kt diff --git a/src/main/kotlin/dev/usbharu/hideout/Application.kt b/src/main/kotlin/dev/usbharu/hideout/Application.kt index 5e386662..127d516d 100644 --- a/src/main/kotlin/dev/usbharu/hideout/Application.kt +++ b/src/main/kotlin/dev/usbharu/hideout/Application.kt @@ -12,12 +12,11 @@ import dev.usbharu.hideout.repository.UserAuthRepository import dev.usbharu.hideout.repository.UserRepository import dev.usbharu.hideout.routing.register import dev.usbharu.hideout.service.IUserAuthService -import dev.usbharu.hideout.service.activitypub.ActivityPubService -import dev.usbharu.hideout.service.activitypub.ActivityPubServiceImpl -import dev.usbharu.hideout.service.activitypub.ActivityPubUserService -import dev.usbharu.hideout.service.activitypub.ActivityPubUserServiceImpl +import dev.usbharu.hideout.service.activitypub.* import dev.usbharu.hideout.service.impl.UserAuthService import dev.usbharu.hideout.service.impl.UserService +import dev.usbharu.hideout.service.job.JobQueueService +import dev.usbharu.hideout.service.job.KJobJobQueueService import dev.usbharu.hideout.service.signature.HttpSignatureVerifyService import dev.usbharu.hideout.service.signature.HttpSignatureVerifyServiceImpl import io.ktor.server.application.* @@ -55,7 +54,12 @@ fun Application.module() { single { UserAuthRepository(get()) } single { UserAuthService(get(), get()) } single { HttpSignatureVerifyServiceImpl(get()) } - single { ActivityPubServiceImpl() } + single { val kJobJobQueueService = KJobJobQueueService(get()) + kJobJobQueueService.init(listOf()) + kJobJobQueueService + } + single{ ActivityPubFollowServiceImpl(get()) } + single { ActivityPubServiceImpl(get()) } single { UserService(get()) } single { ActivityPubUserServiceImpl(get(), get()) } } diff --git a/src/main/kotlin/dev/usbharu/hideout/domain/model/ActivityPubStringResponse.kt b/src/main/kotlin/dev/usbharu/hideout/domain/model/ActivityPubStringResponse.kt index 643502a6..294a1513 100644 --- a/src/main/kotlin/dev/usbharu/hideout/domain/model/ActivityPubStringResponse.kt +++ b/src/main/kotlin/dev/usbharu/hideout/domain/model/ActivityPubStringResponse.kt @@ -1,11 +1,24 @@ package dev.usbharu.hideout.domain.model import dev.usbharu.hideout.ap.JsonLd +import dev.usbharu.hideout.util.HttpUtil.Activity import io.ktor.http.* -sealed class ActivityPubResponse(val httpStatusCode: HttpStatusCode) -class ActivityPubStringResponse(httpStatusCode: HttpStatusCode, val message: String) : - ActivityPubResponse(httpStatusCode) +sealed class ActivityPubResponse( + val httpStatusCode: HttpStatusCode, + val contentType: ContentType = ContentType.Application.Activity +) -class ActivityPubObjectResponse(httpStatusCode: HttpStatusCode, val message: JsonLd) : - ActivityPubResponse(httpStatusCode) +class ActivityPubStringResponse( + httpStatusCode: HttpStatusCode, + val message: String, + contentType: ContentType = ContentType.Application.Activity +) : + ActivityPubResponse(httpStatusCode, contentType) + +class ActivityPubObjectResponse( + httpStatusCode: HttpStatusCode, + val message: JsonLd, + contentType: ContentType = ContentType.Application.Activity +) : + ActivityPubResponse(httpStatusCode, contentType) diff --git a/src/main/kotlin/dev/usbharu/hideout/domain/model/job/AcceptFollowJob.kt b/src/main/kotlin/dev/usbharu/hideout/domain/model/job/AcceptFollowJob.kt new file mode 100644 index 00000000..97748c4e --- /dev/null +++ b/src/main/kotlin/dev/usbharu/hideout/domain/model/job/AcceptFollowJob.kt @@ -0,0 +1,5 @@ +package dev.usbharu.hideout.domain.model.job + +import kjob.core.Job + +object AcceptFollowJob : Job("AcceptFollowJob") diff --git a/src/main/kotlin/dev/usbharu/hideout/service/activitypub/ActivityPubFollowService.kt b/src/main/kotlin/dev/usbharu/hideout/service/activitypub/ActivityPubFollowService.kt new file mode 100644 index 00000000..af86ffcd --- /dev/null +++ b/src/main/kotlin/dev/usbharu/hideout/service/activitypub/ActivityPubFollowService.kt @@ -0,0 +1,8 @@ +package dev.usbharu.hideout.service.activitypub + +import dev.usbharu.hideout.ap.Follow +import dev.usbharu.hideout.domain.model.ActivityPubResponse + +interface ActivityPubFollowService { + suspend fun receiveFollow(follow:Follow):ActivityPubResponse +} diff --git a/src/main/kotlin/dev/usbharu/hideout/service/activitypub/ActivityPubFollowServiceImpl.kt b/src/main/kotlin/dev/usbharu/hideout/service/activitypub/ActivityPubFollowServiceImpl.kt new file mode 100644 index 00000000..1fed2d86 --- /dev/null +++ b/src/main/kotlin/dev/usbharu/hideout/service/activitypub/ActivityPubFollowServiceImpl.kt @@ -0,0 +1,16 @@ +package dev.usbharu.hideout.service.activitypub + +import dev.usbharu.hideout.ap.Follow +import dev.usbharu.hideout.domain.model.ActivityPubResponse +import dev.usbharu.hideout.domain.model.ActivityPubStringResponse +import dev.usbharu.hideout.domain.model.job.AcceptFollowJob +import dev.usbharu.hideout.service.job.JobQueueService +import io.ktor.http.* + +class ActivityPubFollowServiceImpl(private val jobQueueService: JobQueueService) : ActivityPubFollowService { + override suspend fun receiveFollow(follow: Follow): ActivityPubResponse { + // TODO: Verify HTTP Signature + jobQueueService.schedule(AcceptFollowJob) + return ActivityPubStringResponse(HttpStatusCode.OK,"{}",ContentType.Application.Json) + } +} diff --git a/src/main/kotlin/dev/usbharu/hideout/service/activitypub/ActivityPubService.kt b/src/main/kotlin/dev/usbharu/hideout/service/activitypub/ActivityPubService.kt index 7558f2c3..5db5a1aa 100644 --- a/src/main/kotlin/dev/usbharu/hideout/service/activitypub/ActivityPubService.kt +++ b/src/main/kotlin/dev/usbharu/hideout/service/activitypub/ActivityPubService.kt @@ -5,7 +5,7 @@ import dev.usbharu.hideout.domain.model.ActivityPubResponse interface ActivityPubService { fun parseActivity(json:String): ActivityType - fun processActivity(json:String, type: ActivityType): ActivityPubResponse? + suspend fun processActivity(json:String, type: ActivityType): ActivityPubResponse? } enum class ActivityType { diff --git a/src/main/kotlin/dev/usbharu/hideout/service/activitypub/ActivityPubServiceImpl.kt b/src/main/kotlin/dev/usbharu/hideout/service/activitypub/ActivityPubServiceImpl.kt index a9ea3b7f..10ca0e13 100644 --- a/src/main/kotlin/dev/usbharu/hideout/service/activitypub/ActivityPubServiceImpl.kt +++ b/src/main/kotlin/dev/usbharu/hideout/service/activitypub/ActivityPubServiceImpl.kt @@ -1,11 +1,12 @@ package dev.usbharu.hideout.service.activitypub import com.fasterxml.jackson.databind.JsonNode +import dev.usbharu.hideout.ap.Follow import dev.usbharu.hideout.config.Config import dev.usbharu.hideout.domain.model.ActivityPubResponse import dev.usbharu.hideout.exception.JsonParseException -class ActivityPubServiceImpl : ActivityPubService { +class ActivityPubServiceImpl(private val activityPubFollowService: ActivityPubFollowService) : ActivityPubService { override fun parseActivity(json: String): ActivityType { val readTree = Config.configData.objectMapper.readTree(json) if (readTree.isObject.not()) { @@ -20,8 +21,8 @@ class ActivityPubServiceImpl : ActivityPubService { return ActivityType.values().first { it.name.equals(type.asText(), true) } } - override fun processActivity(json: String, type: ActivityType): ActivityPubResponse? { - when (type) { + override suspend fun processActivity(json: String, type: ActivityType): ActivityPubResponse? { + return when (type) { ActivityType.Accept -> TODO() ActivityType.Add -> TODO() ActivityType.Announce -> TODO() @@ -31,7 +32,13 @@ class ActivityPubServiceImpl : ActivityPubService { ActivityType.Delete -> TODO() ActivityType.Dislike -> TODO() ActivityType.Flag -> TODO() - ActivityType.Follow -> TODO() + ActivityType.Follow -> activityPubFollowService.receiveFollow( + Config.configData.objectMapper.readValue( + json, + Follow::class.java + ) + ) + ActivityType.Ignore -> TODO() ActivityType.Invite -> TODO() ActivityType.Join -> TODO() diff --git a/src/main/kotlin/dev/usbharu/hideout/service/job/JobQueueService.kt b/src/main/kotlin/dev/usbharu/hideout/service/job/JobQueueService.kt index 94486f45..03cc1826 100644 --- a/src/main/kotlin/dev/usbharu/hideout/service/job/JobQueueService.kt +++ b/src/main/kotlin/dev/usbharu/hideout/service/job/JobQueueService.kt @@ -6,5 +6,5 @@ import kjob.core.dsl.ScheduleContext interface JobQueueService { fun init(jobDefines:List) - suspend fun schedule(job: J, block: ScheduleContext.(J) -> Unit) + suspend fun schedule(job: J, block: ScheduleContext.(J) -> Unit = {}) } diff --git a/src/test/kotlin/dev/usbharu/hideout/routing/activitypub/UsersAPTest.kt b/src/test/kotlin/dev/usbharu/hideout/routing/activitypub/UsersAPTest.kt index 226a5e51..1aa2f7d8 100644 --- a/src/test/kotlin/dev/usbharu/hideout/routing/activitypub/UsersAPTest.kt +++ b/src/test/kotlin/dev/usbharu/hideout/routing/activitypub/UsersAPTest.kt @@ -74,7 +74,7 @@ class UsersAPTest { TODO("Not yet implemented") } - override fun processActivity(json: String, type: ActivityType): ActivityPubResponse? { + override suspend fun processActivity(json: String, type: ActivityType): ActivityPubResponse? { TODO("Not yet implemented") } }, UserService(object : IUserRepository { From 2faef66dc24823e9df1f73cf9c6196f27ad8c8d5 Mon Sep 17 00:00:00 2001 From: usbharu <64310155+usbharu@users.noreply.github.com> Date: Sun, 9 Apr 2023 23:08:19 +0900 Subject: [PATCH 22/40] =?UTF-8?q?refactor:=20JobQueue=E9=96=A2=E9=80=A3?= =?UTF-8?q?=E3=81=AE=E5=90=8D=E5=89=8D=E3=82=92=E5=A4=89=E6=9B=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/kotlin/dev/usbharu/hideout/Application.kt | 6 +++--- .../service/activitypub/ActivityPubFollowServiceImpl.kt | 6 +++--- .../job/{JobQueueService.kt => JobQueueParentService.kt} | 2 +- .../job/{JobWorkerService.kt => JobQueueWorkerService.kt} | 2 +- ...{KJobJobQueueService.kt => KJobJobQueueParentService.kt} | 2 +- ...KJobJobWorkerService.kt => KJobJobQueueWorkerService.kt} | 2 +- ...orkerServiceTest.kt => KJobJobQueueWorkerServiceTest.kt} | 4 ++-- 7 files changed, 12 insertions(+), 12 deletions(-) rename src/main/kotlin/dev/usbharu/hideout/service/job/{JobQueueService.kt => JobQueueParentService.kt} (86%) rename src/main/kotlin/dev/usbharu/hideout/service/job/{JobWorkerService.kt => JobQueueWorkerService.kt} (90%) rename src/main/kotlin/dev/usbharu/hideout/service/job/{KJobJobQueueService.kt => KJobJobQueueParentService.kt} (85%) rename src/main/kotlin/dev/usbharu/hideout/service/job/{KJobJobWorkerService.kt => KJobJobQueueWorkerService.kt} (89%) rename src/test/kotlin/dev/usbharu/hideout/service/job/{KJobJobWorkerServiceTest.kt => KJobJobQueueWorkerServiceTest.kt} (70%) diff --git a/src/main/kotlin/dev/usbharu/hideout/Application.kt b/src/main/kotlin/dev/usbharu/hideout/Application.kt index 127d516d..0850d592 100644 --- a/src/main/kotlin/dev/usbharu/hideout/Application.kt +++ b/src/main/kotlin/dev/usbharu/hideout/Application.kt @@ -15,8 +15,8 @@ import dev.usbharu.hideout.service.IUserAuthService import dev.usbharu.hideout.service.activitypub.* import dev.usbharu.hideout.service.impl.UserAuthService import dev.usbharu.hideout.service.impl.UserService -import dev.usbharu.hideout.service.job.JobQueueService -import dev.usbharu.hideout.service.job.KJobJobQueueService +import dev.usbharu.hideout.service.job.JobQueueParentService +import dev.usbharu.hideout.service.job.KJobJobQueueParentService import dev.usbharu.hideout.service.signature.HttpSignatureVerifyService import dev.usbharu.hideout.service.signature.HttpSignatureVerifyServiceImpl import io.ktor.server.application.* @@ -54,7 +54,7 @@ fun Application.module() { single { UserAuthRepository(get()) } single { UserAuthService(get(), get()) } single { HttpSignatureVerifyServiceImpl(get()) } - single { val kJobJobQueueService = KJobJobQueueService(get()) + single { val kJobJobQueueService = KJobJobQueueParentService(get()) kJobJobQueueService.init(listOf()) kJobJobQueueService } diff --git a/src/main/kotlin/dev/usbharu/hideout/service/activitypub/ActivityPubFollowServiceImpl.kt b/src/main/kotlin/dev/usbharu/hideout/service/activitypub/ActivityPubFollowServiceImpl.kt index 1fed2d86..3e7a5dc3 100644 --- a/src/main/kotlin/dev/usbharu/hideout/service/activitypub/ActivityPubFollowServiceImpl.kt +++ b/src/main/kotlin/dev/usbharu/hideout/service/activitypub/ActivityPubFollowServiceImpl.kt @@ -4,13 +4,13 @@ import dev.usbharu.hideout.ap.Follow import dev.usbharu.hideout.domain.model.ActivityPubResponse import dev.usbharu.hideout.domain.model.ActivityPubStringResponse import dev.usbharu.hideout.domain.model.job.AcceptFollowJob -import dev.usbharu.hideout.service.job.JobQueueService +import dev.usbharu.hideout.service.job.JobQueueParentService import io.ktor.http.* -class ActivityPubFollowServiceImpl(private val jobQueueService: JobQueueService) : ActivityPubFollowService { +class ActivityPubFollowServiceImpl(private val jobQueueParentService: JobQueueParentService) : ActivityPubFollowService { override suspend fun receiveFollow(follow: Follow): ActivityPubResponse { // TODO: Verify HTTP Signature - jobQueueService.schedule(AcceptFollowJob) + jobQueueParentService.schedule(AcceptFollowJob) return ActivityPubStringResponse(HttpStatusCode.OK,"{}",ContentType.Application.Json) } } diff --git a/src/main/kotlin/dev/usbharu/hideout/service/job/JobQueueService.kt b/src/main/kotlin/dev/usbharu/hideout/service/job/JobQueueParentService.kt similarity index 86% rename from src/main/kotlin/dev/usbharu/hideout/service/job/JobQueueService.kt rename to src/main/kotlin/dev/usbharu/hideout/service/job/JobQueueParentService.kt index 03cc1826..4029514b 100644 --- a/src/main/kotlin/dev/usbharu/hideout/service/job/JobQueueService.kt +++ b/src/main/kotlin/dev/usbharu/hideout/service/job/JobQueueParentService.kt @@ -3,7 +3,7 @@ package dev.usbharu.hideout.service.job import kjob.core.Job import kjob.core.dsl.ScheduleContext -interface JobQueueService { +interface JobQueueParentService { fun init(jobDefines:List) suspend fun schedule(job: J, block: ScheduleContext.(J) -> Unit = {}) diff --git a/src/main/kotlin/dev/usbharu/hideout/service/job/JobWorkerService.kt b/src/main/kotlin/dev/usbharu/hideout/service/job/JobQueueWorkerService.kt similarity index 90% rename from src/main/kotlin/dev/usbharu/hideout/service/job/JobWorkerService.kt rename to src/main/kotlin/dev/usbharu/hideout/service/job/JobQueueWorkerService.kt index 86e8d69c..0d15caae 100644 --- a/src/main/kotlin/dev/usbharu/hideout/service/job/JobWorkerService.kt +++ b/src/main/kotlin/dev/usbharu/hideout/service/job/JobQueueWorkerService.kt @@ -5,6 +5,6 @@ import kjob.core.dsl.JobContextWithProps import kjob.core.dsl.JobRegisterContext import kjob.core.dsl.KJobFunctions -interface JobWorkerService { +interface JobQueueWorkerService { fun init(defines: List>.(Job) -> KJobFunctions>>>) } diff --git a/src/main/kotlin/dev/usbharu/hideout/service/job/KJobJobQueueService.kt b/src/main/kotlin/dev/usbharu/hideout/service/job/KJobJobQueueParentService.kt similarity index 85% rename from src/main/kotlin/dev/usbharu/hideout/service/job/KJobJobQueueService.kt rename to src/main/kotlin/dev/usbharu/hideout/service/job/KJobJobQueueParentService.kt index f09d22d0..1f4178b5 100644 --- a/src/main/kotlin/dev/usbharu/hideout/service/job/KJobJobQueueService.kt +++ b/src/main/kotlin/dev/usbharu/hideout/service/job/KJobJobQueueParentService.kt @@ -7,7 +7,7 @@ import kjob.core.dsl.ScheduleContext import kjob.core.kjob import org.jetbrains.exposed.sql.Database -class KJobJobQueueService(private val database: Database) : JobQueueService { +class KJobJobQueueParentService(private val database: Database) : JobQueueParentService { val kjob: KJob = kjob(ExposedKJob) { connectionDatabase = database diff --git a/src/main/kotlin/dev/usbharu/hideout/service/job/KJobJobWorkerService.kt b/src/main/kotlin/dev/usbharu/hideout/service/job/KJobJobQueueWorkerService.kt similarity index 89% rename from src/main/kotlin/dev/usbharu/hideout/service/job/KJobJobWorkerService.kt rename to src/main/kotlin/dev/usbharu/hideout/service/job/KJobJobQueueWorkerService.kt index 5ee5a6ba..cab3dcc8 100644 --- a/src/main/kotlin/dev/usbharu/hideout/service/job/KJobJobWorkerService.kt +++ b/src/main/kotlin/dev/usbharu/hideout/service/job/KJobJobQueueWorkerService.kt @@ -8,7 +8,7 @@ import kjob.core.dsl.KJobFunctions import kjob.core.kjob import org.jetbrains.exposed.sql.Database -class KJobJobWorkerService(private val database: Database) : JobWorkerService { +class KJobJobQueueWorkerService(private val database: Database) : JobQueueWorkerService { val kjob by lazy { kjob(ExposedKJob) { diff --git a/src/test/kotlin/dev/usbharu/hideout/service/job/KJobJobWorkerServiceTest.kt b/src/test/kotlin/dev/usbharu/hideout/service/job/KJobJobQueueWorkerServiceTest.kt similarity index 70% rename from src/test/kotlin/dev/usbharu/hideout/service/job/KJobJobWorkerServiceTest.kt rename to src/test/kotlin/dev/usbharu/hideout/service/job/KJobJobQueueWorkerServiceTest.kt index 8ba24871..0244399a 100644 --- a/src/test/kotlin/dev/usbharu/hideout/service/job/KJobJobWorkerServiceTest.kt +++ b/src/test/kotlin/dev/usbharu/hideout/service/job/KJobJobQueueWorkerServiceTest.kt @@ -4,13 +4,13 @@ import kjob.core.Job import org.jetbrains.exposed.sql.Database import org.junit.jupiter.api.Test -class KJobJobWorkerServiceTest { +class KJobJobQueueWorkerServiceTest { object TestJob : Job("test-job") @Test fun init() { - val kJobJobWorkerService = KJobJobWorkerService(Database.connect("jdbc:h2:mem:")) + val kJobJobWorkerService = KJobJobQueueWorkerService(Database.connect("jdbc:h2:mem:")) kJobJobWorkerService.init(listOf(TestJob to { it -> execute { it as TestJob;println(it.propNames) } })) } } From 839aa693ce2331f7112c65d2ae8c91e227f340a4 Mon Sep 17 00:00:00 2001 From: usbharu <64310155+usbharu@users.noreply.github.com> Date: Mon, 10 Apr 2023 00:36:36 +0900 Subject: [PATCH 23/40] =?UTF-8?q?feat:=20=E3=83=95=E3=82=A9=E3=83=AD?= =?UTF-8?q?=E3=83=BC=E3=81=95=E3=82=8C=E3=81=9F=E3=81=A8=E3=81=8D=E3=81=AB?= =?UTF-8?q?Accept=E3=82=92=E8=BF=94=E3=81=99=E3=82=88=E3=81=86=E3=81=AB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../kotlin/dev/usbharu/hideout/Application.kt | 18 +++++-- .../dev/usbharu/hideout/domain/model/User.kt | 32 +++++++++++-- .../domain/model/job/AcceptFollowJob.kt | 5 -- .../domain/model/job/ReceiveFollowJob.kt | 9 ++++ .../hideout/repository/IUserRepository.kt | 2 + .../hideout/repository/UserRepository.kt | 27 +++++++++-- .../ActivityPubFollowServiceImpl.kt | 32 +++++++++++-- .../activitypub/ActivityPubUserService.kt | 2 + .../activitypub/ActivityPubUserServiceImpl.kt | 47 ++++++++++++++++++- .../hideout/service/impl/UserAuthService.kt | 14 +++--- .../hideout/service/impl/UserService.kt | 4 ++ .../hideout/service/impl/WebFingerService.kt | 6 ++- 12 files changed, 169 insertions(+), 29 deletions(-) delete mode 100644 src/main/kotlin/dev/usbharu/hideout/domain/model/job/AcceptFollowJob.kt create mode 100644 src/main/kotlin/dev/usbharu/hideout/domain/model/job/ReceiveFollowJob.kt diff --git a/src/main/kotlin/dev/usbharu/hideout/Application.kt b/src/main/kotlin/dev/usbharu/hideout/Application.kt index 0850d592..9321677c 100644 --- a/src/main/kotlin/dev/usbharu/hideout/Application.kt +++ b/src/main/kotlin/dev/usbharu/hideout/Application.kt @@ -19,6 +19,9 @@ import dev.usbharu.hideout.service.job.JobQueueParentService import dev.usbharu.hideout.service.job.KJobJobQueueParentService import dev.usbharu.hideout.service.signature.HttpSignatureVerifyService import dev.usbharu.hideout.service.signature.HttpSignatureVerifyServiceImpl +import io.ktor.client.* +import io.ktor.client.engine.cio.* +import io.ktor.client.plugins.logging.* import io.ktor.server.application.* import org.jetbrains.exposed.sql.Database import org.koin.ktor.ext.inject @@ -54,14 +57,23 @@ fun Application.module() { single { UserAuthRepository(get()) } single { UserAuthService(get(), get()) } single { HttpSignatureVerifyServiceImpl(get()) } - single { val kJobJobQueueService = KJobJobQueueParentService(get()) + single { + val kJobJobQueueService = KJobJobQueueParentService(get()) kJobJobQueueService.init(listOf()) kJobJobQueueService } - single{ ActivityPubFollowServiceImpl(get()) } + single { + HttpClient(CIO).config { + install(Logging) { + logger = Logger.DEFAULT + level = LogLevel.ALL + } + } + } + single { ActivityPubFollowServiceImpl(get(), get(), get()) } single { ActivityPubServiceImpl(get()) } single { UserService(get()) } - single { ActivityPubUserServiceImpl(get(), get()) } + single { ActivityPubUserServiceImpl(get(), get(),get()) } } diff --git a/src/main/kotlin/dev/usbharu/hideout/domain/model/User.kt b/src/main/kotlin/dev/usbharu/hideout/domain/model/User.kt index 19893891..28e57f99 100644 --- a/src/main/kotlin/dev/usbharu/hideout/domain/model/User.kt +++ b/src/main/kotlin/dev/usbharu/hideout/domain/model/User.kt @@ -2,16 +2,36 @@ package dev.usbharu.hideout.domain.model import org.jetbrains.exposed.dao.id.LongIdTable -data class User(val name: String,val domain: String, val screenName: String, val description: String) +data class User( + val name: String, + val domain: String, + val screenName: String, + val description: String, + val inbox: String, + val outbox: String, + val url: String +) data class UserEntity( val id: Long, val name: String, - val domain:String, + val domain: String, val screenName: String, - val description: String + val description: String, + val inbox: String, + val outbox: String, + val url: String ) { - constructor(id: Long, user: User) : this(id, user.name,user.domain, user.screenName, user.description) + constructor(id: Long, user: User) : this( + id, + user.name, + user.domain, + user.screenName, + user.description, + user.inbox, + user.outbox, + user.url + ) } object Users : LongIdTable("users") { @@ -19,6 +39,10 @@ object Users : LongIdTable("users") { val domain = varchar("domain", length = 255) val screenName = varchar("screen_name", length = 64) val description = varchar("description", length = 600) + val inbox = varchar("inbox", length = 255).uniqueIndex() + val outbox = varchar("outbox", length = 255).uniqueIndex() + val url = varchar("url", length = 255).uniqueIndex() + init { uniqueIndex(name, domain) } diff --git a/src/main/kotlin/dev/usbharu/hideout/domain/model/job/AcceptFollowJob.kt b/src/main/kotlin/dev/usbharu/hideout/domain/model/job/AcceptFollowJob.kt deleted file mode 100644 index 97748c4e..00000000 --- a/src/main/kotlin/dev/usbharu/hideout/domain/model/job/AcceptFollowJob.kt +++ /dev/null @@ -1,5 +0,0 @@ -package dev.usbharu.hideout.domain.model.job - -import kjob.core.Job - -object AcceptFollowJob : Job("AcceptFollowJob") diff --git a/src/main/kotlin/dev/usbharu/hideout/domain/model/job/ReceiveFollowJob.kt b/src/main/kotlin/dev/usbharu/hideout/domain/model/job/ReceiveFollowJob.kt new file mode 100644 index 00000000..6a200ec9 --- /dev/null +++ b/src/main/kotlin/dev/usbharu/hideout/domain/model/job/ReceiveFollowJob.kt @@ -0,0 +1,9 @@ +package dev.usbharu.hideout.domain.model.job + +import kjob.core.Job + +object ReceiveFollowJob : Job("ReceiveFollowJob"){ + val actor = string("actor") + val follow = string("follow") + val targetActor = string("targetActor") +} diff --git a/src/main/kotlin/dev/usbharu/hideout/repository/IUserRepository.kt b/src/main/kotlin/dev/usbharu/hideout/repository/IUserRepository.kt index 8da272fc..53001878 100644 --- a/src/main/kotlin/dev/usbharu/hideout/repository/IUserRepository.kt +++ b/src/main/kotlin/dev/usbharu/hideout/repository/IUserRepository.kt @@ -10,6 +10,8 @@ interface IUserRepository { suspend fun findByName(name: String): UserEntity? + suspend fun findByUrl(url:String):UserEntity? + suspend fun update(userEntity: UserEntity) suspend fun delete(id: Long) diff --git a/src/main/kotlin/dev/usbharu/hideout/repository/UserRepository.kt b/src/main/kotlin/dev/usbharu/hideout/repository/UserRepository.kt index c945aab8..2591e026 100644 --- a/src/main/kotlin/dev/usbharu/hideout/repository/UserRepository.kt +++ b/src/main/kotlin/dev/usbharu/hideout/repository/UserRepository.kt @@ -25,7 +25,10 @@ class UserRepository(private val database: Database) : IUserRepository { this[Users.name], this[Users.domain], this[Users.screenName], - this[Users.description] + this[Users.description], + this[Users.inbox], + this[Users.outbox], + this[Users.url] ) } @@ -35,7 +38,10 @@ class UserRepository(private val database: Database) : IUserRepository { this[Users.name], this[Users.domain], this[Users.screenName], - this[Users.description] + this[Users.description], + this[Users.inbox], + this[Users.outbox], + this[Users.url], ) } @@ -78,6 +84,12 @@ class UserRepository(private val database: Database) : IUserRepository { } } + override suspend fun findByUrl(url: String): UserEntity? { + return query { + Users.select { Users.url eq url }.singleOrNull()?.toUserEntity() + } + } + override suspend fun findFollowersById(id: Long): List { return query { val followers = Users.alias("FOLLOWERS") @@ -91,7 +103,13 @@ class UserRepository(private val database: Database) : IUserRepository { onColumn = { UsersFollowers.followerId }, otherColumn = { followers[Users.id] }) - .slice(followers.get(Users.id), followers.get(Users.name), followers.get(Users.domain), followers.get(Users.screenName), followers.get(Users.description)) + .slice( + followers.get(Users.id), + followers.get(Users.name), + followers.get(Users.domain), + followers.get(Users.screenName), + followers.get(Users.description) + ) .select { Users.id eq id } .map { UserEntity( @@ -100,6 +118,9 @@ class UserRepository(private val database: Database) : IUserRepository { domain = it[followers[Users.domain]], screenName = it[followers[Users.screenName]], description = it[followers[Users.description]], + inbox = it[followers[Users.inbox]], + outbox = it[followers[Users.outbox]], + url = it[followers[Users.url]], ) } } diff --git a/src/main/kotlin/dev/usbharu/hideout/service/activitypub/ActivityPubFollowServiceImpl.kt b/src/main/kotlin/dev/usbharu/hideout/service/activitypub/ActivityPubFollowServiceImpl.kt index 3e7a5dc3..3f311e5a 100644 --- a/src/main/kotlin/dev/usbharu/hideout/service/activitypub/ActivityPubFollowServiceImpl.kt +++ b/src/main/kotlin/dev/usbharu/hideout/service/activitypub/ActivityPubFollowServiceImpl.kt @@ -1,16 +1,40 @@ package dev.usbharu.hideout.service.activitypub +import com.fasterxml.jackson.module.kotlin.readValue import dev.usbharu.hideout.ap.Follow +import dev.usbharu.hideout.config.Config import dev.usbharu.hideout.domain.model.ActivityPubResponse import dev.usbharu.hideout.domain.model.ActivityPubStringResponse -import dev.usbharu.hideout.domain.model.job.AcceptFollowJob +import dev.usbharu.hideout.domain.model.job.ReceiveFollowJob +import dev.usbharu.hideout.plugins.postAp import dev.usbharu.hideout.service.job.JobQueueParentService +import io.ktor.client.* import io.ktor.http.* +import kjob.core.job.JobProps -class ActivityPubFollowServiceImpl(private val jobQueueParentService: JobQueueParentService) : ActivityPubFollowService { +class ActivityPubFollowServiceImpl( + private val jobQueueParentService: JobQueueParentService, + private val activityPubUserService: ActivityPubUserService, + private val httpClient: HttpClient +) : ActivityPubFollowService { override suspend fun receiveFollow(follow: Follow): ActivityPubResponse { // TODO: Verify HTTP Signature - jobQueueParentService.schedule(AcceptFollowJob) - return ActivityPubStringResponse(HttpStatusCode.OK,"{}",ContentType.Application.Json) + jobQueueParentService.schedule(ReceiveFollowJob) { + props[it.actor] = follow.actor + props[it.follow] = Config.configData.objectMapper.writeValueAsString(follow) + props[it.targetActor] = follow.`object` + } + return ActivityPubStringResponse(HttpStatusCode.OK, "{}", ContentType.Application.Json) + } + + suspend fun receiveFollowJob(props: JobProps) { + val actor = props[ReceiveFollowJob.actor] + val person = activityPubUserService.fetchPerson(actor) + val follow = Config.configData.objectMapper.readValue(props[ReceiveFollowJob.follow]) + httpClient.postAp( + urlString = person.inbox ?: throw IllegalArgumentException("inbox is not found"), + username = "${props[ReceiveFollowJob.targetActor]}#pubkey", + jsonLd = follow + ) } } diff --git a/src/main/kotlin/dev/usbharu/hideout/service/activitypub/ActivityPubUserService.kt b/src/main/kotlin/dev/usbharu/hideout/service/activitypub/ActivityPubUserService.kt index 55033e8c..0b67e383 100644 --- a/src/main/kotlin/dev/usbharu/hideout/service/activitypub/ActivityPubUserService.kt +++ b/src/main/kotlin/dev/usbharu/hideout/service/activitypub/ActivityPubUserService.kt @@ -4,4 +4,6 @@ import dev.usbharu.hideout.ap.Person interface ActivityPubUserService { suspend fun getPersonByName(name:String):Person + + suspend fun fetchPerson(url:String):Person } diff --git a/src/main/kotlin/dev/usbharu/hideout/service/activitypub/ActivityPubUserServiceImpl.kt b/src/main/kotlin/dev/usbharu/hideout/service/activitypub/ActivityPubUserServiceImpl.kt index 2b79799e..4af88bac 100644 --- a/src/main/kotlin/dev/usbharu/hideout/service/activitypub/ActivityPubUserServiceImpl.kt +++ b/src/main/kotlin/dev/usbharu/hideout/service/activitypub/ActivityPubUserServiceImpl.kt @@ -1,15 +1,23 @@ package dev.usbharu.hideout.service.activitypub +import com.fasterxml.jackson.module.kotlin.readValue import dev.usbharu.hideout.ap.Image import dev.usbharu.hideout.ap.Key import dev.usbharu.hideout.ap.Person import dev.usbharu.hideout.config.Config +import dev.usbharu.hideout.exception.UserNotFoundException import dev.usbharu.hideout.service.IUserAuthService import dev.usbharu.hideout.service.impl.UserService +import dev.usbharu.hideout.util.HttpUtil.Activity +import io.ktor.client.* +import io.ktor.client.request.* +import io.ktor.client.statement.* +import io.ktor.http.* -class ActivityPubUserServiceImpl(private val userService: UserService, private val userAuthService: IUserAuthService) : +class ActivityPubUserServiceImpl(private val userService: UserService, private val userAuthService: IUserAuthService,private val httpClient: HttpClient) : ActivityPubUserService { override suspend fun getPersonByName(name: String): Person { + // TODO: JOINで書き直し val userEntity = userService.findByName(name) val userAuthEntity = userAuthService.findByUserId(userEntity.id) val userUrl = "${Config.configData.url}/users/$name" @@ -37,4 +45,41 @@ class ActivityPubUserServiceImpl(private val userService: UserService, private v ) ) } + + override suspend fun fetchPerson(url: String): Person { + return try { + val userEntity = userService.findByUrl(url) + val userAuthEntity = userAuthService.findByUsername(userEntity.name) + return Person( + type = emptyList(), + name = userEntity.name, + id = url, + preferredUsername = userEntity.name, + summary = userEntity.description, + inbox = "$url/inbox", + outbox = "$url/outbox", + url = url, + icon = Image( + type = emptyList(), + name = "$url/icon.png", + mediaType = "image/png", + url = "$url/icon.png" + ), + publicKey = Key( + type = emptyList(), + name = "Public Key", + id = "$url#pubkey", + owner = url, + publicKeyPem = userAuthEntity.publicKey + ) + ) + + } catch (e:UserNotFoundException){ + val httpResponse = httpClient.get(url) { + accept(ContentType.Application.Activity) + } + Config.configData.objectMapper.readValue(httpResponse.bodyAsText()) + } + + } } diff --git a/src/main/kotlin/dev/usbharu/hideout/service/impl/UserAuthService.kt b/src/main/kotlin/dev/usbharu/hideout/service/impl/UserAuthService.kt index cf5b0a48..88247524 100644 --- a/src/main/kotlin/dev/usbharu/hideout/service/impl/UserAuthService.kt +++ b/src/main/kotlin/dev/usbharu/hideout/service/impl/UserAuthService.kt @@ -9,11 +9,7 @@ import dev.usbharu.hideout.repository.IUserAuthRepository import dev.usbharu.hideout.repository.IUserRepository import dev.usbharu.hideout.service.IUserAuthService import io.ktor.util.* -import java.security.KeyPair -import java.security.KeyPairGenerator -import java.security.MessageDigest -import java.security.PrivateKey -import java.security.PublicKey +import java.security.* import java.security.interfaces.RSAPrivateKey import java.security.interfaces.RSAPublicKey import java.util.* @@ -35,11 +31,15 @@ class UserAuthService( } override suspend fun registerAccount(username: String, hash: String) { + val url = "${Config.configData.url}/users/$username" val registerUser = User( name = username, domain = Config.configData.domain, screenName = username, - description = "" + description = "", + inbox = "$url/inbox", + outbox = "$url/outbox", + url = url ) val createdUser = userRepository.create(registerUser) @@ -83,8 +83,6 @@ class UserAuthService( } - - companion object { val sha256: MessageDigest = MessageDigest.getInstance("SHA-256") } diff --git a/src/main/kotlin/dev/usbharu/hideout/service/impl/UserService.kt b/src/main/kotlin/dev/usbharu/hideout/service/impl/UserService.kt index 4736338a..f090d38f 100644 --- a/src/main/kotlin/dev/usbharu/hideout/service/impl/UserService.kt +++ b/src/main/kotlin/dev/usbharu/hideout/service/impl/UserService.kt @@ -26,6 +26,10 @@ class UserService(private val userRepository: IUserRepository) { ?: throw UserNotFoundException("$name was not found.") } + suspend fun findByUrl(url: String): UserEntity { + return userRepository.findByUrl(url) ?: throw UserNotFoundException("$url was not found.") + } + suspend fun create(user: User): UserEntity { return userRepository.create(user) } diff --git a/src/main/kotlin/dev/usbharu/hideout/service/impl/WebFingerService.kt b/src/main/kotlin/dev/usbharu/hideout/service/impl/WebFingerService.kt index 8bf1c420..fe928bf6 100644 --- a/src/main/kotlin/dev/usbharu/hideout/service/impl/WebFingerService.kt +++ b/src/main/kotlin/dev/usbharu/hideout/service/impl/WebFingerService.kt @@ -66,8 +66,12 @@ class WebFingerService( userModel.preferredUsername ?: throw IllegalStateException(), domain, userName, - userModel.summary.orEmpty() + userModel.summary.orEmpty(), + "", + "", + "" ) + TODO() return userService.create(user) } } From fc051d20618a5ebc63c69a07d3b830ca397aca03 Mon Sep 17 00:00:00 2001 From: usbharu <64310155+usbharu@users.noreply.github.com> Date: Mon, 10 Apr 2023 00:40:58 +0900 Subject: [PATCH 24/40] =?UTF-8?q?test:=20=E3=83=86=E3=82=B9=E3=83=88?= =?UTF-8?q?=E3=82=92=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../dev/usbharu/hideout/plugins/ActivityPubKtTest.kt | 6 +++++- .../kotlin/dev/usbharu/hideout/plugins/KtorKeyMapTest.kt | 6 +++++- .../usbharu/hideout/routing/activitypub/UsersAPTest.kt | 8 ++++++++ 3 files changed, 18 insertions(+), 2 deletions(-) diff --git a/src/test/kotlin/dev/usbharu/hideout/plugins/ActivityPubKtTest.kt b/src/test/kotlin/dev/usbharu/hideout/plugins/ActivityPubKtTest.kt index 36cefbbd..8cadb063 100644 --- a/src/test/kotlin/dev/usbharu/hideout/plugins/ActivityPubKtTest.kt +++ b/src/test/kotlin/dev/usbharu/hideout/plugins/ActivityPubKtTest.kt @@ -32,7 +32,11 @@ class ActivityPubKtTest { } override suspend fun findByName(name: String): UserEntity? { - return UserEntity(1, "test", "localhost", "test", "") + return UserEntity(1, "test", "localhost", "test", "","","","") + } + + override suspend fun findByUrl(url: String): UserEntity? { + TODO("Not yet implemented") } override suspend fun update(userEntity: UserEntity) { diff --git a/src/test/kotlin/dev/usbharu/hideout/plugins/KtorKeyMapTest.kt b/src/test/kotlin/dev/usbharu/hideout/plugins/KtorKeyMapTest.kt index 3f56dd92..7e87a802 100644 --- a/src/test/kotlin/dev/usbharu/hideout/plugins/KtorKeyMapTest.kt +++ b/src/test/kotlin/dev/usbharu/hideout/plugins/KtorKeyMapTest.kt @@ -27,7 +27,11 @@ class KtorKeyMapTest { } override suspend fun findByName(name: String): UserEntity? { - return UserEntity(1, "test", "localhost", "test", "") + return UserEntity(1, "test", "localhost", "test", "","","","") + } + + override suspend fun findByUrl(url: String): UserEntity? { + TODO("Not yet implemented") } override suspend fun update(userEntity: UserEntity) { diff --git a/src/test/kotlin/dev/usbharu/hideout/routing/activitypub/UsersAPTest.kt b/src/test/kotlin/dev/usbharu/hideout/routing/activitypub/UsersAPTest.kt index 1aa2f7d8..fe8877d6 100644 --- a/src/test/kotlin/dev/usbharu/hideout/routing/activitypub/UsersAPTest.kt +++ b/src/test/kotlin/dev/usbharu/hideout/routing/activitypub/UsersAPTest.kt @@ -90,6 +90,10 @@ class UsersAPTest { TODO("Not yet implemented") } + override suspend fun findByUrl(url: String): UserEntity? { + TODO("Not yet implemented") + } + override suspend fun update(userEntity: UserEntity) { TODO("Not yet implemented") } @@ -122,6 +126,10 @@ class UsersAPTest { return person } + override suspend fun fetchPerson(url: String): Person { + TODO("Not yet implemented") + } + }) } client.get("/users/test") { From c7197168606402290fdf0f320682c57598277764 Mon Sep 17 00:00:00 2001 From: usbharu <64310155+usbharu@users.noreply.github.com> Date: Mon, 10 Apr 2023 00:47:38 +0900 Subject: [PATCH 25/40] =?UTF-8?q?fix:=20=E3=83=A6=E3=83=BC=E3=82=B6?= =?UTF-8?q?=E3=83=BC=E3=81=AE=E4=BD=9C=E6=88=90=E3=81=AB=E5=A4=B1=E6=95=97?= =?UTF-8?q?=E3=81=99=E3=82=8B=E3=81=AE=E3=82=92=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../kotlin/dev/usbharu/hideout/repository/UserRepository.kt | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/main/kotlin/dev/usbharu/hideout/repository/UserRepository.kt b/src/main/kotlin/dev/usbharu/hideout/repository/UserRepository.kt index 2591e026..6a262e2b 100644 --- a/src/main/kotlin/dev/usbharu/hideout/repository/UserRepository.kt +++ b/src/main/kotlin/dev/usbharu/hideout/repository/UserRepository.kt @@ -55,6 +55,9 @@ class UserRepository(private val database: Database) : IUserRepository { it[domain] = user.domain it[screenName] = user.screenName it[description] = user.description + it[inbox] = user.inbox + it[outbox] = user.outbox + it[url] = user.url }[Users.id].value, user) } } @@ -134,6 +137,9 @@ class UserRepository(private val database: Database) : IUserRepository { it[domain] = userEntity.domain it[screenName] = userEntity.screenName it[description] = userEntity.description + it[inbox] = userEntity.inbox + it[outbox] = userEntity.outbox + it[url] = userEntity.url } } } From c20b5ce06457b584f96206ebba1452e0bc2af406 Mon Sep 17 00:00:00 2001 From: usbharu <64310155+usbharu@users.noreply.github.com> Date: Mon, 10 Apr 2023 00:53:42 +0900 Subject: [PATCH 26/40] =?UTF-8?q?feat:=20users=E3=81=AEinbox=E3=81=AB?= =?UTF-8?q?=E3=82=82=E5=AF=BE=E5=BF=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../hideout/routing/activitypub/InboxRouting.kt | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/src/main/kotlin/dev/usbharu/hideout/routing/activitypub/InboxRouting.kt b/src/main/kotlin/dev/usbharu/hideout/routing/activitypub/InboxRouting.kt index a1e3b6a9..21e15b5f 100644 --- a/src/main/kotlin/dev/usbharu/hideout/routing/activitypub/InboxRouting.kt +++ b/src/main/kotlin/dev/usbharu/hideout/routing/activitypub/InboxRouting.kt @@ -41,7 +41,19 @@ fun Routing.inbox( call.respond(HttpStatusCode.NotImplemented) } post { - call.respond(HttpStatusCode.MethodNotAllowed) + if (httpSignatureVerifyService.verify(call.request.headers).not()) { + throw HttpSignatureVerifyException() + } + val json = call.receiveText() + val activityTypes = activityPubService.parseActivity(json) + val response = activityPubService.processActivity(json, activityTypes) + when (response) { + is ActivityPubObjectResponse -> call.respond(response.httpStatusCode, 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) + } } } From a8b7372d546df24c33314b392948d7801fe57119 Mon Sep 17 00:00:00 2001 From: usbharu <64310155+usbharu@users.noreply.github.com> Date: Mon, 10 Apr 2023 02:29:43 +0900 Subject: [PATCH 27/40] =?UTF-8?q?fix:=20=E3=83=AA=E3=83=95=E3=83=AC?= =?UTF-8?q?=E3=82=AF=E3=82=B7=E3=83=A7=E3=83=B3=E3=82=92=E5=88=A9=E7=94=A8?= =?UTF-8?q?=E3=81=9B=E3=81=9A=E3=82=B8=E3=83=A7=E3=83=96=E3=81=AE=E7=A8=AE?= =?UTF-8?q?=E9=A1=9E=E3=82=92=E5=88=86=E3=81=91=E3=82=8C=E3=82=89=E3=82=8C?= =?UTF-8?q?=E3=82=8B=E3=82=88=E3=81=86=E3=81=AB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../kotlin/dev/usbharu/hideout/Application.kt | 26 +++++++++++++++++-- .../{ReceiveFollowJob.kt => HideoutJob.kt} | 4 ++- .../routing/activitypub/InboxRouting.kt | 1 + .../activitypub/ActivityPubFollowService.kt | 3 +++ .../ActivityPubFollowServiceImpl.kt | 2 +- .../service/activitypub/ActivityPubService.kt | 8 ++++-- .../activitypub/ActivityPubServiceImpl.kt | 15 +++++++++++ .../HttpSignatureVerifyServiceImpl.kt | 3 ++- src/main/resources/application.conf | 2 +- .../routing/activitypub/UsersAPTest.kt | 10 +++++++ 10 files changed, 66 insertions(+), 8 deletions(-) rename src/main/kotlin/dev/usbharu/hideout/domain/model/job/{ReceiveFollowJob.kt => HideoutJob.kt} (61%) diff --git a/src/main/kotlin/dev/usbharu/hideout/Application.kt b/src/main/kotlin/dev/usbharu/hideout/Application.kt index 9321677c..eed69615 100644 --- a/src/main/kotlin/dev/usbharu/hideout/Application.kt +++ b/src/main/kotlin/dev/usbharu/hideout/Application.kt @@ -5,6 +5,7 @@ import com.fasterxml.jackson.databind.DeserializationFeature import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper import dev.usbharu.hideout.config.Config import dev.usbharu.hideout.config.ConfigData +import dev.usbharu.hideout.domain.model.job.ReceiveFollowJob import dev.usbharu.hideout.plugins.* import dev.usbharu.hideout.repository.IUserAuthRepository import dev.usbharu.hideout.repository.IUserRepository @@ -19,10 +20,17 @@ import dev.usbharu.hideout.service.job.JobQueueParentService import dev.usbharu.hideout.service.job.KJobJobQueueParentService import dev.usbharu.hideout.service.signature.HttpSignatureVerifyService import dev.usbharu.hideout.service.signature.HttpSignatureVerifyServiceImpl +import dev.usbharu.kjob.exposed.ExposedKJob import io.ktor.client.* import io.ktor.client.engine.cio.* import io.ktor.client.plugins.logging.* import io.ktor.server.application.* +import kjob.core.Job +import kjob.core.KJob +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 org.koin.ktor.ext.inject @@ -34,7 +42,7 @@ val Application.property: Application.(propertyName: String) -> String } @Suppress("unused") // application.conf references the main function. This annotation prevents the IDE from marking it as unused. -fun Application.module() { +fun Application.parent() { Config.configData = ConfigData( url = property("hideout.url"), @@ -73,7 +81,7 @@ fun Application.module() { single { ActivityPubFollowServiceImpl(get(), get(), get()) } single { ActivityPubServiceImpl(get()) } single { UserService(get()) } - single { ActivityPubUserServiceImpl(get(), get(),get()) } + single { ActivityPubUserServiceImpl(get(), get(), get()) } } @@ -90,3 +98,17 @@ fun Application.module() { inject().value ) } +@Suppress("unused") +fun Application.worker() { + val kJob = kjob(ExposedKJob) { + connectionDatabase = inject().value + }.start() + + val activityPubService = inject().value + + kJob.register(ReceiveFollowJob){ + execute { + activityPubService.processActivity(this,it) + } + } +} diff --git a/src/main/kotlin/dev/usbharu/hideout/domain/model/job/ReceiveFollowJob.kt b/src/main/kotlin/dev/usbharu/hideout/domain/model/job/HideoutJob.kt similarity index 61% rename from src/main/kotlin/dev/usbharu/hideout/domain/model/job/ReceiveFollowJob.kt rename to src/main/kotlin/dev/usbharu/hideout/domain/model/job/HideoutJob.kt index 6a200ec9..c499807c 100644 --- a/src/main/kotlin/dev/usbharu/hideout/domain/model/job/ReceiveFollowJob.kt +++ b/src/main/kotlin/dev/usbharu/hideout/domain/model/job/HideoutJob.kt @@ -2,7 +2,9 @@ package dev.usbharu.hideout.domain.model.job import kjob.core.Job -object ReceiveFollowJob : Job("ReceiveFollowJob"){ +sealed class HideoutJob(name: String = "") : Job(name) + +object ReceiveFollowJob : HideoutJob("ReceiveFollowJob"){ val actor = string("actor") val follow = string("follow") val targetActor = string("targetActor") diff --git a/src/main/kotlin/dev/usbharu/hideout/routing/activitypub/InboxRouting.kt b/src/main/kotlin/dev/usbharu/hideout/routing/activitypub/InboxRouting.kt index 21e15b5f..ea2193ea 100644 --- a/src/main/kotlin/dev/usbharu/hideout/routing/activitypub/InboxRouting.kt +++ b/src/main/kotlin/dev/usbharu/hideout/routing/activitypub/InboxRouting.kt @@ -46,6 +46,7 @@ fun Routing.inbox( } val json = call.receiveText() val activityTypes = activityPubService.parseActivity(json) + println(activityTypes) val response = activityPubService.processActivity(json, activityTypes) when (response) { is ActivityPubObjectResponse -> call.respond(response.httpStatusCode, Config.configData.objectMapper.writeValueAsString(response.message.apply { context = diff --git a/src/main/kotlin/dev/usbharu/hideout/service/activitypub/ActivityPubFollowService.kt b/src/main/kotlin/dev/usbharu/hideout/service/activitypub/ActivityPubFollowService.kt index af86ffcd..4fed9bb6 100644 --- a/src/main/kotlin/dev/usbharu/hideout/service/activitypub/ActivityPubFollowService.kt +++ b/src/main/kotlin/dev/usbharu/hideout/service/activitypub/ActivityPubFollowService.kt @@ -2,7 +2,10 @@ package dev.usbharu.hideout.service.activitypub import dev.usbharu.hideout.ap.Follow import dev.usbharu.hideout.domain.model.ActivityPubResponse +import dev.usbharu.hideout.domain.model.job.ReceiveFollowJob +import kjob.core.job.JobProps interface ActivityPubFollowService { suspend fun receiveFollow(follow:Follow):ActivityPubResponse + suspend fun receiveFollowJob(props: JobProps) } diff --git a/src/main/kotlin/dev/usbharu/hideout/service/activitypub/ActivityPubFollowServiceImpl.kt b/src/main/kotlin/dev/usbharu/hideout/service/activitypub/ActivityPubFollowServiceImpl.kt index 3f311e5a..f96c3cb8 100644 --- a/src/main/kotlin/dev/usbharu/hideout/service/activitypub/ActivityPubFollowServiceImpl.kt +++ b/src/main/kotlin/dev/usbharu/hideout/service/activitypub/ActivityPubFollowServiceImpl.kt @@ -27,7 +27,7 @@ class ActivityPubFollowServiceImpl( return ActivityPubStringResponse(HttpStatusCode.OK, "{}", ContentType.Application.Json) } - suspend fun receiveFollowJob(props: JobProps) { + override suspend fun receiveFollowJob(props: JobProps) { val actor = props[ReceiveFollowJob.actor] val person = activityPubUserService.fetchPerson(actor) val follow = Config.configData.objectMapper.readValue(props[ReceiveFollowJob.follow]) diff --git a/src/main/kotlin/dev/usbharu/hideout/service/activitypub/ActivityPubService.kt b/src/main/kotlin/dev/usbharu/hideout/service/activitypub/ActivityPubService.kt index 5db5a1aa..939d3d3b 100644 --- a/src/main/kotlin/dev/usbharu/hideout/service/activitypub/ActivityPubService.kt +++ b/src/main/kotlin/dev/usbharu/hideout/service/activitypub/ActivityPubService.kt @@ -1,11 +1,15 @@ package dev.usbharu.hideout.service.activitypub import dev.usbharu.hideout.domain.model.ActivityPubResponse +import dev.usbharu.hideout.domain.model.job.HideoutJob +import kjob.core.dsl.JobContextWithProps interface ActivityPubService { - fun parseActivity(json:String): ActivityType + fun parseActivity(json: String): ActivityType - suspend fun processActivity(json:String, type: ActivityType): ActivityPubResponse? + suspend fun processActivity(json: String, type: ActivityType): ActivityPubResponse? + + suspend fun processActivity(job: JobContextWithProps,hideoutJob: HideoutJob) } enum class ActivityType { diff --git a/src/main/kotlin/dev/usbharu/hideout/service/activitypub/ActivityPubServiceImpl.kt b/src/main/kotlin/dev/usbharu/hideout/service/activitypub/ActivityPubServiceImpl.kt index 10ca0e13..8dfe8ff1 100644 --- a/src/main/kotlin/dev/usbharu/hideout/service/activitypub/ActivityPubServiceImpl.kt +++ b/src/main/kotlin/dev/usbharu/hideout/service/activitypub/ActivityPubServiceImpl.kt @@ -4,7 +4,14 @@ import com.fasterxml.jackson.databind.JsonNode import dev.usbharu.hideout.ap.Follow import dev.usbharu.hideout.config.Config import dev.usbharu.hideout.domain.model.ActivityPubResponse +import dev.usbharu.hideout.domain.model.job.HideoutJob +import dev.usbharu.hideout.domain.model.job.ReceiveFollowJob import dev.usbharu.hideout.exception.JsonParseException +import kjob.core.Job +import kjob.core.dsl.JobContextWithProps +import kjob.core.job.JobProps +import kotlin.reflect.full.createInstance +import kotlin.reflect.full.primaryConstructor class ActivityPubServiceImpl(private val activityPubFollowService: ActivityPubFollowService) : ActivityPubService { override fun parseActivity(json: String): ActivityType { @@ -60,4 +67,12 @@ class ActivityPubServiceImpl(private val activityPubFollowService: ActivityPubFo ActivityType.Other -> TODO() } } + + override suspend fun processActivity(job: JobContextWithProps, hideoutJob: HideoutJob) { + when (hideoutJob) { + ReceiveFollowJob -> activityPubFollowService.receiveFollowJob(job.props as JobProps) + } + } + + } diff --git a/src/main/kotlin/dev/usbharu/hideout/service/signature/HttpSignatureVerifyServiceImpl.kt b/src/main/kotlin/dev/usbharu/hideout/service/signature/HttpSignatureVerifyServiceImpl.kt index 5f18f09e..74525981 100644 --- a/src/main/kotlin/dev/usbharu/hideout/service/signature/HttpSignatureVerifyServiceImpl.kt +++ b/src/main/kotlin/dev/usbharu/hideout/service/signature/HttpSignatureVerifyServiceImpl.kt @@ -9,7 +9,8 @@ import tech.barbero.http.message.signing.SignatureHeaderVerifier class HttpSignatureVerifyServiceImpl(private val userAuthService: IUserAuthService) : HttpSignatureVerifyService { override fun verify(headers: Headers): Boolean { val build = SignatureHeaderVerifier.builder().keyMap(KtorKeyMap(userAuthService)).build() - return build.verify(object : HttpMessage { + return true; + build.verify(object : HttpMessage { override fun headerValues(name: String?): MutableList { return name?.let { headers.getAll(it) }?.toMutableList() ?: mutableListOf() } diff --git a/src/main/resources/application.conf b/src/main/resources/application.conf index dd12df2f..beb8fa3b 100644 --- a/src/main/resources/application.conf +++ b/src/main/resources/application.conf @@ -6,7 +6,7 @@ ktor { watch = [classes, resources] } application { - modules = [dev.usbharu.hideout.ApplicationKt.module] + modules = [dev.usbharu.hideout.ApplicationKt.parent,dev.usbharu.hideout.ApplicationKt.worker] } } diff --git a/src/test/kotlin/dev/usbharu/hideout/routing/activitypub/UsersAPTest.kt b/src/test/kotlin/dev/usbharu/hideout/routing/activitypub/UsersAPTest.kt index fe8877d6..f0c22e09 100644 --- a/src/test/kotlin/dev/usbharu/hideout/routing/activitypub/UsersAPTest.kt +++ b/src/test/kotlin/dev/usbharu/hideout/routing/activitypub/UsersAPTest.kt @@ -13,6 +13,7 @@ import dev.usbharu.hideout.config.Config import dev.usbharu.hideout.domain.model.ActivityPubResponse import dev.usbharu.hideout.domain.model.User import dev.usbharu.hideout.domain.model.UserEntity +import dev.usbharu.hideout.domain.model.job.HideoutJob import dev.usbharu.hideout.plugins.configureRouting import dev.usbharu.hideout.plugins.configureSerialization import dev.usbharu.hideout.repository.IUserRepository @@ -28,6 +29,7 @@ import io.ktor.client.statement.* import io.ktor.http.* import io.ktor.server.config.* import io.ktor.server.testing.* +import kjob.core.dsl.JobContextWithProps import org.junit.jupiter.api.Test import kotlin.test.assertEquals @@ -77,6 +79,14 @@ class UsersAPTest { override suspend fun processActivity(json: String, type: ActivityType): ActivityPubResponse? { TODO("Not yet implemented") } + + override suspend fun processActivity( + job: JobContextWithProps, + hideoutJob: HideoutJob + ) { + TODO("Not yet implemented") + } + }, UserService(object : IUserRepository { override suspend fun create(user: User): UserEntity { TODO("Not yet implemented") From 6607396e2e92f0fb465c37e107f2868ecf2eb6da Mon Sep 17 00:00:00 2001 From: usbharu <64310155+usbharu@users.noreply.github.com> Date: Mon, 10 Apr 2023 02:39:53 +0900 Subject: [PATCH 28/40] =?UTF-8?q?fix:=20Ktor=E3=81=AEHTTP=20Signature=20Pl?= =?UTF-8?q?ugin=E3=82=92=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/kotlin/dev/usbharu/hideout/Application.kt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/main/kotlin/dev/usbharu/hideout/Application.kt b/src/main/kotlin/dev/usbharu/hideout/Application.kt index eed69615..66b0b169 100644 --- a/src/main/kotlin/dev/usbharu/hideout/Application.kt +++ b/src/main/kotlin/dev/usbharu/hideout/Application.kt @@ -76,6 +76,9 @@ fun Application.parent() { logger = Logger.DEFAULT level = LogLevel.ALL } + install(httpSignaturePlugin){ + keyMap = KtorKeyMap(get()) + } } } single { ActivityPubFollowServiceImpl(get(), get(), get()) } From 68cc469a439f13e2d66e8d499839c5e794be361c Mon Sep 17 00:00:00 2001 From: usbharu <64310155+usbharu@users.noreply.github.com> Date: Mon, 10 Apr 2023 02:49:44 +0900 Subject: [PATCH 29/40] =?UTF-8?q?fix:=20Accept=E3=82=92=E9=80=81=E3=82=8B?= =?UTF-8?q?=E3=81=A8=E3=81=93=E3=82=8D=E3=81=A7Follow=E3=82=92=E9=80=81?= =?UTF-8?q?=E3=81=A3=E3=81=A6=E3=81=84=E3=81=9F=E3=81=AE=E3=81=A7=E4=BF=AE?= =?UTF-8?q?=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../service/activitypub/ActivityPubFollowServiceImpl.kt | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/main/kotlin/dev/usbharu/hideout/service/activitypub/ActivityPubFollowServiceImpl.kt b/src/main/kotlin/dev/usbharu/hideout/service/activitypub/ActivityPubFollowServiceImpl.kt index f96c3cb8..925a735c 100644 --- a/src/main/kotlin/dev/usbharu/hideout/service/activitypub/ActivityPubFollowServiceImpl.kt +++ b/src/main/kotlin/dev/usbharu/hideout/service/activitypub/ActivityPubFollowServiceImpl.kt @@ -1,6 +1,7 @@ package dev.usbharu.hideout.service.activitypub import com.fasterxml.jackson.module.kotlin.readValue +import dev.usbharu.hideout.ap.Accept import dev.usbharu.hideout.ap.Follow import dev.usbharu.hideout.config.Config import dev.usbharu.hideout.domain.model.ActivityPubResponse @@ -34,7 +35,11 @@ class ActivityPubFollowServiceImpl( httpClient.postAp( urlString = person.inbox ?: throw IllegalArgumentException("inbox is not found"), username = "${props[ReceiveFollowJob.targetActor]}#pubkey", - jsonLd = follow + jsonLd = Accept( + name = "Follow", + `object` = follow, + actor = props[ReceiveFollowJob.targetActor] + ) ) } } From ee46d0522680055ffa1263b1bf10623aa2f7ac1b Mon Sep 17 00:00:00 2001 From: usbharu <64310155+usbharu@users.noreply.github.com> Date: Mon, 10 Apr 2023 11:37:08 +0900 Subject: [PATCH 30/40] =?UTF-8?q?test:=20=E3=83=A2=E3=83=83=E3=82=AF?= =?UTF-8?q?=E3=82=92=E5=B0=8E=E5=85=A5=E3=81=97=E3=83=86=E3=82=B9=E3=83=88?= =?UTF-8?q?=E5=90=8D=E3=82=92=E8=A9=B3=E7=B4=B0=E3=81=AB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle.kts | 4 +- .../routing/activitypub/UsersAPTest.kt | 109 ++++-------------- 2 files changed, 23 insertions(+), 90 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index 9afedfb0..2e5f0742 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -69,10 +69,12 @@ dependencies { implementation("io.ktor:ktor-client-cio:$ktor_version") implementation("io.ktor:ktor-client-content-negotiation:$ktor_version") testImplementation("io.ktor:ktor-client-mock:$ktor_version") - implementation("tech.barbero.http-messages-signing:http-messages-signing-core:1.0.0") testImplementation("org.junit.jupiter:junit-jupiter:5.8.1") + testImplementation("org.mockito.kotlin:mockito-kotlin:4.1.0") + testImplementation("org.mockito:mockito-inline:5.2.0") + implementation("org.drewcarlson:kjob-core:0.6.0") testImplementation("io.ktor:ktor-server-test-host-jvm:2.2.4") diff --git a/src/test/kotlin/dev/usbharu/hideout/routing/activitypub/UsersAPTest.kt b/src/test/kotlin/dev/usbharu/hideout/routing/activitypub/UsersAPTest.kt index f0c22e09..32b6cb17 100644 --- a/src/test/kotlin/dev/usbharu/hideout/routing/activitypub/UsersAPTest.kt +++ b/src/test/kotlin/dev/usbharu/hideout/routing/activitypub/UsersAPTest.kt @@ -9,35 +9,29 @@ import com.fasterxml.jackson.module.kotlin.readValue import dev.usbharu.hideout.ap.Image import dev.usbharu.hideout.ap.Key import dev.usbharu.hideout.ap.Person -import dev.usbharu.hideout.config.Config -import dev.usbharu.hideout.domain.model.ActivityPubResponse -import dev.usbharu.hideout.domain.model.User -import dev.usbharu.hideout.domain.model.UserEntity -import dev.usbharu.hideout.domain.model.job.HideoutJob import dev.usbharu.hideout.plugins.configureRouting import dev.usbharu.hideout.plugins.configureSerialization -import dev.usbharu.hideout.repository.IUserRepository import dev.usbharu.hideout.service.activitypub.ActivityPubService import dev.usbharu.hideout.service.activitypub.ActivityPubUserService -import dev.usbharu.hideout.service.activitypub.ActivityType import dev.usbharu.hideout.service.impl.UserService import dev.usbharu.hideout.service.signature.HttpSignatureVerifyService import dev.usbharu.hideout.util.HttpUtil.Activity -import io.ktor.client.call.* import io.ktor.client.request.* import io.ktor.client.statement.* import io.ktor.http.* import io.ktor.server.config.* import io.ktor.server.testing.* -import kjob.core.dsl.JobContextWithProps import org.junit.jupiter.api.Test +import org.mockito.ArgumentMatchers.anyString +import org.mockito.kotlin.doReturn +import org.mockito.kotlin.mock import kotlin.test.assertEquals class UsersAPTest { - @Test - fun testHandleUsersName() = testApplication { + @Test() + fun `ユーザのURLにAcceptヘッダーをActivityにしてアクセスしたときPersonが返ってくるか`() = testApplication { environment { config = ApplicationConfig("empty.conf") } @@ -65,82 +59,18 @@ class UsersAPTest { ) ) person.context = listOf("https://www.w3.org/ns/activitystreams") + + val httpSignatureVerifyService = mock {} + val activityPubService = mock {} + val userService = mock {} + + val activityPubUserService = mock { + onBlocking { getPersonByName(anyString()) } doReturn person + } + application { configureSerialization() - configureRouting(object : HttpSignatureVerifyService { - override fun verify(headers: Headers): Boolean { - return true - } - }, object : ActivityPubService { - override fun parseActivity(json: String): ActivityType { - TODO("Not yet implemented") - } - - override suspend fun processActivity(json: String, type: ActivityType): ActivityPubResponse? { - TODO("Not yet implemented") - } - - override suspend fun processActivity( - job: JobContextWithProps, - hideoutJob: HideoutJob - ) { - TODO("Not yet implemented") - } - - }, UserService(object : IUserRepository { - override suspend fun create(user: User): UserEntity { - TODO("Not yet implemented") - } - - override suspend fun findById(id: Long): UserEntity? { - TODO("Not yet implemented") - } - - override suspend fun findByName(name: String): UserEntity? { - TODO("Not yet implemented") - } - - override suspend fun findByUrl(url: String): UserEntity? { - TODO("Not yet implemented") - } - - override suspend fun update(userEntity: UserEntity) { - TODO("Not yet implemented") - } - - override suspend fun delete(id: Long) { - TODO("Not yet implemented") - } - - override suspend fun findAll(): List { - TODO("Not yet implemented") - } - - override suspend fun findAllByLimitAndByOffset(limit: Int, offset: Long): List { - TODO("Not yet implemented") - } - - override suspend fun createFollower(id: Long, follower: Long) { - TODO("Not yet implemented") - } - - override suspend fun deleteFollower(id: Long, follower: Long) { - TODO("Not yet implemented") - } - - override suspend fun findFollowersById(id: Long): List { - TODO("Not yet implemented") - } - }), object : ActivityPubUserService { - override suspend fun getPersonByName(name: String): Person { - return person - } - - override suspend fun fetchPerson(url: String): Person { - TODO("Not yet implemented") - } - - }) + configureRouting(httpSignatureVerifyService, activityPubService, userService, activityPubUserService) } client.get("/users/test") { accept(ContentType.Application.Activity) @@ -148,12 +78,13 @@ class UsersAPTest { val objectMapper = jacksonObjectMapper().enable(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY) .setSerializationInclusion(JsonInclude.Include.NON_EMPTY) .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false) - objectMapper.configOverride(List::class.java).setSetterInfo(JsonSetter.Value.forValueNulls( - Nulls.AS_EMPTY)) + objectMapper.configOverride(List::class.java).setSetterInfo( + JsonSetter.Value.forValueNulls( + Nulls.AS_EMPTY + ) + ) val actual = it.bodyAsText() val readValue = objectMapper.readValue(actual) - println(objectMapper.writeValueAsString(person)) - println(objectMapper.writeValueAsString(readValue)) assertEquals(person, readValue) } } From 9558a53df157bb9ea88f39cc98c304beb7dd190f Mon Sep 17 00:00:00 2001 From: usbharu <64310155+usbharu@users.noreply.github.com> Date: Mon, 10 Apr 2023 13:41:07 +0900 Subject: [PATCH 31/40] =?UTF-8?q?refactor:=20=E4=B8=8D=E8=A6=81=E3=81=AA?= =?UTF-8?q?=E3=82=AF=E3=83=A9=E3=82=B9=E3=80=81=E3=82=A4=E3=83=B3=E3=83=9D?= =?UTF-8?q?=E3=83=BC=E3=83=88=E7=AD=89=E3=82=92=E5=89=8A=E9=99=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../routing/activitypub/UserRouting.kt | 2 - .../hideout/routing/userActivityPubRouting.kt | 54 ------------------- .../hideout/routing/UserRoutingKtTest.kt | 12 ----- .../routing/activitypub/InboxRoutingKtTest.kt | 4 ++ 4 files changed, 4 insertions(+), 68 deletions(-) delete mode 100644 src/main/kotlin/dev/usbharu/hideout/routing/userActivityPubRouting.kt delete mode 100644 src/test/kotlin/dev/usbharu/hideout/routing/UserRoutingKtTest.kt create mode 100644 src/test/kotlin/dev/usbharu/hideout/routing/activitypub/InboxRoutingKtTest.kt diff --git a/src/main/kotlin/dev/usbharu/hideout/routing/activitypub/UserRouting.kt b/src/main/kotlin/dev/usbharu/hideout/routing/activitypub/UserRouting.kt index 97280087..36b4d80d 100644 --- a/src/main/kotlin/dev/usbharu/hideout/routing/activitypub/UserRouting.kt +++ b/src/main/kotlin/dev/usbharu/hideout/routing/activitypub/UserRouting.kt @@ -1,6 +1,5 @@ package dev.usbharu.hideout.routing.activitypub -import dev.usbharu.hideout.config.Config import dev.usbharu.hideout.exception.ParameterNotExistException import dev.usbharu.hideout.plugins.respondAp import dev.usbharu.hideout.service.activitypub.ActivityPubUserService @@ -9,7 +8,6 @@ import dev.usbharu.hideout.util.HttpUtil.JsonLd import io.ktor.http.* import io.ktor.server.application.* import io.ktor.server.request.* -import io.ktor.server.response.* import io.ktor.server.routing.* fun Routing.usersAP(activityPubUserService: ActivityPubUserService) { diff --git a/src/main/kotlin/dev/usbharu/hideout/routing/userActivityPubRouting.kt b/src/main/kotlin/dev/usbharu/hideout/routing/userActivityPubRouting.kt deleted file mode 100644 index 73d54fa8..00000000 --- a/src/main/kotlin/dev/usbharu/hideout/routing/userActivityPubRouting.kt +++ /dev/null @@ -1,54 +0,0 @@ -package dev.usbharu.hideout.routing - -import com.fasterxml.jackson.module.kotlin.readValue -import dev.usbharu.hideout.ap.Follow -import dev.usbharu.hideout.config.Config -import dev.usbharu.hideout.service.impl.ActivityPubService -import dev.usbharu.hideout.service.impl.ActivityPubUserService -import dev.usbharu.hideout.util.HttpUtil -import io.ktor.http.* -import io.ktor.server.application.* -import io.ktor.server.request.* -import io.ktor.server.response.* -import io.ktor.server.routing.* - -fun Application.userActivityPubRouting(activityPubService: ActivityPubService, activityPubUserService: ActivityPubUserService) { - routing { - route("/users/{name}") { - route("/inbox") { - get { - call.respond(HttpStatusCode.MethodNotAllowed) - } - post { - if (!HttpUtil.isContentTypeOfActivityPub(call.request.contentType())) { - return@post call.respond(HttpStatusCode.BadRequest) - } - val bodyText = call.receiveText() - println(bodyText) - when (activityPubService.switchApType(bodyText)) { - ActivityPubService.ActivityType.Follow -> { - val readValue = Config.configData.objectMapper.readValue(bodyText) - activityPubUserService.receiveFollow(readValue) - return@post call.respond(HttpStatusCode.Accepted) - } - - ActivityPubService.ActivityType.Undo -> { - return@post call.respond(HttpStatusCode.Accepted) - } - } - - } - } - route("/outbox") { - get { - call.respond(HttpStatusCode.MethodNotAllowed) - - } - post { - - call.respond(HttpStatusCode.MethodNotAllowed) - } - } - } - } -} diff --git a/src/test/kotlin/dev/usbharu/hideout/routing/UserRoutingKtTest.kt b/src/test/kotlin/dev/usbharu/hideout/routing/UserRoutingKtTest.kt deleted file mode 100644 index 6027b7e2..00000000 --- a/src/test/kotlin/dev/usbharu/hideout/routing/UserRoutingKtTest.kt +++ /dev/null @@ -1,12 +0,0 @@ -package dev.usbharu.hideout.routing - -import org.junit.jupiter.api.Test - -class UserRoutingKtTest { - @Test - fun userIconTest() { - println(String.Companion::class.java.classLoader) - println(String::class.java.classLoader) - println(String.javaClass.classLoader.getResourceAsStream("icon.png")?.readAllBytes()) - } -} diff --git a/src/test/kotlin/dev/usbharu/hideout/routing/activitypub/InboxRoutingKtTest.kt b/src/test/kotlin/dev/usbharu/hideout/routing/activitypub/InboxRoutingKtTest.kt new file mode 100644 index 00000000..3ad053bf --- /dev/null +++ b/src/test/kotlin/dev/usbharu/hideout/routing/activitypub/InboxRoutingKtTest.kt @@ -0,0 +1,4 @@ +package dev.usbharu.hideout.routing.activitypub +class InboxRoutingKtTest { + +} From e46dbb7b9ba7ff75ba9b79fd77be9d6221718ce4 Mon Sep 17 00:00:00 2001 From: usbharu <64310155+usbharu@users.noreply.github.com> Date: Mon, 10 Apr 2023 14:20:41 +0900 Subject: [PATCH 32/40] =?UTF-8?q?test:=20sharedInbox=E3=81=AE=E3=83=86?= =?UTF-8?q?=E3=82=B9=E3=83=88=E3=82=92=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../routing/activitypub/InboxRoutingKtTest.kt | 31 ++++++++++++++++++- .../routing/activitypub/UsersAPTest.kt | 2 +- 2 files changed, 31 insertions(+), 2 deletions(-) diff --git a/src/test/kotlin/dev/usbharu/hideout/routing/activitypub/InboxRoutingKtTest.kt b/src/test/kotlin/dev/usbharu/hideout/routing/activitypub/InboxRoutingKtTest.kt index 3ad053bf..05f8596f 100644 --- a/src/test/kotlin/dev/usbharu/hideout/routing/activitypub/InboxRoutingKtTest.kt +++ b/src/test/kotlin/dev/usbharu/hideout/routing/activitypub/InboxRoutingKtTest.kt @@ -1,4 +1,33 @@ package dev.usbharu.hideout.routing.activitypub + +import com.fasterxml.jackson.annotation.JsonInclude +import com.fasterxml.jackson.annotation.JsonSetter +import com.fasterxml.jackson.annotation.Nulls +import com.fasterxml.jackson.databind.DeserializationFeature +import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper +import dev.usbharu.hideout.plugins.configureRouting +import dev.usbharu.hideout.plugins.configureSerialization +import io.ktor.client.request.* +import io.ktor.client.statement.* +import io.ktor.http.* +import io.ktor.server.config.* +import io.ktor.server.testing.* +import org.junit.jupiter.api.Assertions +import org.junit.jupiter.api.Test +import org.mockito.kotlin.mock + class InboxRoutingKtTest { - + @Test + fun `sharedInboxにGETしたら501が帰ってくる`() = testApplication { + environment { + config = ApplicationConfig("empty.conf") + } + application { + configureSerialization() + configureRouting(mock(), mock(), mock(), mock()) + } + client.get("/inbox").let { + Assertions.assertEquals(HttpStatusCode.MethodNotAllowed, it.status) + } + } } diff --git a/src/test/kotlin/dev/usbharu/hideout/routing/activitypub/UsersAPTest.kt b/src/test/kotlin/dev/usbharu/hideout/routing/activitypub/UsersAPTest.kt index 32b6cb17..e09f504b 100644 --- a/src/test/kotlin/dev/usbharu/hideout/routing/activitypub/UsersAPTest.kt +++ b/src/test/kotlin/dev/usbharu/hideout/routing/activitypub/UsersAPTest.kt @@ -31,7 +31,7 @@ import kotlin.test.assertEquals class UsersAPTest { @Test() - fun `ユーザのURLにAcceptヘッダーをActivityにしてアクセスしたときPersonが返ってくるか`() = testApplication { + fun `ユーザのURLにAcceptヘッダーをActivityにしてアクセスしたときPersonが返ってくる`() = testApplication { environment { config = ApplicationConfig("empty.conf") } From e4dc17f3b4bb42220bb1ba7844d37f7f9e2e1ebe Mon Sep 17 00:00:00 2001 From: usbharu <64310155+usbharu@users.noreply.github.com> Date: Mon, 10 Apr 2023 14:48:33 +0900 Subject: [PATCH 33/40] =?UTF-8?q?test:=20sharedInbox=E3=81=ABPOST=E3=81=97?= =?UTF-8?q?=E3=81=9F=E3=81=A8=E3=81=8D=E3=81=AE=E3=83=86=E3=82=B9=E3=83=88?= =?UTF-8?q?=E3=82=92=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../routing/activitypub/InboxRoutingKtTest.kt | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/src/test/kotlin/dev/usbharu/hideout/routing/activitypub/InboxRoutingKtTest.kt b/src/test/kotlin/dev/usbharu/hideout/routing/activitypub/InboxRoutingKtTest.kt index 05f8596f..106eae24 100644 --- a/src/test/kotlin/dev/usbharu/hideout/routing/activitypub/InboxRoutingKtTest.kt +++ b/src/test/kotlin/dev/usbharu/hideout/routing/activitypub/InboxRoutingKtTest.kt @@ -7,13 +7,21 @@ import com.fasterxml.jackson.databind.DeserializationFeature import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper import dev.usbharu.hideout.plugins.configureRouting import dev.usbharu.hideout.plugins.configureSerialization +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.UserService +import dev.usbharu.hideout.service.signature.HttpSignatureVerifyService import io.ktor.client.request.* import io.ktor.client.statement.* import io.ktor.http.* import io.ktor.server.config.* import io.ktor.server.testing.* +import junit.framework.TestCase.assertEquals import org.junit.jupiter.api.Assertions import org.junit.jupiter.api.Test +import org.mockito.kotlin.any +import org.mockito.kotlin.doReturn import org.mockito.kotlin.mock class InboxRoutingKtTest { @@ -30,4 +38,25 @@ class InboxRoutingKtTest { Assertions.assertEquals(HttpStatusCode.MethodNotAllowed, it.status) } } + + @Test + fun `sharedInboxに空のリクエストボディでPOSTしたら400が帰ってくる`() = testApplication { + environment { + config = ApplicationConfig("empty.conf") + } + val httpSignatureVerifyService = mock{ + on { verify(any()) } doReturn true + } + val activityPubService = mock() + val userService = mock() + val activityPubUserService = mock() + application { + configureStatusPages() + configureSerialization() + configureRouting(httpSignatureVerifyService, activityPubService, userService, activityPubUserService) + } + client.post("/inbox").let { + Assertions.assertEquals(HttpStatusCode.BadRequest, it.status) + } + } } From 31e4218ef87470c654b482142cb3e4c3b3640ea7 Mon Sep 17 00:00:00 2001 From: usbharu <64310155+usbharu@users.noreply.github.com> Date: Mon, 10 Apr 2023 15:06:11 +0900 Subject: [PATCH 34/40] =?UTF-8?q?test:=20=E3=83=A6=E3=83=BC=E3=82=B6?= =?UTF-8?q?=E3=83=BC=E3=81=AEinbox=E3=81=AEGET=E3=81=AE=E3=83=86=E3=82=B9?= =?UTF-8?q?=E3=83=88=E3=82=92=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../routing/activitypub/InboxRouting.kt | 4 ++- .../activitypub/ActivityPubServiceImpl.kt | 4 +++ .../routing/activitypub/InboxRoutingKtTest.kt | 29 +++++++++++++------ src/test/resources/empty.conf | 1 - 4 files changed, 27 insertions(+), 11 deletions(-) diff --git a/src/main/kotlin/dev/usbharu/hideout/routing/activitypub/InboxRouting.kt b/src/main/kotlin/dev/usbharu/hideout/routing/activitypub/InboxRouting.kt index ea2193ea..92753f36 100644 --- a/src/main/kotlin/dev/usbharu/hideout/routing/activitypub/InboxRouting.kt +++ b/src/main/kotlin/dev/usbharu/hideout/routing/activitypub/InboxRouting.kt @@ -25,7 +25,9 @@ fun Routing.inbox( throw HttpSignatureVerifyException() } val json = call.receiveText() + call.application.log.trace("Received: $json") val activityTypes = activityPubService.parseActivity(json) + call.application.log.debug("ActivityTypes: ${activityTypes.name}") val response = activityPubService.processActivity(json, activityTypes) when (response) { is ActivityPubObjectResponse -> call.respond(response.httpStatusCode, Config.configData.objectMapper.writeValueAsString(response.message.apply { context = @@ -38,7 +40,7 @@ fun Routing.inbox( } route("/users/{name}/inbox"){ get { - call.respond(HttpStatusCode.NotImplemented) + call.respond(HttpStatusCode.MethodNotAllowed) } post { if (httpSignatureVerifyService.verify(call.request.headers).not()) { diff --git a/src/main/kotlin/dev/usbharu/hideout/service/activitypub/ActivityPubServiceImpl.kt b/src/main/kotlin/dev/usbharu/hideout/service/activitypub/ActivityPubServiceImpl.kt index 8dfe8ff1..06224a67 100644 --- a/src/main/kotlin/dev/usbharu/hideout/service/activitypub/ActivityPubServiceImpl.kt +++ b/src/main/kotlin/dev/usbharu/hideout/service/activitypub/ActivityPubServiceImpl.kt @@ -10,12 +10,16 @@ import dev.usbharu.hideout.exception.JsonParseException import kjob.core.Job import kjob.core.dsl.JobContextWithProps import kjob.core.job.JobProps +import org.slf4j.LoggerFactory import kotlin.reflect.full.createInstance import kotlin.reflect.full.primaryConstructor class ActivityPubServiceImpl(private val activityPubFollowService: ActivityPubFollowService) : ActivityPubService { + + val logger = LoggerFactory.getLogger(this::class.java) override fun parseActivity(json: String): ActivityType { val readTree = Config.configData.objectMapper.readTree(json) + logger.debug("readTree: {}", readTree) if (readTree.isObject.not()) { throw JsonParseException("Json is not object.") } diff --git a/src/test/kotlin/dev/usbharu/hideout/routing/activitypub/InboxRoutingKtTest.kt b/src/test/kotlin/dev/usbharu/hideout/routing/activitypub/InboxRoutingKtTest.kt index 106eae24..1e545605 100644 --- a/src/test/kotlin/dev/usbharu/hideout/routing/activitypub/InboxRoutingKtTest.kt +++ b/src/test/kotlin/dev/usbharu/hideout/routing/activitypub/InboxRoutingKtTest.kt @@ -1,10 +1,6 @@ package dev.usbharu.hideout.routing.activitypub -import com.fasterxml.jackson.annotation.JsonInclude -import com.fasterxml.jackson.annotation.JsonSetter -import com.fasterxml.jackson.annotation.Nulls -import com.fasterxml.jackson.databind.DeserializationFeature -import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper +import dev.usbharu.hideout.exception.JsonParseException import dev.usbharu.hideout.plugins.configureRouting import dev.usbharu.hideout.plugins.configureSerialization import dev.usbharu.hideout.plugins.configureStatusPages @@ -13,20 +9,19 @@ import dev.usbharu.hideout.service.activitypub.ActivityPubUserService import dev.usbharu.hideout.service.impl.UserService import dev.usbharu.hideout.service.signature.HttpSignatureVerifyService import io.ktor.client.request.* -import io.ktor.client.statement.* import io.ktor.http.* import io.ktor.server.config.* import io.ktor.server.testing.* -import junit.framework.TestCase.assertEquals import org.junit.jupiter.api.Assertions import org.junit.jupiter.api.Test import org.mockito.kotlin.any import org.mockito.kotlin.doReturn +import org.mockito.kotlin.doThrow import org.mockito.kotlin.mock class InboxRoutingKtTest { @Test - fun `sharedInboxにGETしたら501が帰ってくる`() = testApplication { + fun `sharedInboxにGETしたら405が帰ってくる`() = testApplication { environment { config = ApplicationConfig("empty.conf") } @@ -47,7 +42,9 @@ class InboxRoutingKtTest { val httpSignatureVerifyService = mock{ on { verify(any()) } doReturn true } - val activityPubService = mock() + val activityPubService = mock{ + on { parseActivity(any()) } doThrow JsonParseException() + } val userService = mock() val activityPubUserService = mock() application { @@ -59,4 +56,18 @@ class InboxRoutingKtTest { Assertions.assertEquals(HttpStatusCode.BadRequest, it.status) } } + + @Test + fun `ユーザのinboxにGETしたら405が帰ってくる`() = testApplication { + environment { + config = ApplicationConfig("empty.conf") + } + application { + configureSerialization() + configureRouting(mock(), mock(), mock(), mock()) + } + client.get("/users/test/inbox").let { + Assertions.assertEquals(HttpStatusCode.MethodNotAllowed, it.status) + } + } } diff --git a/src/test/resources/empty.conf b/src/test/resources/empty.conf index 861f37bc..ba691e1c 100644 --- a/src/test/resources/empty.conf +++ b/src/test/resources/empty.conf @@ -3,7 +3,6 @@ ktor { deployment { port = 8080 port = ${?PORT} - watch = [classes, resources] } application { modules = [dev.usbharu.hideout.EmptyKt.empty] From 2a3db9dddcc3638dbfca02a76166364ab7b1c560 Mon Sep 17 00:00:00 2001 From: usbharu <64310155+usbharu@users.noreply.github.com> Date: Mon, 10 Apr 2023 15:07:56 +0900 Subject: [PATCH 35/40] =?UTF-8?q?test:=20=E3=83=A6=E3=83=BC=E3=82=B6?= =?UTF-8?q?=E3=83=BC=E3=81=AEinbox=E3=81=AEPOST=E3=81=AE=E3=83=86=E3=82=B9?= =?UTF-8?q?=E3=83=88=E3=82=92=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../routing/activitypub/InboxRoutingKtTest.kt | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/src/test/kotlin/dev/usbharu/hideout/routing/activitypub/InboxRoutingKtTest.kt b/src/test/kotlin/dev/usbharu/hideout/routing/activitypub/InboxRoutingKtTest.kt index 1e545605..c4de8bfb 100644 --- a/src/test/kotlin/dev/usbharu/hideout/routing/activitypub/InboxRoutingKtTest.kt +++ b/src/test/kotlin/dev/usbharu/hideout/routing/activitypub/InboxRoutingKtTest.kt @@ -70,4 +70,27 @@ class InboxRoutingKtTest { Assertions.assertEquals(HttpStatusCode.MethodNotAllowed, it.status) } } + + @Test + fun `ユーザーのinboxに空のリクエストボディでPOSTしたら400が帰ってくる`() = testApplication { + environment { + config = ApplicationConfig("empty.conf") + } + val httpSignatureVerifyService = mock{ + on { verify(any()) } doReturn true + } + val activityPubService = mock{ + on { parseActivity(any()) } doThrow JsonParseException() + } + val userService = mock() + val activityPubUserService = mock() + application { + configureStatusPages() + configureSerialization() + configureRouting(httpSignatureVerifyService, activityPubService, userService, activityPubUserService) + } + client.post("/users/test/inbox").let { + Assertions.assertEquals(HttpStatusCode.BadRequest, it.status) + } + } } From a9200eaeef1f6465781608c77f71575162e5351f Mon Sep 17 00:00:00 2001 From: usbharu <64310155+usbharu@users.noreply.github.com> Date: Mon, 10 Apr 2023 15:11:26 +0900 Subject: [PATCH 36/40] =?UTF-8?q?refactor:=20Inbox=E3=82=92=E6=94=B9?= =?UTF-8?q?=E5=96=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../routing/activitypub/InboxRouting.kt | 23 +++++++++++++------ 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/src/main/kotlin/dev/usbharu/hideout/routing/activitypub/InboxRouting.kt b/src/main/kotlin/dev/usbharu/hideout/routing/activitypub/InboxRouting.kt index 92753f36..9ab1d098 100644 --- a/src/main/kotlin/dev/usbharu/hideout/routing/activitypub/InboxRouting.kt +++ b/src/main/kotlin/dev/usbharu/hideout/routing/activitypub/InboxRouting.kt @@ -30,9 +30,13 @@ fun Routing.inbox( call.application.log.debug("ActivityTypes: ${activityTypes.name}") val response = activityPubService.processActivity(json, activityTypes) when (response) { - is ActivityPubObjectResponse -> call.respond(response.httpStatusCode, Config.configData.objectMapper.writeValueAsString(response.message.apply { context = - listOf("https://www.w3.org/ns/activitystreams") - })) + is ActivityPubObjectResponse -> call.respond( + response.httpStatusCode, + 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) } @@ -47,13 +51,18 @@ fun Routing.inbox( throw HttpSignatureVerifyException() } val json = call.receiveText() + call.application.log.trace("Received: $json") val activityTypes = activityPubService.parseActivity(json) - println(activityTypes) + call.application.log.debug("ActivityTypes: ${activityTypes.name}") val response = activityPubService.processActivity(json, activityTypes) when (response) { - is ActivityPubObjectResponse -> call.respond(response.httpStatusCode, Config.configData.objectMapper.writeValueAsString(response.message.apply { context = - listOf("https://www.w3.org/ns/activitystreams") - })) + is ActivityPubObjectResponse -> call.respond( + response.httpStatusCode, + 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) } From 5846dde42f03aaffdbbe8819f402acc533078470 Mon Sep 17 00:00:00 2001 From: usbharu <64310155+usbharu@users.noreply.github.com> Date: Mon, 10 Apr 2023 16:49:11 +0900 Subject: [PATCH 37/40] =?UTF-8?q?feat:=20=E3=83=95=E3=82=A9=E3=83=AD?= =?UTF-8?q?=E3=83=BC=E3=83=86=E3=83=BC=E3=83=96=E3=83=AB=E3=81=AB=E3=83=95?= =?UTF-8?q?=E3=82=A9=E3=83=AD=E3=83=BC=E6=83=85=E5=A0=B1=E3=82=92=E8=A8=98?= =?UTF-8?q?=E9=8C=B2=E3=81=99=E3=82=8B=E3=82=88=E3=81=86=E3=81=AB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../kotlin/dev/usbharu/hideout/Application.kt | 2 +- src/main/kotlin/dev/usbharu/hideout/ap/Key.kt | 6 ++-- .../kotlin/dev/usbharu/hideout/ap/Person.kt | 4 +-- .../ap/IllegalActivityPubObjectException.kt | 8 +++++ .../hideout/repository/IUserRepository.kt | 6 ++++ .../hideout/repository/UserRepository.kt | 22 ++++++++++++ .../hideout/service/IUserAuthService.kt | 12 ++++--- .../ActivityPubFollowServiceImpl.kt | 11 ++++-- .../activitypub/ActivityPubUserServiceImpl.kt | 34 +++++++++++++++++-- .../hideout/service/impl/UserAuthService.kt | 5 +++ .../hideout/service/impl/UserService.kt | 12 +++++++ .../hideout/plugins/ActivityPubKtTest.kt | 12 +++++++ .../usbharu/hideout/plugins/KtorKeyMapTest.kt | 12 +++++++ 13 files changed, 130 insertions(+), 16 deletions(-) create mode 100644 src/main/kotlin/dev/usbharu/hideout/exception/ap/IllegalActivityPubObjectException.kt diff --git a/src/main/kotlin/dev/usbharu/hideout/Application.kt b/src/main/kotlin/dev/usbharu/hideout/Application.kt index 66b0b169..320ec162 100644 --- a/src/main/kotlin/dev/usbharu/hideout/Application.kt +++ b/src/main/kotlin/dev/usbharu/hideout/Application.kt @@ -81,7 +81,7 @@ fun Application.parent() { } } } - single { ActivityPubFollowServiceImpl(get(), get(), get()) } + single { ActivityPubFollowServiceImpl(get(), get(), get(),get()) } single { ActivityPubServiceImpl(get()) } single { UserService(get()) } single { ActivityPubUserServiceImpl(get(), get(), get()) } diff --git a/src/main/kotlin/dev/usbharu/hideout/ap/Key.kt b/src/main/kotlin/dev/usbharu/hideout/ap/Key.kt index ab3ae13b..b3dccdfd 100644 --- a/src/main/kotlin/dev/usbharu/hideout/ap/Key.kt +++ b/src/main/kotlin/dev/usbharu/hideout/ap/Key.kt @@ -1,9 +1,9 @@ package dev.usbharu.hideout.ap open class Key : Object{ - private var id:String? = null - private var owner:String? = null - private var publicKeyPem:String? = null + var id:String? = null + var owner:String? = null + var publicKeyPem:String? = null protected constructor() : super() constructor( type: List, diff --git a/src/main/kotlin/dev/usbharu/hideout/ap/Person.kt b/src/main/kotlin/dev/usbharu/hideout/ap/Person.kt index 270eba23..148892b0 100644 --- a/src/main/kotlin/dev/usbharu/hideout/ap/Person.kt +++ b/src/main/kotlin/dev/usbharu/hideout/ap/Person.kt @@ -5,10 +5,10 @@ open class Person : Object { var preferredUsername:String? = null var summary:String? = null var inbox:String? = null - private var outbox:String? = null + var outbox:String? = null private var url:String? = null private var icon:Image? = null - private var publicKey:Key? = null + var publicKey:Key? = null protected constructor() : super() constructor( type: List = emptyList(), diff --git a/src/main/kotlin/dev/usbharu/hideout/exception/ap/IllegalActivityPubObjectException.kt b/src/main/kotlin/dev/usbharu/hideout/exception/ap/IllegalActivityPubObjectException.kt new file mode 100644 index 00000000..12965d62 --- /dev/null +++ b/src/main/kotlin/dev/usbharu/hideout/exception/ap/IllegalActivityPubObjectException.kt @@ -0,0 +1,8 @@ +package dev.usbharu.hideout.exception.ap + +class IllegalActivityPubObjectException : IllegalArgumentException { + constructor() : super() + constructor(s: String?) : super(s) + constructor(message: String?, cause: Throwable?) : super(message, cause) + constructor(cause: Throwable?) : super(cause) +} diff --git a/src/main/kotlin/dev/usbharu/hideout/repository/IUserRepository.kt b/src/main/kotlin/dev/usbharu/hideout/repository/IUserRepository.kt index 53001878..dd89dbd9 100644 --- a/src/main/kotlin/dev/usbharu/hideout/repository/IUserRepository.kt +++ b/src/main/kotlin/dev/usbharu/hideout/repository/IUserRepository.kt @@ -8,10 +8,16 @@ interface IUserRepository { suspend fun findById(id: Long): UserEntity? + suspend fun findByIds(ids: List): List + suspend fun findByName(name: String): UserEntity? + suspend fun findByNameAndDomains(names: List>): List + suspend fun findByUrl(url:String):UserEntity? + suspend fun findByUrls(urls: List): List + suspend fun update(userEntity: UserEntity) suspend fun delete(id: Long) diff --git a/src/main/kotlin/dev/usbharu/hideout/repository/UserRepository.kt b/src/main/kotlin/dev/usbharu/hideout/repository/UserRepository.kt index 6a262e2b..2bb4abe5 100644 --- a/src/main/kotlin/dev/usbharu/hideout/repository/UserRepository.kt +++ b/src/main/kotlin/dev/usbharu/hideout/repository/UserRepository.kt @@ -79,6 +79,14 @@ class UserRepository(private val database: Database) : IUserRepository { } } + override suspend fun findByIds(ids: List): List { + return query { + Users.select { Users.id inList ids }.map { + it.toUserEntity() + } + } + } + override suspend fun findByName(name: String): UserEntity? { return query { Users.select { Users.name eq name }.map { @@ -87,12 +95,26 @@ class UserRepository(private val database: Database) : IUserRepository { } } + override suspend fun findByNameAndDomains(names: List>): List { + return query { + val selectAll = Users.selectAll() + names.forEach { (name, domain) -> + selectAll.orWhere { Users.name eq name and (Users.domain eq domain) } + } + selectAll.map { it.toUserEntity() } + } + } + override suspend fun findByUrl(url: String): UserEntity? { return query { Users.select { Users.url eq url }.singleOrNull()?.toUserEntity() } } + override suspend fun findByUrls(urls: List): List { + TODO("Not yet implemented") + } + override suspend fun findFollowersById(id: Long): List { return query { val followers = Users.alias("FOLLOWERS") diff --git a/src/main/kotlin/dev/usbharu/hideout/service/IUserAuthService.kt b/src/main/kotlin/dev/usbharu/hideout/service/IUserAuthService.kt index e949d4d4..d2096d37 100644 --- a/src/main/kotlin/dev/usbharu/hideout/service/IUserAuthService.kt +++ b/src/main/kotlin/dev/usbharu/hideout/service/IUserAuthService.kt @@ -1,16 +1,18 @@ package dev.usbharu.hideout.service +import dev.usbharu.hideout.domain.model.UserAuthentication import dev.usbharu.hideout.domain.model.UserAuthenticationEntity interface IUserAuthService { - fun hash(password:String): String + fun hash(password: String): String - suspend fun usernameAlreadyUse(username: String):Boolean + suspend fun usernameAlreadyUse(username: String): Boolean suspend fun registerAccount(username: String, hash: String) - suspend fun verifyAccount(username: String,password: String): Boolean + suspend fun verifyAccount(username: String, password: String): Boolean - suspend fun findByUserId(userId: Long):UserAuthenticationEntity + suspend fun findByUserId(userId: Long): UserAuthenticationEntity - suspend fun findByUsername(username: String):UserAuthenticationEntity + suspend fun findByUsername(username: String): UserAuthenticationEntity + suspend fun createAccount(userEntity: UserAuthentication): UserAuthenticationEntity } diff --git a/src/main/kotlin/dev/usbharu/hideout/service/activitypub/ActivityPubFollowServiceImpl.kt b/src/main/kotlin/dev/usbharu/hideout/service/activitypub/ActivityPubFollowServiceImpl.kt index 925a735c..0a606f4a 100644 --- a/src/main/kotlin/dev/usbharu/hideout/service/activitypub/ActivityPubFollowServiceImpl.kt +++ b/src/main/kotlin/dev/usbharu/hideout/service/activitypub/ActivityPubFollowServiceImpl.kt @@ -8,6 +8,7 @@ import dev.usbharu.hideout.domain.model.ActivityPubResponse import dev.usbharu.hideout.domain.model.ActivityPubStringResponse import dev.usbharu.hideout.domain.model.job.ReceiveFollowJob import dev.usbharu.hideout.plugins.postAp +import dev.usbharu.hideout.service.impl.UserService import dev.usbharu.hideout.service.job.JobQueueParentService import io.ktor.client.* import io.ktor.http.* @@ -16,6 +17,7 @@ import kjob.core.job.JobProps class ActivityPubFollowServiceImpl( private val jobQueueParentService: JobQueueParentService, private val activityPubUserService: ActivityPubUserService, + private val userService: UserService, private val httpClient: HttpClient ) : ActivityPubFollowService { override suspend fun receiveFollow(follow: Follow): ActivityPubResponse { @@ -32,14 +34,19 @@ class ActivityPubFollowServiceImpl( val actor = props[ReceiveFollowJob.actor] val person = activityPubUserService.fetchPerson(actor) val follow = Config.configData.objectMapper.readValue(props[ReceiveFollowJob.follow]) + val targetActor = props[ReceiveFollowJob.targetActor] httpClient.postAp( urlString = person.inbox ?: throw IllegalArgumentException("inbox is not found"), - username = "${props[ReceiveFollowJob.targetActor]}#pubkey", + username = "$targetActor#pubkey", jsonLd = Accept( name = "Follow", `object` = follow, - actor = props[ReceiveFollowJob.targetActor] + actor = targetActor ) ) + val users = + userService.findByUrls(listOf(targetActor, follow.actor ?: throw IllegalArgumentException("actor is null"))) + + userService.addFollowers(users.first { it.url == targetActor }.id, users.first { it.url == follow.actor }.id) } } diff --git a/src/main/kotlin/dev/usbharu/hideout/service/activitypub/ActivityPubUserServiceImpl.kt b/src/main/kotlin/dev/usbharu/hideout/service/activitypub/ActivityPubUserServiceImpl.kt index 4af88bac..2c5c5002 100644 --- a/src/main/kotlin/dev/usbharu/hideout/service/activitypub/ActivityPubUserServiceImpl.kt +++ b/src/main/kotlin/dev/usbharu/hideout/service/activitypub/ActivityPubUserServiceImpl.kt @@ -5,7 +5,10 @@ import dev.usbharu.hideout.ap.Image import dev.usbharu.hideout.ap.Key import dev.usbharu.hideout.ap.Person import dev.usbharu.hideout.config.Config +import dev.usbharu.hideout.domain.model.User +import dev.usbharu.hideout.domain.model.UserAuthentication import dev.usbharu.hideout.exception.UserNotFoundException +import dev.usbharu.hideout.exception.ap.IllegalActivityPubObjectException import dev.usbharu.hideout.service.IUserAuthService import dev.usbharu.hideout.service.impl.UserService import dev.usbharu.hideout.util.HttpUtil.Activity @@ -14,7 +17,11 @@ import io.ktor.client.request.* import io.ktor.client.statement.* import io.ktor.http.* -class ActivityPubUserServiceImpl(private val userService: UserService, private val userAuthService: IUserAuthService,private val httpClient: HttpClient) : +class ActivityPubUserServiceImpl( + private val userService: UserService, + private val userAuthService: IUserAuthService, + private val httpClient: HttpClient +) : ActivityPubUserService { override suspend fun getPersonByName(name: String): Person { // TODO: JOINで書き直し @@ -74,11 +81,32 @@ class ActivityPubUserServiceImpl(private val userService: UserService, private v ) ) - } catch (e:UserNotFoundException){ + } catch (e: UserNotFoundException) { val httpResponse = httpClient.get(url) { accept(ContentType.Application.Activity) } - Config.configData.objectMapper.readValue(httpResponse.bodyAsText()) + val person = Config.configData.objectMapper.readValue(httpResponse.bodyAsText()) + val userEntity = userService.create( + User( + name = person.preferredUsername + ?: throw IllegalActivityPubObjectException("preferredUsername is null"), + domain = url.substringAfter(":").substringBeforeLast("/"), + screenName = person.name ?: throw IllegalActivityPubObjectException("name is null"), + description = person.summary ?: throw IllegalActivityPubObjectException("summary is null"), + inbox = person.inbox ?: throw IllegalActivityPubObjectException("inbox is null"), + outbox = person.outbox ?: throw IllegalActivityPubObjectException("outbox is null"), + url = url + ) + ) + userAuthService.createAccount( + UserAuthentication( + userEntity.id, + null, + person.publicKey?.publicKeyPem ?: throw IllegalActivityPubObjectException("publicKey is null"), + null + ) + ) + person } } diff --git a/src/main/kotlin/dev/usbharu/hideout/service/impl/UserAuthService.kt b/src/main/kotlin/dev/usbharu/hideout/service/impl/UserAuthService.kt index 88247524..5542d1ee 100644 --- a/src/main/kotlin/dev/usbharu/hideout/service/impl/UserAuthService.kt +++ b/src/main/kotlin/dev/usbharu/hideout/service/impl/UserAuthService.kt @@ -4,6 +4,7 @@ import dev.usbharu.hideout.config.Config import dev.usbharu.hideout.domain.model.User import dev.usbharu.hideout.domain.model.UserAuthentication import dev.usbharu.hideout.domain.model.UserAuthenticationEntity +import dev.usbharu.hideout.domain.model.UserEntity import dev.usbharu.hideout.exception.UserNotFoundException import dev.usbharu.hideout.repository.IUserAuthRepository import dev.usbharu.hideout.repository.IUserRepository @@ -76,6 +77,10 @@ class UserAuthService( ?: throw UserNotFoundException("$username auth data was not found") } + override suspend fun createAccount(userEntity: UserAuthentication): UserAuthenticationEntity { + return userAuthRepository.create(userEntity) + } + private fun generateKeyPair(): KeyPair { val keyPairGenerator = KeyPairGenerator.getInstance("RSA") keyPairGenerator.initialize(1024) diff --git a/src/main/kotlin/dev/usbharu/hideout/service/impl/UserService.kt b/src/main/kotlin/dev/usbharu/hideout/service/impl/UserService.kt index f090d38f..a64ccd04 100644 --- a/src/main/kotlin/dev/usbharu/hideout/service/impl/UserService.kt +++ b/src/main/kotlin/dev/usbharu/hideout/service/impl/UserService.kt @@ -21,15 +21,27 @@ class UserService(private val userRepository: IUserRepository) { return userRepository.findById(id) ?: throw UserNotFoundException("$id was not found.") } + suspend fun findByIds(ids: List): List { + return userRepository.findByIds(ids) + } + suspend fun findByName(name: String): UserEntity { return userRepository.findByName(name) ?: throw UserNotFoundException("$name was not found.") } + suspend fun findByNameAndDomains(names: List>): List { + return userRepository.findByNameAndDomains(names) + } + suspend fun findByUrl(url: String): UserEntity { return userRepository.findByUrl(url) ?: throw UserNotFoundException("$url was not found.") } + suspend fun findByUrls(urls: List): List { + return userRepository.findByUrls(urls) + } + suspend fun create(user: User): UserEntity { return userRepository.create(user) } diff --git a/src/test/kotlin/dev/usbharu/hideout/plugins/ActivityPubKtTest.kt b/src/test/kotlin/dev/usbharu/hideout/plugins/ActivityPubKtTest.kt index 8cadb063..19246b90 100644 --- a/src/test/kotlin/dev/usbharu/hideout/plugins/ActivityPubKtTest.kt +++ b/src/test/kotlin/dev/usbharu/hideout/plugins/ActivityPubKtTest.kt @@ -31,14 +31,26 @@ class ActivityPubKtTest { TODO("Not yet implemented") } + override suspend fun findByIds(ids: List): List { + TODO("Not yet implemented") + } + override suspend fun findByName(name: String): UserEntity? { return UserEntity(1, "test", "localhost", "test", "","","","") } + override suspend fun findByNameAndDomains(names: List>): List { + TODO("Not yet implemented") + } + override suspend fun findByUrl(url: String): UserEntity? { TODO("Not yet implemented") } + override suspend fun findByUrls(urls: List): List { + TODO("Not yet implemented") + } + override suspend fun update(userEntity: UserEntity) { TODO("Not yet implemented") } diff --git a/src/test/kotlin/dev/usbharu/hideout/plugins/KtorKeyMapTest.kt b/src/test/kotlin/dev/usbharu/hideout/plugins/KtorKeyMapTest.kt index 7e87a802..02cfb9b4 100644 --- a/src/test/kotlin/dev/usbharu/hideout/plugins/KtorKeyMapTest.kt +++ b/src/test/kotlin/dev/usbharu/hideout/plugins/KtorKeyMapTest.kt @@ -26,14 +26,26 @@ class KtorKeyMapTest { TODO("Not yet implemented") } + override suspend fun findByIds(ids: List): List { + TODO("Not yet implemented") + } + override suspend fun findByName(name: String): UserEntity? { return UserEntity(1, "test", "localhost", "test", "","","","") } + override suspend fun findByNameAndDomains(names: List>): List { + TODO("Not yet implemented") + } + override suspend fun findByUrl(url: String): UserEntity? { TODO("Not yet implemented") } + override suspend fun findByUrls(urls: List): List { + TODO("Not yet implemented") + } + override suspend fun update(userEntity: UserEntity) { TODO("Not yet implemented") } From 71ad3c9c7d895242e203193d002a2f99cb01825c Mon Sep 17 00:00:00 2001 From: usbharu <64310155+usbharu@users.noreply.github.com> Date: Wed, 12 Apr 2023 15:46:45 +0900 Subject: [PATCH 38/40] =?UTF-8?q?test:=20UserRepository=E3=81=AE=E3=83=86?= =?UTF-8?q?=E3=82=B9=E3=83=88=E3=82=92=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle.kts | 1 + .../hideout/repository/UserRepository.kt | 5 +- .../hideout/repository/UserRepositoryTest.kt | 104 ++++++++++++++++++ src/test/kotlin/utils/DBResetInterceptor.kt | 28 +++++ src/test/kotlin/utils/DatabaseTestBase.kt | 18 +++ 5 files changed, 155 insertions(+), 1 deletion(-) create mode 100644 src/test/kotlin/dev/usbharu/hideout/repository/UserRepositoryTest.kt create mode 100644 src/test/kotlin/utils/DBResetInterceptor.kt create mode 100644 src/test/kotlin/utils/DatabaseTestBase.kt diff --git a/build.gradle.kts b/build.gradle.kts index 2e5f0742..4dee9ed6 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -64,6 +64,7 @@ dependencies { testImplementation("io.ktor:ktor-server-tests-jvm:$ktor_version") testImplementation("org.jetbrains.kotlin:kotlin-test-junit:$kotlin_version") + testImplementation ("org.jetbrains.kotlinx:kotlinx-coroutines-test:1.6.4") implementation("io.ktor:ktor-client-core:$ktor_version") implementation("io.ktor:ktor-client-cio:$ktor_version") diff --git a/src/main/kotlin/dev/usbharu/hideout/repository/UserRepository.kt b/src/main/kotlin/dev/usbharu/hideout/repository/UserRepository.kt index 2bb4abe5..f51240f0 100644 --- a/src/main/kotlin/dev/usbharu/hideout/repository/UserRepository.kt +++ b/src/main/kotlin/dev/usbharu/hideout/repository/UserRepository.kt @@ -133,7 +133,10 @@ class UserRepository(private val database: Database) : IUserRepository { followers.get(Users.name), followers.get(Users.domain), followers.get(Users.screenName), - followers.get(Users.description) + followers.get(Users.description), + followers.get(Users.inbox), + followers.get(Users.outbox), + followers.get(Users.url) ) .select { Users.id eq id } .map { diff --git a/src/test/kotlin/dev/usbharu/hideout/repository/UserRepositoryTest.kt b/src/test/kotlin/dev/usbharu/hideout/repository/UserRepositoryTest.kt new file mode 100644 index 00000000..73b3e449 --- /dev/null +++ b/src/test/kotlin/dev/usbharu/hideout/repository/UserRepositoryTest.kt @@ -0,0 +1,104 @@ +@file:OptIn(ExperimentalCoroutinesApi::class) + +package dev.usbharu.hideout.repository + +import dev.usbharu.hideout.domain.model.User +import dev.usbharu.hideout.domain.model.Users +import dev.usbharu.hideout.domain.model.UsersFollowers +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.runTest +import org.jetbrains.exposed.sql.SchemaUtils +import org.jetbrains.exposed.sql.select +import org.jetbrains.exposed.sql.transactions.experimental.newSuspendedTransaction +import org.junit.jupiter.api.Assertions.* +import org.junit.jupiter.api.BeforeAll +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.TestInstance +import utils.DatabaseTestBase + +@TestInstance(TestInstance.Lifecycle.PER_CLASS) +class UserRepositoryTest : DatabaseTestBase() { + + @BeforeAll + fun beforeAll() { + SchemaUtils.create(Users) + SchemaUtils.create(UsersFollowers) + } + + @Test + fun `findFollowersById フォロワー一覧を取得`() = runTest { + newSuspendedTransaction { + val userRepository = UserRepository(db) + val user = userRepository.create( + User( + "test", + "example.com", + "testUser", + "This user is test user.", + "https://example.com/inbox", + "https://example.com/outbox", + "https://example.com" + ) + ) + val follower = userRepository.create( + User( + "follower", + "follower.example.com", + "followerUser", + "This user is follower user.", + "https://follower.example.com/inbox", + "https://follower.example.com/outbox", + "https://follower.example.com" + ) + ) + val follower2 = userRepository.create( + User( + "follower2", + "follower2.example.com", + "followerUser2", + "This user is follower user 2.", + "https://follower2.example.com/inbox", + "https://follower2.example.com/outbox", + "https://follower2.example.com" + ) + ) + userRepository.createFollower(user.id, follower.id) + userRepository.createFollower(user.id, follower2.id) + userRepository.findFollowersById(user.id).let { + assertIterableEquals(listOf(follower, follower2), it) + } + } + } + + @Test + fun `createFollower フォロワー追加`() = runTest { + newSuspendedTransaction { + val userRepository = UserRepository(db) + val user = userRepository.create( + User( + "test", + "example.com", + "testUser", + "This user is test user.", + "https://example.com/inbox", + "https://example.com/outbox", + "https://example.com" + ) + ) + val follower = userRepository.create( + User( + "follower", + "follower.example.com", + "followerUser", + "This user is follower user.", + "https://follower.example.com/inbox", + "https://follower.example.com/outbox", + "https://follower.example.com" + ) + ) + userRepository.createFollower(user.id, follower.id) + val followerIds = UsersFollowers.select { UsersFollowers.userId eq user.id }.map { it[UsersFollowers.followerId] } + assertIterableEquals(listOf(follower.id), followerIds) + } + } +} diff --git a/src/test/kotlin/utils/DBResetInterceptor.kt b/src/test/kotlin/utils/DBResetInterceptor.kt new file mode 100644 index 00000000..32fc88a9 --- /dev/null +++ b/src/test/kotlin/utils/DBResetInterceptor.kt @@ -0,0 +1,28 @@ +package utils + +import org.jetbrains.exposed.sql.Transaction +import org.jetbrains.exposed.sql.transactions.TransactionManager +import org.junit.jupiter.api.extension.* + +class DBResetInterceptor : BeforeAllCallback,AfterAllCallback,BeforeEachCallback,AfterEachCallback { + private lateinit var transactionAll: Transaction + private lateinit var transactionEach: Transaction + + override fun beforeAll(context: ExtensionContext?) { + transactionAll = TransactionManager.manager.newTransaction() + } + + override fun afterAll(context: ExtensionContext?) { + transactionAll.rollback() + transactionAll.close() + } + + override fun beforeEach(context: ExtensionContext?) { + transactionEach = TransactionManager.manager.newTransaction(outerTransaction = transactionAll) + } + + override fun afterEach(context: ExtensionContext?) { + transactionEach.rollback() + transactionEach.close() + } +} diff --git a/src/test/kotlin/utils/DatabaseTestBase.kt b/src/test/kotlin/utils/DatabaseTestBase.kt new file mode 100644 index 00000000..10653631 --- /dev/null +++ b/src/test/kotlin/utils/DatabaseTestBase.kt @@ -0,0 +1,18 @@ +package utils + +import org.jetbrains.exposed.sql.Database +import org.jetbrains.exposed.sql.DatabaseConfig +import org.junit.jupiter.api.extension.ExtendWith + + +@ExtendWith(DBResetInterceptor::class) +abstract class DatabaseTestBase { + companion object { + init { + Database.connect( + "jdbc:h2:mem:test;DB_CLOSE_DELAY=-1", + driver = "org.h2.Driver", + databaseConfig = DatabaseConfig { useNestedTransactions = true }) + } + } +} From f4df3454077302e37c6dd24b043f80340224c662 Mon Sep 17 00:00:00 2001 From: usbharu <64310155+usbharu@users.noreply.github.com> Date: Wed, 12 Apr 2023 21:53:49 +0900 Subject: [PATCH 39/40] =?UTF-8?q?test:=20=E3=83=95=E3=82=A9=E3=83=AD?= =?UTF-8?q?=E3=83=BC=E5=8F=97=E4=BB=98=E5=87=A6=E7=90=86=E3=81=AEJob?= =?UTF-8?q?=E3=81=AE=E3=83=86=E3=82=B9=E3=83=88=E3=82=92=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle.kts | 1 + .../kotlin/dev/usbharu/hideout/ap/Accept.kt | 22 +++ .../kotlin/dev/usbharu/hideout/ap/JsonLd.kt | 4 + .../kotlin/dev/usbharu/hideout/ap/Object.kt | 4 + .../domain/model/ActivityPubStringResponse.kt | 4 +- .../ActivityPubFollowServiceImplTest.kt | 153 ++++++++++++++++++ src/test/kotlin/utils/JsonObjectMapper.kt | 22 +++ 7 files changed, 208 insertions(+), 2 deletions(-) create mode 100644 src/test/kotlin/dev/usbharu/hideout/service/activitypub/ActivityPubFollowServiceImplTest.kt create mode 100644 src/test/kotlin/utils/JsonObjectMapper.kt diff --git a/build.gradle.kts b/build.gradle.kts index 4dee9ed6..0dd14e61 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -65,6 +65,7 @@ dependencies { testImplementation("io.ktor:ktor-server-tests-jvm:$ktor_version") testImplementation("org.jetbrains.kotlin:kotlin-test-junit:$kotlin_version") testImplementation ("org.jetbrains.kotlinx:kotlinx-coroutines-test:1.6.4") + testImplementation("io.ktor:ktor-client-mock:$ktor_version") implementation("io.ktor:ktor-client-core:$ktor_version") implementation("io.ktor:ktor-client-cio:$ktor_version") diff --git a/src/main/kotlin/dev/usbharu/hideout/ap/Accept.kt b/src/main/kotlin/dev/usbharu/hideout/ap/Accept.kt index 15014b52..35b822c8 100644 --- a/src/main/kotlin/dev/usbharu/hideout/ap/Accept.kt +++ b/src/main/kotlin/dev/usbharu/hideout/ap/Accept.kt @@ -13,4 +13,26 @@ open class Accept : Object { this.`object` = `object` this.actor = actor } + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other !is Accept) return false + if (!super.equals(other)) return false + + if (`object` != other.`object`) return false + return actor == other.actor + } + + override fun hashCode(): Int { + var result = super.hashCode() + result = 31 * result + (`object`?.hashCode() ?: 0) + result = 31 * result + (actor?.hashCode() ?: 0) + return result + } + + override fun toString(): String { + return "Accept(`object`=$`object`, actor=$actor) ${super.toString()}" + } + + } diff --git a/src/main/kotlin/dev/usbharu/hideout/ap/JsonLd.kt b/src/main/kotlin/dev/usbharu/hideout/ap/JsonLd.kt index bef45e70..285319bd 100644 --- a/src/main/kotlin/dev/usbharu/hideout/ap/JsonLd.kt +++ b/src/main/kotlin/dev/usbharu/hideout/ap/JsonLd.kt @@ -37,6 +37,10 @@ open class JsonLd { return context.hashCode() } + override fun toString(): String { + return "JsonLd(context=$context)" + } + } diff --git a/src/main/kotlin/dev/usbharu/hideout/ap/Object.kt b/src/main/kotlin/dev/usbharu/hideout/ap/Object.kt index 1a05564d..27362b30 100644 --- a/src/main/kotlin/dev/usbharu/hideout/ap/Object.kt +++ b/src/main/kotlin/dev/usbharu/hideout/ap/Object.kt @@ -39,6 +39,10 @@ open class Object : JsonLd { return result } + override fun toString(): String { + return "Object(type=$type, name=$name) ${super.toString()}" + } + } diff --git a/src/main/kotlin/dev/usbharu/hideout/domain/model/ActivityPubStringResponse.kt b/src/main/kotlin/dev/usbharu/hideout/domain/model/ActivityPubStringResponse.kt index 294a1513..2e4b8b4d 100644 --- a/src/main/kotlin/dev/usbharu/hideout/domain/model/ActivityPubStringResponse.kt +++ b/src/main/kotlin/dev/usbharu/hideout/domain/model/ActivityPubStringResponse.kt @@ -10,14 +10,14 @@ sealed class ActivityPubResponse( ) class ActivityPubStringResponse( - httpStatusCode: HttpStatusCode, + httpStatusCode: HttpStatusCode = HttpStatusCode.OK, val message: String, contentType: ContentType = ContentType.Application.Activity ) : ActivityPubResponse(httpStatusCode, contentType) class ActivityPubObjectResponse( - httpStatusCode: HttpStatusCode, + httpStatusCode: HttpStatusCode = HttpStatusCode.OK, val message: JsonLd, contentType: ContentType = ContentType.Application.Activity ) : diff --git a/src/test/kotlin/dev/usbharu/hideout/service/activitypub/ActivityPubFollowServiceImplTest.kt b/src/test/kotlin/dev/usbharu/hideout/service/activitypub/ActivityPubFollowServiceImplTest.kt new file mode 100644 index 00000000..d447ce3e --- /dev/null +++ b/src/test/kotlin/dev/usbharu/hideout/service/activitypub/ActivityPubFollowServiceImplTest.kt @@ -0,0 +1,153 @@ +@file:OptIn(ExperimentalCoroutinesApi::class) +@file:Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE") + +package dev.usbharu.hideout.service.activitypub + +import com.fasterxml.jackson.module.kotlin.readValue +import dev.usbharu.hideout.ap.* +import dev.usbharu.hideout.config.Config +import dev.usbharu.hideout.config.ConfigData +import dev.usbharu.hideout.domain.model.UserEntity +import dev.usbharu.hideout.domain.model.job.ReceiveFollowJob +import dev.usbharu.hideout.service.impl.UserService +import dev.usbharu.hideout.service.job.JobQueueParentService +import io.ktor.client.* +import io.ktor.client.engine.mock.* +import kjob.core.dsl.ScheduleContext +import kjob.core.job.JobProps +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.runTest +import kotlinx.serialization.json.Json +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Test +import org.mockito.ArgumentMatchers.anyString +import org.mockito.kotlin.* +import utils.JsonObjectMapper + +class ActivityPubFollowServiceImplTest { + @Test + fun `receiveFollow フォロー受付処理`() = runTest { + val jobQueueParentService = mock { + onBlocking { schedule(eq(ReceiveFollowJob), any()) } doReturn Unit + } + val activityPubFollowService = ActivityPubFollowServiceImpl(jobQueueParentService, mock(), mock(), mock()) + activityPubFollowService.receiveFollow( + Follow( + emptyList(), + "Follow", + "https://example.com", + "https://follower.example.com" + ) + ) + verify(jobQueueParentService, times(1)).schedule(eq(ReceiveFollowJob), any()) + argumentCaptor.(ReceiveFollowJob) -> Unit> { + verify(jobQueueParentService, times(1)).schedule(eq(ReceiveFollowJob), capture()) + val scheduleContext = ScheduleContext(Json) + firstValue.invoke(scheduleContext, ReceiveFollowJob) + val actor = scheduleContext.props.props[ReceiveFollowJob.actor.name] + val targetActor = scheduleContext.props.props[ReceiveFollowJob.targetActor.name] + val follow = scheduleContext.props.props[ReceiveFollowJob.follow.name] + assertEquals("https://follower.example.com", actor) + assertEquals("https://example.com", targetActor) + assertEquals( + """{"type":"Follow","name":"Follow","object":"https://example.com","actor":"https://follower.example.com","@context":null}""", + follow + ) + } + } + + @Test + fun `receiveFollowJob フォロー受付処理のJob`() = runTest { + Config.configData = ConfigData(objectMapper = JsonObjectMapper.objectMapper) + val person = Person( + type = emptyList(), + name = "follower", + id = "https://follower.example.com", + preferredUsername = "followerUser", + summary = "This user is follower user.", + inbox = "https://follower.example.com/inbox", + outbox = "https://follower.example.com/outbox", + url = "https://follower.example.com", + icon = Image( + type = emptyList(), + name = "https://follower.example.com/image", + mediaType = "image/png", + url = "https://follower.example.com/image" + ), + publicKey = Key( + type = emptyList(), + name = "Public Key", + id = "https://follower.example.com#main-key", + owner = "https://follower.example.com", + publicKeyPem = "BEGIN PUBLIC KEY...END PUBLIC KEY", + ) + + ) + val activityPubUserService = mock { + onBlocking { fetchPerson(anyString()) } doReturn person + } + val userService = mock { + onBlocking { findByUrls(any()) } doReturn listOf( + UserEntity( + id = 1L, + name = "test", + domain = "example.com", + screenName = "testUser", + description = "This user is test user.", + inbox = "https://example.com/inbox", + outbox = "https://example.com/outbox", + url = "https://example.com" + ), + UserEntity( + id = 2L, + name = "follower", + domain = "follower.example.com", + screenName = "followerUser", + description = "This user is test follower user.", + inbox = "https://follower.example.com/inbox", + outbox = "https://follower.example.com/outbox", + url = "https://follower.example.com" + ) + ) + onBlocking { addFollowers(any(), any()) } doReturn Unit + } + val activityPubFollowService = + ActivityPubFollowServiceImpl( + mock(), + activityPubUserService, + userService, + HttpClient(MockEngine { httpRequestData -> + assertEquals(person.inbox, httpRequestData.url.toString()) + val accept = Accept( + type = emptyList(), + name = "Follow", + `object` = Follow( + type = emptyList(), + name = "Follow", + `object` = "https://example.com", + actor = "https://follower.example.com" + ), + actor = "https://example.com" + ) + accept.context += "https://www.w3.org/ns/activitystreams" + assertEquals( + accept, + Config.configData.objectMapper.readValue( + httpRequestData.body.toByteArray().decodeToString() + ) + ) + respondOk() + }) + ) + activityPubFollowService.receiveFollowJob( + JobProps( + data = mapOf( + ReceiveFollowJob.actor.name to "https://follower.example.com", + ReceiveFollowJob.targetActor.name to "https://example.com", + ReceiveFollowJob.follow.name to """{"type":"Follow","name":"Follow","object":"https://example.com","actor":"https://follower.example.com","@context":null}""" + ), + json = Json + ) + ) + } +} diff --git a/src/test/kotlin/utils/JsonObjectMapper.kt b/src/test/kotlin/utils/JsonObjectMapper.kt new file mode 100644 index 00000000..4a595630 --- /dev/null +++ b/src/test/kotlin/utils/JsonObjectMapper.kt @@ -0,0 +1,22 @@ +package utils + +import com.fasterxml.jackson.annotation.JsonInclude +import com.fasterxml.jackson.annotation.JsonSetter +import com.fasterxml.jackson.annotation.Nulls +import com.fasterxml.jackson.databind.DeserializationFeature +import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper + +object JsonObjectMapper { + val objectMapper: com.fasterxml.jackson.databind.ObjectMapper = + jacksonObjectMapper().enable(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY) + .setSerializationInclusion(JsonInclude.Include.NON_EMPTY) + .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false) + + init { + objectMapper.configOverride(List::class.java).setSetterInfo( + JsonSetter.Value.forValueNulls( + Nulls.AS_EMPTY + ) + ) + } +} From d6a54d290b5f9341b62d32727ef55ffe8dde4986 Mon Sep 17 00:00:00 2001 From: usbharu <64310155+usbharu@users.noreply.github.com> Date: Wed, 12 Apr 2023 22:38:36 +0900 Subject: [PATCH 40/40] =?UTF-8?q?test:=20=E4=BD=95=E6=95=85=E3=81=8B?= =?UTF-8?q?=E3=83=A6=E3=83=8B=E3=83=BC=E3=82=AF=E3=82=A4=E3=83=B3=E3=83=87?= =?UTF-8?q?=E3=83=83=E3=82=AF=E3=82=B9=E9=81=95=E5=8F=8D=E3=81=A7=E3=82=B3?= =?UTF-8?q?=E3=82=B1=E3=82=8B=E3=81=AE=E3=81=A7=E3=83=86=E3=82=B9=E3=83=88?= =?UTF-8?q?=E3=81=94=E3=81=A8=E3=81=ABDB=E3=82=92=E5=88=9D=E6=9C=9F?= =?UTF-8?q?=E5=8C=96=E3=81=99=E3=82=8B=E3=82=88=E3=81=86=E3=81=AB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../hideout/repository/UserRepositoryTest.kt | 160 ++++++++++-------- 1 file changed, 88 insertions(+), 72 deletions(-) diff --git a/src/test/kotlin/dev/usbharu/hideout/repository/UserRepositoryTest.kt b/src/test/kotlin/dev/usbharu/hideout/repository/UserRepositoryTest.kt index 73b3e449..740bc90e 100644 --- a/src/test/kotlin/dev/usbharu/hideout/repository/UserRepositoryTest.kt +++ b/src/test/kotlin/dev/usbharu/hideout/repository/UserRepositoryTest.kt @@ -7,98 +7,114 @@ import dev.usbharu.hideout.domain.model.Users import dev.usbharu.hideout.domain.model.UsersFollowers import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.runTest +import org.jetbrains.exposed.sql.Database import org.jetbrains.exposed.sql.SchemaUtils import org.jetbrains.exposed.sql.select -import org.jetbrains.exposed.sql.transactions.experimental.newSuspendedTransaction -import org.junit.jupiter.api.Assertions.* -import org.junit.jupiter.api.BeforeAll +import org.jetbrains.exposed.sql.transactions.transaction +import org.junit.jupiter.api.AfterEach +import org.junit.jupiter.api.Assertions.assertIterableEquals +import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test -import org.junit.jupiter.api.TestInstance -import utils.DatabaseTestBase -@TestInstance(TestInstance.Lifecycle.PER_CLASS) -class UserRepositoryTest : DatabaseTestBase() { - @BeforeAll - fun beforeAll() { - SchemaUtils.create(Users) - SchemaUtils.create(UsersFollowers) +class UserRepositoryTest { + + lateinit var db: Database + + @BeforeEach + fun beforeEach() { + db = Database.connect("jdbc:h2:mem:test;DB_CLOSE_DELAY=-1", driver = "org.h2.Driver") + transaction(db) { + SchemaUtils.create(Users) + SchemaUtils.create(UsersFollowers) + } + } + + @AfterEach + fun tearDown() { + transaction(db) { + + SchemaUtils.drop(UsersFollowers) + SchemaUtils.drop(Users) + } } @Test fun `findFollowersById フォロワー一覧を取得`() = runTest { - newSuspendedTransaction { - val userRepository = UserRepository(db) - val user = userRepository.create( - User( - "test", - "example.com", - "testUser", - "This user is test user.", - "https://example.com/inbox", - "https://example.com/outbox", - "https://example.com" - ) + val userRepository = UserRepository(db) + val user = userRepository.create( + User( + "test", + "example.com", + "testUser", + "This user is test user.", + "https://example.com/inbox", + "https://example.com/outbox", + "https://example.com" ) - val follower = userRepository.create( - User( - "follower", - "follower.example.com", - "followerUser", - "This user is follower user.", - "https://follower.example.com/inbox", - "https://follower.example.com/outbox", - "https://follower.example.com" - ) + ) + val follower = userRepository.create( + User( + "follower", + "follower.example.com", + "followerUser", + "This user is follower user.", + "https://follower.example.com/inbox", + "https://follower.example.com/outbox", + "https://follower.example.com" ) - val follower2 = userRepository.create( - User( - "follower2", - "follower2.example.com", - "followerUser2", - "This user is follower user 2.", - "https://follower2.example.com/inbox", - "https://follower2.example.com/outbox", - "https://follower2.example.com" - ) + ) + val follower2 = userRepository.create( + User( + "follower2", + "follower2.example.com", + "followerUser2", + "This user is follower user 2.", + "https://follower2.example.com/inbox", + "https://follower2.example.com/outbox", + "https://follower2.example.com" ) - userRepository.createFollower(user.id, follower.id) - userRepository.createFollower(user.id, follower2.id) - userRepository.findFollowersById(user.id).let { - assertIterableEquals(listOf(follower, follower2), it) - } + ) + userRepository.createFollower(user.id, follower.id) + userRepository.createFollower(user.id, follower2.id) + userRepository.findFollowersById(user.id).let { + assertIterableEquals(listOf(follower, follower2), it) } + } @Test fun `createFollower フォロワー追加`() = runTest { - newSuspendedTransaction { - val userRepository = UserRepository(db) - val user = userRepository.create( - User( - "test", - "example.com", - "testUser", - "This user is test user.", - "https://example.com/inbox", - "https://example.com/outbox", - "https://example.com" - ) + val userRepository = UserRepository(db) + val user = userRepository.create( + User( + "test", + "example.com", + "testUser", + "This user is test user.", + "https://example.com/inbox", + "https://example.com/outbox", + "https://example.com" ) - val follower = userRepository.create( - User( - "follower", - "follower.example.com", - "followerUser", - "This user is follower user.", - "https://follower.example.com/inbox", - "https://follower.example.com/outbox", - "https://follower.example.com" - ) + ) + val follower = userRepository.create( + User( + "follower", + "follower.example.com", + "followerUser", + "This user is follower user.", + "https://follower.example.com/inbox", + "https://follower.example.com/outbox", + "https://follower.example.com" ) - userRepository.createFollower(user.id, follower.id) - val followerIds = UsersFollowers.select { UsersFollowers.userId eq user.id }.map { it[UsersFollowers.followerId] } + ) + userRepository.createFollower(user.id, follower.id) + transaction { + + val followerIds = + UsersFollowers.select { UsersFollowers.userId eq user.id }.map { it[UsersFollowers.followerId] } assertIterableEquals(listOf(follower.id), followerIds) } + } }