mirror of https://github.com/usbharu/Hideout.git
Merge pull request #17 from usbharu/feature/ap-create
Feature/ap create
This commit is contained in:
commit
c581e563d9
|
@ -6,6 +6,6 @@ exposed_version=0.41.1
|
||||||
h2_version=2.1.214
|
h2_version=2.1.214
|
||||||
koin_version=3.3.1
|
koin_version=3.3.1
|
||||||
org.gradle.parallel=true
|
org.gradle.parallel=true
|
||||||
#org.gradle.configureondemand=true
|
org.gradle.configureondemand=true
|
||||||
org.gradle.caching=true
|
org.gradle.caching=true
|
||||||
org.gradle.jvmargs=-Xmx2048m -XX:+HeapDumpOnOutOfMemoryError -XX:+UseParallelGC
|
org.gradle.jvmargs=-Xmx2048m -XX:+HeapDumpOnOutOfMemoryError -XX:+UseParallelGC
|
||||||
|
|
Binary file not shown.
|
@ -1,6 +1,6 @@
|
||||||
distributionBase=GRADLE_USER_HOME
|
distributionBase=GRADLE_USER_HOME
|
||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.6.1-bin.zip
|
distributionUrl=https\://services.gradle.org/distributions/gradle-8.0-bin.zip
|
||||||
networkTimeout=10000
|
networkTimeout=10000
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
zipStorePath=wrapper/dists
|
zipStorePath=wrapper/dists
|
||||||
|
|
|
@ -1,7 +1,12 @@
|
||||||
package dev.usbharu.hideout.domain.model.ap
|
package dev.usbharu.hideout.domain.model.ap
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.databind.annotation.JsonDeserialize
|
||||||
|
|
||||||
open class Create : Object {
|
open class Create : Object {
|
||||||
|
@JsonDeserialize(using = ObjectDeserializer::class)
|
||||||
var `object`: Object? = null
|
var `object`: Object? = null
|
||||||
|
var to: List<String> = emptyList()
|
||||||
|
var cc: List<String> = emptyList()
|
||||||
|
|
||||||
protected constructor() : super()
|
protected constructor() : super()
|
||||||
constructor(
|
constructor(
|
||||||
|
@ -9,14 +14,18 @@ open class Create : Object {
|
||||||
name: String? = null,
|
name: String? = null,
|
||||||
`object`: Object?,
|
`object`: Object?,
|
||||||
actor: String? = null,
|
actor: String? = null,
|
||||||
id: String? = null
|
id: String? = null,
|
||||||
|
to: List<String> = emptyList(),
|
||||||
|
cc: List<String> = emptyList()
|
||||||
) : super(
|
) : super(
|
||||||
add(type, "Create"),
|
type = add(type, "Create"),
|
||||||
name,
|
name = name,
|
||||||
actor,
|
actor = actor,
|
||||||
id
|
id = id
|
||||||
) {
|
) {
|
||||||
this.`object` = `object`
|
this.`object` = `object`
|
||||||
|
this.to = to
|
||||||
|
this.cc = cc
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun equals(other: Any?): Boolean {
|
override fun equals(other: Any?): Boolean {
|
||||||
|
|
|
@ -5,6 +5,9 @@ open class Note : Object {
|
||||||
var content: String? = null
|
var content: String? = null
|
||||||
var published: String? = null
|
var published: String? = null
|
||||||
var to: List<String> = emptyList()
|
var to: List<String> = emptyList()
|
||||||
|
var cc: List<String> = emptyList()
|
||||||
|
var sensitive: Boolean = false
|
||||||
|
var inReplyTo: String? = null
|
||||||
|
|
||||||
protected constructor() : super()
|
protected constructor() : super()
|
||||||
constructor(
|
constructor(
|
||||||
|
@ -14,7 +17,10 @@ open class Note : Object {
|
||||||
attributedTo: String?,
|
attributedTo: String?,
|
||||||
content: String?,
|
content: String?,
|
||||||
published: String?,
|
published: String?,
|
||||||
to: List<String> = emptyList()
|
to: List<String> = emptyList(),
|
||||||
|
cc: List<String> = emptyList(),
|
||||||
|
sensitive: Boolean = false,
|
||||||
|
inReplyTo: String? = null
|
||||||
) : super(
|
) : super(
|
||||||
type = add(type, "Note"),
|
type = add(type, "Note"),
|
||||||
name = name,
|
name = name,
|
||||||
|
@ -24,6 +30,9 @@ open class Note : Object {
|
||||||
this.content = content
|
this.content = content
|
||||||
this.published = published
|
this.published = published
|
||||||
this.to = to
|
this.to = to
|
||||||
|
this.cc = cc
|
||||||
|
this.sensitive = sensitive
|
||||||
|
this.inReplyTo = inReplyTo
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun equals(other: Any?): Boolean {
|
override fun equals(other: Any?): Boolean {
|
||||||
|
|
|
@ -4,7 +4,7 @@ import com.fasterxml.jackson.core.JsonParser
|
||||||
import com.fasterxml.jackson.databind.DeserializationContext
|
import com.fasterxml.jackson.databind.DeserializationContext
|
||||||
import com.fasterxml.jackson.databind.JsonDeserializer
|
import com.fasterxml.jackson.databind.JsonDeserializer
|
||||||
import com.fasterxml.jackson.databind.JsonNode
|
import com.fasterxml.jackson.databind.JsonNode
|
||||||
import dev.usbharu.hideout.service.activitypub.ActivityType
|
import dev.usbharu.hideout.service.activitypub.ActivityVocabulary
|
||||||
|
|
||||||
class ObjectDeserializer : JsonDeserializer<Object>() {
|
class ObjectDeserializer : JsonDeserializer<Object>() {
|
||||||
override fun deserialize(p: JsonParser?, ctxt: DeserializationContext?): Object {
|
override fun deserialize(p: JsonParser?, ctxt: DeserializationContext?): Object {
|
||||||
|
@ -22,21 +22,25 @@ class ObjectDeserializer : JsonDeserializer<Object>() {
|
||||||
val type = treeNode["type"]
|
val type = treeNode["type"]
|
||||||
val activityType = if (type.isArray) {
|
val activityType = if (type.isArray) {
|
||||||
type.firstNotNullOf { jsonNode: JsonNode ->
|
type.firstNotNullOf { jsonNode: JsonNode ->
|
||||||
ActivityType.values().firstOrNull { it.name.equals(jsonNode.asText(), true) }
|
ActivityVocabulary.values().firstOrNull { it.name.equals(jsonNode.asText(), true) }
|
||||||
}
|
}
|
||||||
} else if (type.isValueNode) {
|
} else if (type.isValueNode) {
|
||||||
ActivityType.values().first { it.name.equals(type.asText(), true) }
|
ActivityVocabulary.values().first { it.name.equals(type.asText(), true) }
|
||||||
} else {
|
} else {
|
||||||
TODO()
|
TODO()
|
||||||
}
|
}
|
||||||
|
|
||||||
return when (activityType) {
|
return when (activityType) {
|
||||||
ActivityType.Follow -> {
|
ActivityVocabulary.Follow -> {
|
||||||
val readValue = p.codec.treeToValue(treeNode, Follow::class.java)
|
val readValue = p.codec.treeToValue(treeNode, Follow::class.java)
|
||||||
println(readValue)
|
println(readValue)
|
||||||
readValue
|
readValue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ActivityVocabulary.Note -> {
|
||||||
|
p.codec.treeToValue(treeNode, Note::class.java)
|
||||||
|
}
|
||||||
|
|
||||||
else -> {
|
else -> {
|
||||||
TODO()
|
TODO()
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,7 +5,7 @@ open class Person : Object {
|
||||||
var summary: String? = null
|
var summary: String? = null
|
||||||
var inbox: String? = null
|
var inbox: String? = null
|
||||||
var outbox: String? = null
|
var outbox: String? = null
|
||||||
private var url: String? = null
|
var url: String? = null
|
||||||
private var icon: Image? = null
|
private var icon: Image? = null
|
||||||
var publicKey: Key? = null
|
var publicKey: Key? = null
|
||||||
|
|
||||||
|
|
|
@ -9,5 +9,7 @@ data class Post(
|
||||||
val visibility: Visibility,
|
val visibility: Visibility,
|
||||||
val url: String,
|
val url: String,
|
||||||
val repostId: Long? = null,
|
val repostId: Long? = null,
|
||||||
val replyId: Long? = null
|
val replyId: Long? = null,
|
||||||
|
val sensitive: Boolean = false,
|
||||||
|
val apId: String = url
|
||||||
)
|
)
|
||||||
|
|
|
@ -17,6 +17,8 @@ data class User(
|
||||||
val createdAt: Instant
|
val createdAt: Instant
|
||||||
) {
|
) {
|
||||||
override fun toString(): String {
|
override fun toString(): String {
|
||||||
return "User(id=$id, name='$name', domain='$domain', screenName='$screenName', description='$description', password=****, inbox='$inbox', outbox='$outbox', url='$url', publicKey='$publicKey', privateKey=****, createdAt=$createdAt)"
|
return "User(id=$id, name='$name', domain='$domain', screenName='$screenName', description='$description'," +
|
||||||
|
" password=****, inbox='$inbox', outbox='$outbox', url='$url', publicKey='$publicKey'," +
|
||||||
|
" privateKey=****, createdAt=$createdAt)"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,6 +5,7 @@ import dev.usbharu.hideout.domain.model.hideout.entity.Post
|
||||||
interface IPostRepository {
|
interface IPostRepository {
|
||||||
suspend fun generateId(): Long
|
suspend fun generateId(): Long
|
||||||
suspend fun save(post: Post): Post
|
suspend fun save(post: Post): Post
|
||||||
suspend fun findOneById(id: Long): Post
|
suspend fun findOneById(id: Long): Post?
|
||||||
|
suspend fun findByUrl(url: String): Post?
|
||||||
suspend fun delete(id: Long)
|
suspend fun delete(id: Long)
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,6 +16,7 @@ class PostRepositoryImpl(database: Database, private val idGenerateService: IdGe
|
||||||
init {
|
init {
|
||||||
transaction(database) {
|
transaction(database) {
|
||||||
SchemaUtils.create(Posts)
|
SchemaUtils.create(Posts)
|
||||||
|
SchemaUtils.createMissingTablesAndColumns(Posts)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -37,14 +38,22 @@ class PostRepositoryImpl(database: Database, private val idGenerateService: IdGe
|
||||||
it[url] = post.url
|
it[url] = post.url
|
||||||
it[repostId] = post.repostId
|
it[repostId] = post.repostId
|
||||||
it[replyId] = post.replyId
|
it[replyId] = post.replyId
|
||||||
|
it[sensitive] = post.sensitive
|
||||||
|
it[apId] = post.apId
|
||||||
}
|
}
|
||||||
return@query post
|
return@query post
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun findOneById(id: Long): Post {
|
override suspend fun findOneById(id: Long): Post? {
|
||||||
return query {
|
return query {
|
||||||
Posts.select { Posts.id eq id }.single().toPost()
|
Posts.select { Posts.id eq id }.singleOrNull()?.toPost()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun findByUrl(url: String): Post? {
|
||||||
|
return query {
|
||||||
|
Posts.select { Posts.url eq url }.singleOrNull()?.toPost()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -65,6 +74,8 @@ object Posts : Table() {
|
||||||
val url = varchar("url", 500)
|
val url = varchar("url", 500)
|
||||||
val repostId = long("repostId").references(id).nullable()
|
val repostId = long("repostId").references(id).nullable()
|
||||||
val replyId = long("replyId").references(id).nullable()
|
val replyId = long("replyId").references(id).nullable()
|
||||||
|
val sensitive = bool("sensitive").default(false)
|
||||||
|
val apId = varchar("ap_id", 100).uniqueIndex()
|
||||||
override val primaryKey: PrimaryKey = PrimaryKey(id)
|
override val primaryKey: PrimaryKey = PrimaryKey(id)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -78,6 +89,8 @@ fun ResultRow.toPost(): Post {
|
||||||
visibility = Visibility.values().first { visibility -> visibility.ordinal == this[Posts.visibility] },
|
visibility = Visibility.values().first { visibility -> visibility.ordinal == this[Posts.visibility] },
|
||||||
url = this[Posts.url],
|
url = this[Posts.url],
|
||||||
repostId = this[Posts.repostId],
|
repostId = this[Posts.repostId],
|
||||||
replyId = this[Posts.replyId]
|
replyId = this[Posts.replyId],
|
||||||
|
sensitive = this[Posts.sensitive],
|
||||||
|
apId = this[Posts.apId]
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,8 @@
|
||||||
|
package dev.usbharu.hideout.service.activitypub
|
||||||
|
|
||||||
|
import dev.usbharu.hideout.domain.model.ActivityPubResponse
|
||||||
|
import dev.usbharu.hideout.domain.model.ap.Create
|
||||||
|
|
||||||
|
interface ActivityPubCreateService {
|
||||||
|
suspend fun receiveCreate(create: Create): ActivityPubResponse
|
||||||
|
}
|
|
@ -0,0 +1,25 @@
|
||||||
|
package dev.usbharu.hideout.service.activitypub
|
||||||
|
|
||||||
|
import dev.usbharu.hideout.domain.model.ActivityPubResponse
|
||||||
|
import dev.usbharu.hideout.domain.model.ActivityPubStringResponse
|
||||||
|
import dev.usbharu.hideout.domain.model.ap.Create
|
||||||
|
import dev.usbharu.hideout.domain.model.ap.Note
|
||||||
|
import dev.usbharu.hideout.exception.ap.IllegalActivityPubObjectException
|
||||||
|
import io.ktor.http.*
|
||||||
|
import org.koin.core.annotation.Single
|
||||||
|
|
||||||
|
@Single
|
||||||
|
class ActivityPubCreateServiceImpl(
|
||||||
|
private val activityPubNoteService: ActivityPubNoteService
|
||||||
|
) : ActivityPubCreateService {
|
||||||
|
override suspend fun receiveCreate(create: Create): ActivityPubResponse {
|
||||||
|
val value = create.`object` ?: throw IllegalActivityPubObjectException("object is null")
|
||||||
|
if (value.type.contains("Note").not()) {
|
||||||
|
throw IllegalActivityPubObjectException("object is not Note")
|
||||||
|
}
|
||||||
|
|
||||||
|
val note = value as Note
|
||||||
|
activityPubNoteService.fetchNote(note)
|
||||||
|
return ActivityPubStringResponse(HttpStatusCode.OK, "Created")
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,5 +1,6 @@
|
||||||
package dev.usbharu.hideout.service.activitypub
|
package dev.usbharu.hideout.service.activitypub
|
||||||
|
|
||||||
|
import dev.usbharu.hideout.domain.model.ap.Note
|
||||||
import dev.usbharu.hideout.domain.model.hideout.entity.Post
|
import dev.usbharu.hideout.domain.model.hideout.entity.Post
|
||||||
import dev.usbharu.hideout.domain.model.job.DeliverPostJob
|
import dev.usbharu.hideout.domain.model.job.DeliverPostJob
|
||||||
import kjob.core.job.JobProps
|
import kjob.core.job.JobProps
|
||||||
|
@ -8,4 +9,7 @@ interface ActivityPubNoteService {
|
||||||
|
|
||||||
suspend fun createNote(post: Post)
|
suspend fun createNote(post: Post)
|
||||||
suspend fun createNoteJob(props: JobProps<DeliverPostJob>)
|
suspend fun createNoteJob(props: JobProps<DeliverPostJob>)
|
||||||
|
|
||||||
|
suspend fun fetchNote(url: String, targetActor: String? = null): Note
|
||||||
|
suspend fun fetchNote(note: Note, targetActor: String? = null): Note
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,11 +5,16 @@ import dev.usbharu.hideout.config.Config
|
||||||
import dev.usbharu.hideout.domain.model.ap.Create
|
import dev.usbharu.hideout.domain.model.ap.Create
|
||||||
import dev.usbharu.hideout.domain.model.ap.Note
|
import dev.usbharu.hideout.domain.model.ap.Note
|
||||||
import dev.usbharu.hideout.domain.model.hideout.entity.Post
|
import dev.usbharu.hideout.domain.model.hideout.entity.Post
|
||||||
|
import dev.usbharu.hideout.domain.model.hideout.entity.Visibility
|
||||||
import dev.usbharu.hideout.domain.model.job.DeliverPostJob
|
import dev.usbharu.hideout.domain.model.job.DeliverPostJob
|
||||||
|
import dev.usbharu.hideout.exception.ap.IllegalActivityPubObjectException
|
||||||
|
import dev.usbharu.hideout.plugins.getAp
|
||||||
import dev.usbharu.hideout.plugins.postAp
|
import dev.usbharu.hideout.plugins.postAp
|
||||||
|
import dev.usbharu.hideout.repository.IPostRepository
|
||||||
import dev.usbharu.hideout.service.impl.IUserService
|
import dev.usbharu.hideout.service.impl.IUserService
|
||||||
import dev.usbharu.hideout.service.job.JobQueueParentService
|
import dev.usbharu.hideout.service.job.JobQueueParentService
|
||||||
import io.ktor.client.*
|
import io.ktor.client.*
|
||||||
|
import io.ktor.client.call.*
|
||||||
import kjob.core.job.JobProps
|
import kjob.core.job.JobProps
|
||||||
import org.koin.core.annotation.Single
|
import org.koin.core.annotation.Single
|
||||||
import org.slf4j.LoggerFactory
|
import org.slf4j.LoggerFactory
|
||||||
|
@ -19,7 +24,9 @@ import java.time.Instant
|
||||||
class ActivityPubNoteServiceImpl(
|
class ActivityPubNoteServiceImpl(
|
||||||
private val httpClient: HttpClient,
|
private val httpClient: HttpClient,
|
||||||
private val jobQueueParentService: JobQueueParentService,
|
private val jobQueueParentService: JobQueueParentService,
|
||||||
private val userService: IUserService
|
private val userService: IUserService,
|
||||||
|
private val postRepository: IPostRepository,
|
||||||
|
private val activityPubUserService: ActivityPubUserService
|
||||||
) : ActivityPubNoteService {
|
) : ActivityPubNoteService {
|
||||||
|
|
||||||
private val logger = LoggerFactory.getLogger(this::class.java)
|
private val logger = LoggerFactory.getLogger(this::class.java)
|
||||||
|
@ -46,7 +53,7 @@ class ActivityPubNoteServiceImpl(
|
||||||
attributedTo = actor,
|
attributedTo = actor,
|
||||||
content = postEntity.text,
|
content = postEntity.text,
|
||||||
published = Instant.ofEpochMilli(postEntity.createdAt).toString(),
|
published = Instant.ofEpochMilli(postEntity.createdAt).toString(),
|
||||||
to = listOf("https://www.w3.org/ns/activitystreams#Public", actor + "/follower")
|
to = listOf(public, actor + "/follower")
|
||||||
)
|
)
|
||||||
val inbox = props[DeliverPostJob.inbox]
|
val inbox = props[DeliverPostJob.inbox]
|
||||||
logger.debug("createNoteJob: actor={}, note={}, inbox={}", actor, postEntity, inbox)
|
logger.debug("createNoteJob: actor={}, note={}, inbox={}", actor, postEntity, inbox)
|
||||||
|
@ -61,4 +68,82 @@ class ActivityPubNoteServiceImpl(
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override suspend fun fetchNote(url: String, targetActor: String?): Note {
|
||||||
|
val post = postRepository.findByUrl(url)
|
||||||
|
if (post != null) {
|
||||||
|
val user = userService.findById(post.userId)
|
||||||
|
val reply = post.replyId?.let { postRepository.findOneById(it) }
|
||||||
|
return Note(
|
||||||
|
name = "Post",
|
||||||
|
id = post.apId,
|
||||||
|
attributedTo = user.url,
|
||||||
|
content = post.text,
|
||||||
|
published = Instant.ofEpochMilli(post.createdAt).toString(),
|
||||||
|
to = listOf(public, user.url + "/follower"),
|
||||||
|
sensitive = post.sensitive,
|
||||||
|
cc = listOf(public, user.url + "/follower"),
|
||||||
|
inReplyTo = reply?.url
|
||||||
|
)
|
||||||
|
}
|
||||||
|
val response = httpClient.getAp(
|
||||||
|
url,
|
||||||
|
"$targetActor#pubkey"
|
||||||
|
)
|
||||||
|
val note = response.body<Note>()
|
||||||
|
return note(note, targetActor, url)
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun ActivityPubNoteServiceImpl.note(
|
||||||
|
note: Note,
|
||||||
|
targetActor: String?,
|
||||||
|
url: String
|
||||||
|
): Note {
|
||||||
|
val person = activityPubUserService.fetchPerson(
|
||||||
|
note.attributedTo ?: throw IllegalActivityPubObjectException("note.attributedTo is null"),
|
||||||
|
targetActor
|
||||||
|
)
|
||||||
|
val user =
|
||||||
|
userService.findByUrl(person.url ?: throw IllegalActivityPubObjectException("person.url is null"))
|
||||||
|
|
||||||
|
val visibility =
|
||||||
|
if (note.to.contains(public) && note.cc.contains(public)) {
|
||||||
|
Visibility.PUBLIC
|
||||||
|
} else if (note.to.find { it.endsWith("/followers") } != null && note.cc.contains(public)) {
|
||||||
|
Visibility.UNLISTED
|
||||||
|
} else if (note.to.find { it.endsWith("/followers") } != null) {
|
||||||
|
Visibility.FOLLOWERS
|
||||||
|
} else {
|
||||||
|
Visibility.DIRECT
|
||||||
|
}
|
||||||
|
|
||||||
|
val reply = note.inReplyTo?.let {
|
||||||
|
fetchNote(it, targetActor)
|
||||||
|
postRepository.findByUrl(it)
|
||||||
|
}
|
||||||
|
|
||||||
|
postRepository.save(
|
||||||
|
Post(
|
||||||
|
id = postRepository.generateId(),
|
||||||
|
userId = user.id,
|
||||||
|
overview = null,
|
||||||
|
text = note.content.orEmpty(),
|
||||||
|
createdAt = Instant.parse(note.published).toEpochMilli(),
|
||||||
|
visibility = visibility,
|
||||||
|
url = note.id ?: url,
|
||||||
|
repostId = null,
|
||||||
|
replyId = reply?.id,
|
||||||
|
sensitive = note.sensitive,
|
||||||
|
apId = note.id ?: url,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
return note
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun fetchNote(note: Note, targetActor: String?): Note =
|
||||||
|
note(note, targetActor, note.id ?: throw IllegalArgumentException("note.id is null"))
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
val public: String = "https://www.w3.org/ns/activitystreams#Public"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -43,3 +43,60 @@ enum class ActivityType {
|
||||||
View,
|
View,
|
||||||
Other
|
Other
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum class ActivityVocabulary {
|
||||||
|
Object,
|
||||||
|
Link,
|
||||||
|
Activity,
|
||||||
|
IntransitiveActivity,
|
||||||
|
Collection,
|
||||||
|
OrderedCollection,
|
||||||
|
CollectionPage,
|
||||||
|
OrderedCollectionPage,
|
||||||
|
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,
|
||||||
|
Application,
|
||||||
|
Group,
|
||||||
|
Organization,
|
||||||
|
Person,
|
||||||
|
Service,
|
||||||
|
Article,
|
||||||
|
Audio,
|
||||||
|
Document,
|
||||||
|
Event,
|
||||||
|
Image,
|
||||||
|
Note,
|
||||||
|
Page,
|
||||||
|
Place,
|
||||||
|
Profile,
|
||||||
|
Relationship,
|
||||||
|
Tombstone,
|
||||||
|
Video,
|
||||||
|
Mention,
|
||||||
|
}
|
||||||
|
|
|
@ -20,7 +20,8 @@ class ActivityPubServiceImpl(
|
||||||
private val activityPubReceiveFollowService: ActivityPubReceiveFollowService,
|
private val activityPubReceiveFollowService: ActivityPubReceiveFollowService,
|
||||||
private val activityPubNoteService: ActivityPubNoteService,
|
private val activityPubNoteService: ActivityPubNoteService,
|
||||||
private val activityPubUndoService: ActivityPubUndoService,
|
private val activityPubUndoService: ActivityPubUndoService,
|
||||||
private val activityPubAcceptService: ActivityPubAcceptService
|
private val activityPubAcceptService: ActivityPubAcceptService,
|
||||||
|
private val activityPubCreateService: ActivityPubCreateService
|
||||||
) : ActivityPubService {
|
) : ActivityPubService {
|
||||||
|
|
||||||
val logger: Logger = LoggerFactory.getLogger(this::class.java)
|
val logger: Logger = LoggerFactory.getLogger(this::class.java)
|
||||||
|
@ -32,9 +33,9 @@ class ActivityPubServiceImpl(
|
||||||
}
|
}
|
||||||
val type = readTree["type"]
|
val type = readTree["type"]
|
||||||
if (type.isArray) {
|
if (type.isArray) {
|
||||||
return type.mapNotNull { jsonNode: JsonNode ->
|
return type.firstNotNullOf { jsonNode: JsonNode ->
|
||||||
ActivityType.values().firstOrNull { it.name.equals(jsonNode.asText(), true) }
|
ActivityType.values().firstOrNull { it.name.equals(jsonNode.asText(), true) }
|
||||||
}.first()
|
}
|
||||||
}
|
}
|
||||||
return ActivityType.values().first { it.name.equals(type.asText(), true) }
|
return ActivityType.values().first { it.name.equals(type.asText(), true) }
|
||||||
}
|
}
|
||||||
|
@ -51,6 +52,7 @@ class ActivityPubServiceImpl(
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
ActivityType.Create -> activityPubCreateService.receiveCreate(configData.objectMapper.readValue(json))
|
||||||
ActivityType.Undo -> activityPubUndoService.receiveUndo(configData.objectMapper.readValue(json))
|
ActivityType.Undo -> activityPubUndoService.receiveUndo(configData.objectMapper.readValue(json))
|
||||||
|
|
||||||
else -> {
|
else -> {
|
||||||
|
@ -62,7 +64,10 @@ class ActivityPubServiceImpl(
|
||||||
override suspend fun <T : HideoutJob> processActivity(job: JobContextWithProps<T>, hideoutJob: HideoutJob) {
|
override suspend fun <T : HideoutJob> processActivity(job: JobContextWithProps<T>, hideoutJob: HideoutJob) {
|
||||||
logger.debug("processActivity: ${hideoutJob.name}")
|
logger.debug("processActivity: ${hideoutJob.name}")
|
||||||
when (hideoutJob) {
|
when (hideoutJob) {
|
||||||
ReceiveFollowJob -> activityPubReceiveFollowService.receiveFollowJob(job.props as JobProps<ReceiveFollowJob>)
|
ReceiveFollowJob -> activityPubReceiveFollowService.receiveFollowJob(
|
||||||
|
job.props as JobProps<ReceiveFollowJob>
|
||||||
|
)
|
||||||
|
|
||||||
DeliverPostJob -> activityPubNoteService.createNoteJob(job.props as JobProps<DeliverPostJob>)
|
DeliverPostJob -> activityPubNoteService.createNoteJob(job.props as JobProps<DeliverPostJob>)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,6 +9,7 @@ import io.ktor.http.*
|
||||||
import org.koin.core.annotation.Single
|
import org.koin.core.annotation.Single
|
||||||
|
|
||||||
@Single
|
@Single
|
||||||
|
@Suppress("UnsafeCallOnNullableType")
|
||||||
class ActivityPubUndoServiceImpl(
|
class ActivityPubUndoServiceImpl(
|
||||||
private val userService: IUserService,
|
private val userService: IUserService,
|
||||||
private val activityPubUserService: ActivityPubUserService
|
private val activityPubUserService: ActivityPubUserService
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
<pattern>%d{YYYY-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
|
<pattern>%d{YYYY-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
|
||||||
</encoder>
|
</encoder>
|
||||||
</appender>
|
</appender>
|
||||||
<root level="DEBUG">
|
<root level="TRACE">
|
||||||
<appender-ref ref="STDOUT"/>
|
<appender-ref ref="STDOUT"/>
|
||||||
</root>
|
</root>
|
||||||
<logger name="org.eclipse.jetty" level="INFO"/>
|
<logger name="org.eclipse.jetty" level="INFO"/>
|
||||||
|
|
|
@ -30,7 +30,8 @@ class UndoTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun Undoをデシリアライズ出来る() {
|
fun Undoをデシリアライズ出来る() {
|
||||||
@Language("JSON") val json = """
|
@Language("JSON")
|
||||||
|
val json = """
|
||||||
{
|
{
|
||||||
"@context": [
|
"@context": [
|
||||||
"https://www.w3.org/ns/activitystreams",
|
"https://www.w3.org/ns/activitystreams",
|
||||||
|
|
|
@ -92,6 +92,18 @@ class ActivityPubKtTest {
|
||||||
TODO("Not yet implemented")
|
TODO("Not yet implemented")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override suspend fun addFollowRequest(id: Long, follower: Long) {
|
||||||
|
TODO("Not yet implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun deleteFollowRequest(id: Long, follower: Long) {
|
||||||
|
TODO("Not yet implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun findFollowRequestsById(id: Long, follower: Long): Boolean {
|
||||||
|
TODO("Not yet implemented")
|
||||||
|
}
|
||||||
|
|
||||||
override suspend fun nextId(): Long {
|
override suspend fun nextId(): Long {
|
||||||
TODO("Not yet implemented")
|
TODO("Not yet implemented")
|
||||||
}
|
}
|
||||||
|
|
|
@ -88,6 +88,18 @@ class KtorKeyMapTest {
|
||||||
TODO("Not yet implemented")
|
TODO("Not yet implemented")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override suspend fun addFollowRequest(id: Long, follower: Long) {
|
||||||
|
TODO("Not yet implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun deleteFollowRequest(id: Long, follower: Long) {
|
||||||
|
TODO("Not yet implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun findFollowRequestsById(id: Long, follower: Long): Boolean {
|
||||||
|
TODO("Not yet implemented")
|
||||||
|
}
|
||||||
|
|
||||||
override suspend fun nextId(): Long {
|
override suspend fun nextId(): Long {
|
||||||
TODO("Not yet implemented")
|
TODO("Not yet implemented")
|
||||||
}
|
}
|
||||||
|
|
|
@ -41,11 +41,14 @@ class JwtRefreshTokenRepositoryImplTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `save 存在しない場合はinsertする`() = runTest {
|
fun `save 存在しない場合はinsertする`() = runTest {
|
||||||
val repository = JwtRefreshTokenRepositoryImpl(db, object : IdGenerateService {
|
val repository = JwtRefreshTokenRepositoryImpl(
|
||||||
|
db,
|
||||||
|
object : IdGenerateService {
|
||||||
override suspend fun generateId(): Long {
|
override suspend fun generateId(): Long {
|
||||||
TODO("Not yet implemented")
|
TODO("Not yet implemented")
|
||||||
}
|
}
|
||||||
})
|
}
|
||||||
|
)
|
||||||
val now = Instant.now(Clock.tickMillis(ZoneId.systemDefault()))
|
val now = Instant.now(Clock.tickMillis(ZoneId.systemDefault()))
|
||||||
val expiresAt = now.plus(10, ChronoUnit.MINUTES)
|
val expiresAt = now.plus(10, ChronoUnit.MINUTES)
|
||||||
|
|
||||||
|
@ -57,11 +60,14 @@ class JwtRefreshTokenRepositoryImplTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `save 存在する場合はupdateする`() = runTest {
|
fun `save 存在する場合はupdateする`() = runTest {
|
||||||
val repository = JwtRefreshTokenRepositoryImpl(db, object : IdGenerateService {
|
val repository = JwtRefreshTokenRepositoryImpl(
|
||||||
|
db,
|
||||||
|
object : IdGenerateService {
|
||||||
override suspend fun generateId(): Long {
|
override suspend fun generateId(): Long {
|
||||||
TODO("Not yet implemented")
|
TODO("Not yet implemented")
|
||||||
}
|
}
|
||||||
})
|
}
|
||||||
|
)
|
||||||
transaction {
|
transaction {
|
||||||
JwtRefreshTokens.insert {
|
JwtRefreshTokens.insert {
|
||||||
it[id] = 1L
|
it[id] = 1L
|
||||||
|
|
|
@ -28,6 +28,7 @@ class UserRepositoryTest {
|
||||||
transaction(db) {
|
transaction(db) {
|
||||||
SchemaUtils.create(Users)
|
SchemaUtils.create(Users)
|
||||||
SchemaUtils.create(UsersFollowers)
|
SchemaUtils.create(UsersFollowers)
|
||||||
|
SchemaUtils.create(FollowRequests)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -35,6 +36,7 @@ class UserRepositoryTest {
|
||||||
fun tearDown() {
|
fun tearDown() {
|
||||||
transaction(db) {
|
transaction(db) {
|
||||||
SchemaUtils.drop(UsersFollowers)
|
SchemaUtils.drop(UsersFollowers)
|
||||||
|
SchemaUtils.drop(FollowRequests)
|
||||||
SchemaUtils.drop(Users)
|
SchemaUtils.drop(Users)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -96,9 +98,7 @@ class UserRepositoryTest {
|
||||||
)
|
)
|
||||||
userRepository.createFollower(user.id, follower.id)
|
userRepository.createFollower(user.id, follower.id)
|
||||||
userRepository.createFollower(user.id, follower2.id)
|
userRepository.createFollower(user.id, follower2.id)
|
||||||
userRepository.findFollowersById(user.id).let {
|
assertIterableEquals(listOf(follower, follower2), userRepository.findFollowersById(user.id))
|
||||||
assertIterableEquals(listOf(follower, follower2), it)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|
|
@ -158,8 +158,12 @@ class PostsTest {
|
||||||
config = ApplicationConfig("empty.conf")
|
config = ApplicationConfig("empty.conf")
|
||||||
}
|
}
|
||||||
val post = Post(
|
val post = Post(
|
||||||
12345, 1234, text = "aaa", visibility = Visibility.PUBLIC,
|
12345,
|
||||||
createdAt = Instant.now().toEpochMilli(), url = "https://example.com/posts/1"
|
1234,
|
||||||
|
text = "aaa",
|
||||||
|
visibility = Visibility.PUBLIC,
|
||||||
|
createdAt = Instant.now().toEpochMilli(),
|
||||||
|
url = "https://example.com/posts/1"
|
||||||
)
|
)
|
||||||
val postService = mock<IPostService> {
|
val postService = mock<IPostService> {
|
||||||
onBlocking { findByIdForUser(any(), anyOrNull()) } doReturn post
|
onBlocking { findByIdForUser(any(), anyOrNull()) } doReturn post
|
||||||
|
@ -185,8 +189,12 @@ class PostsTest {
|
||||||
config = ApplicationConfig("empty.conf")
|
config = ApplicationConfig("empty.conf")
|
||||||
}
|
}
|
||||||
val post = Post(
|
val post = Post(
|
||||||
12345, 1234, text = "aaa", visibility = Visibility.FOLLOWERS,
|
12345,
|
||||||
createdAt = Instant.now().toEpochMilli(), url = "https://example.com/posts/1"
|
1234,
|
||||||
|
text = "aaa",
|
||||||
|
visibility = Visibility.FOLLOWERS,
|
||||||
|
createdAt = Instant.now().toEpochMilli(),
|
||||||
|
url = "https://example.com/posts/1"
|
||||||
)
|
)
|
||||||
val postService = mock<IPostService> {
|
val postService = mock<IPostService> {
|
||||||
onBlocking { findByIdForUser(any(), isNotNull()) } doReturn post
|
onBlocking { findByIdForUser(any(), isNotNull()) } doReturn post
|
||||||
|
@ -247,7 +255,6 @@ class PostsTest {
|
||||||
}
|
}
|
||||||
application {
|
application {
|
||||||
authentication {
|
authentication {
|
||||||
|
|
||||||
bearer(TOKEN_AUTH) {
|
bearer(TOKEN_AUTH) {
|
||||||
authenticate {
|
authenticate {
|
||||||
println("aaaaaaaaaaaa")
|
println("aaaaaaaaaaaa")
|
||||||
|
|
|
@ -290,7 +290,8 @@ class UsersTest {
|
||||||
"test User",
|
"test User",
|
||||||
"https://example.com/test",
|
"https://example.com/test",
|
||||||
Instant.now().toEpochMilli()
|
Instant.now().toEpochMilli()
|
||||||
), UserResponse(
|
),
|
||||||
|
UserResponse(
|
||||||
1236,
|
1236,
|
||||||
"follower2",
|
"follower2",
|
||||||
"example.com",
|
"example.com",
|
||||||
|
@ -334,7 +335,8 @@ class UsersTest {
|
||||||
"test User",
|
"test User",
|
||||||
"https://example.com/test",
|
"https://example.com/test",
|
||||||
Instant.now().toEpochMilli()
|
Instant.now().toEpochMilli()
|
||||||
), UserResponse(
|
),
|
||||||
|
UserResponse(
|
||||||
1236,
|
1236,
|
||||||
"follower2",
|
"follower2",
|
||||||
"example.com",
|
"example.com",
|
||||||
|
@ -378,7 +380,8 @@ class UsersTest {
|
||||||
"test User",
|
"test User",
|
||||||
"https://example.com/test",
|
"https://example.com/test",
|
||||||
Instant.now().toEpochMilli()
|
Instant.now().toEpochMilli()
|
||||||
), UserResponse(
|
),
|
||||||
|
UserResponse(
|
||||||
1236,
|
1236,
|
||||||
"follower2",
|
"follower2",
|
||||||
"example.com",
|
"example.com",
|
||||||
|
@ -572,7 +575,8 @@ class UsersTest {
|
||||||
"test User",
|
"test User",
|
||||||
"https://example.com/test",
|
"https://example.com/test",
|
||||||
Instant.now().toEpochMilli()
|
Instant.now().toEpochMilli()
|
||||||
), UserResponse(
|
),
|
||||||
|
UserResponse(
|
||||||
1236,
|
1236,
|
||||||
"follower2",
|
"follower2",
|
||||||
"example.com",
|
"example.com",
|
||||||
|
@ -616,7 +620,8 @@ class UsersTest {
|
||||||
"test User",
|
"test User",
|
||||||
"https://example.com/test",
|
"https://example.com/test",
|
||||||
Instant.now().toEpochMilli()
|
Instant.now().toEpochMilli()
|
||||||
), UserResponse(
|
),
|
||||||
|
UserResponse(
|
||||||
1236,
|
1236,
|
||||||
"follower2",
|
"follower2",
|
||||||
"example.com",
|
"example.com",
|
||||||
|
@ -660,7 +665,8 @@ class UsersTest {
|
||||||
"test User",
|
"test User",
|
||||||
"https://example.com/test",
|
"https://example.com/test",
|
||||||
Instant.now().toEpochMilli()
|
Instant.now().toEpochMilli()
|
||||||
), UserResponse(
|
),
|
||||||
|
UserResponse(
|
||||||
1236,
|
1236,
|
||||||
"follower2",
|
"follower2",
|
||||||
"example.com",
|
"example.com",
|
||||||
|
|
|
@ -37,7 +37,6 @@ class MetaServiceImplTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `updateMeta メタデータを保存できる`() = runTest {
|
fun `updateMeta メタデータを保存できる`() = runTest {
|
||||||
|
|
||||||
val meta = Meta("1.0.1", Jwt(UUID.randomUUID(), "sdfsdjk", "adafda"))
|
val meta = Meta("1.0.1", Jwt(UUID.randomUUID(), "sdfsdjk", "adafda"))
|
||||||
val metaRepository = mock<IMetaRepository> {
|
val metaRepository = mock<IMetaRepository> {
|
||||||
onBlocking { save(any()) } doReturn Unit
|
onBlocking { save(any()) } doReturn Unit
|
||||||
|
|
|
@ -72,7 +72,8 @@ class ActivityPubNoteServiceImplTest {
|
||||||
onBlocking { findFollowersById(eq(1L)) } doReturn followers
|
onBlocking { findFollowersById(eq(1L)) } doReturn followers
|
||||||
}
|
}
|
||||||
val jobQueueParentService = mock<JobQueueParentService>()
|
val jobQueueParentService = mock<JobQueueParentService>()
|
||||||
val activityPubNoteService = ActivityPubNoteServiceImpl(mock(), jobQueueParentService, userService)
|
val activityPubNoteService =
|
||||||
|
ActivityPubNoteServiceImpl(mock(), jobQueueParentService, userService, mock(), mock())
|
||||||
val postEntity = Post(
|
val postEntity = Post(
|
||||||
1L,
|
1L,
|
||||||
1L,
|
1L,
|
||||||
|
@ -95,7 +96,7 @@ class ActivityPubNoteServiceImplTest {
|
||||||
respondOk()
|
respondOk()
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
val activityPubNoteService = ActivityPubNoteServiceImpl(httpClient, mock(), mock())
|
val activityPubNoteService = ActivityPubNoteServiceImpl(httpClient, mock(), mock(), mock(), mock())
|
||||||
activityPubNoteService.createNoteJob(
|
activityPubNoteService.createNoteJob(
|
||||||
JobProps(
|
JobProps(
|
||||||
data = mapOf<String, Any>(
|
data = mapOf<String, Any>(
|
||||||
|
|
|
@ -53,7 +53,7 @@ class PostServiceTest {
|
||||||
text,
|
text,
|
||||||
Instant.now().toEpochMilli(),
|
Instant.now().toEpochMilli(),
|
||||||
visibility,
|
visibility,
|
||||||
"https://example.com"
|
"https://example.com${(userId.toString() + text).hashCode()}"
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -88,6 +88,8 @@ class PostServiceTest {
|
||||||
this[Posts.url] = it.url
|
this[Posts.url] = it.url
|
||||||
this[Posts.replyId] = it.replyId
|
this[Posts.replyId] = it.replyId
|
||||||
this[Posts.repostId] = it.repostId
|
this[Posts.repostId] = it.repostId
|
||||||
|
this[Posts.sensitive] = it.sensitive
|
||||||
|
this[Posts.apId] = it.apId
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -109,7 +111,7 @@ class PostServiceTest {
|
||||||
text,
|
text,
|
||||||
Instant.now().toEpochMilli(),
|
Instant.now().toEpochMilli(),
|
||||||
visibility,
|
visibility,
|
||||||
"https://example.com"
|
"https://example.com${(userId.toString() + text).hashCode()}"
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -148,6 +150,8 @@ class PostServiceTest {
|
||||||
this[Posts.url] = it.url
|
this[Posts.url] = it.url
|
||||||
this[Posts.replyId] = it.replyId
|
this[Posts.replyId] = it.replyId
|
||||||
this[Posts.repostId] = it.repostId
|
this[Posts.repostId] = it.repostId
|
||||||
|
this[Posts.sensitive] = it.sensitive
|
||||||
|
this[Posts.apId] = it.apId
|
||||||
}
|
}
|
||||||
UsersFollowers.insert {
|
UsersFollowers.insert {
|
||||||
it[id] = 100L
|
it[id] = 100L
|
||||||
|
|
Loading…
Reference in New Issue