feat: メディア付き投稿を配送可能に

This commit is contained in:
usbharu 2023-10-10 16:07:21 +09:00
parent db517cf288
commit 216ee78da0
7 changed files with 105 additions and 8 deletions

View File

@ -0,0 +1,48 @@
package dev.usbharu.hideout.domain.model.ap
class Document : Object {
var mediaType: String? = null
var url: String? = null
protected constructor() : super()
constructor(
type: List<String> = emptyList(),
name: String? = null,
mediaType: String,
url: String
) : super(
type = add(type, "Document"),
name = name,
actor = null,
id = null
) {
this.mediaType = mediaType
this.url = url
}
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other !is Document) return false
if (!super.equals(other)) return false
if (mediaType != other.mediaType) return false
if (url != other.url) return false
return true
}
override fun hashCode(): Int {
var result = super.hashCode()
result = 31 * result + (mediaType?.hashCode() ?: 0)
result = 31 * result + (url?.hashCode() ?: 0)
return result
}
override fun toString(): String {
return "Document(mediaType=$mediaType, url=$url) ${super.toString()}"
}
}

View File

@ -2,6 +2,7 @@ package dev.usbharu.hideout.domain.model.ap
open class Note : Object { open class Note : Object {
var attributedTo: String? = null var attributedTo: String? = null
var attachment: List<Document> = emptyList()
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()
@ -22,7 +23,8 @@ open class Note : Object {
to: List<String> = emptyList(), to: List<String> = emptyList(),
cc: List<String> = emptyList(), cc: List<String> = emptyList(),
sensitive: Boolean = false, sensitive: Boolean = false,
inReplyTo: String? = null inReplyTo: String? = null,
attachment: List<Document> = emptyList()
) : super( ) : super(
type = add(type, "Note"), type = add(type, "Note"),
name = name, name = name,
@ -35,30 +37,43 @@ open class Note : Object {
this.cc = cc this.cc = cc
this.sensitive = sensitive this.sensitive = sensitive
this.inReplyTo = inReplyTo this.inReplyTo = inReplyTo
this.attachment = attachment
} }
override fun equals(other: Any?): Boolean { override fun equals(other: Any?): Boolean {
if (this === other) return true if (this === other) return true
if (other !is Note) return false if (other !is Note) return false
if (!super.equals(other)) return false if (!super.equals(other)) return false
if (id != other.id) return false
if (attributedTo != other.attributedTo) return false if (attributedTo != other.attributedTo) return false
if (attachment != other.attachment) return false
if (content != other.content) return false if (content != other.content) return false
if (published != other.published) return false if (published != other.published) return false
return to == other.to if (to != other.to) return false
if (cc != other.cc) return false
if (sensitive != other.sensitive) return false
if (inReplyTo != other.inReplyTo) return false
return true
} }
override fun hashCode(): Int { override fun hashCode(): Int {
var result = super.hashCode() var result = super.hashCode()
result = 31 * result + (id?.hashCode() ?: 0)
result = 31 * result + (attributedTo?.hashCode() ?: 0) result = 31 * result + (attributedTo?.hashCode() ?: 0)
result = 31 * result + attachment.hashCode()
result = 31 * result + (content?.hashCode() ?: 0) result = 31 * result + (content?.hashCode() ?: 0)
result = 31 * result + (published?.hashCode() ?: 0) result = 31 * result + (published?.hashCode() ?: 0)
result = 31 * result + to.hashCode() result = 31 * result + to.hashCode()
result = 31 * result + cc.hashCode()
result = 31 * result + sensitive.hashCode()
result = 31 * result + (inReplyTo?.hashCode() ?: 0)
return result return result
} }
override fun toString(): String = override fun toString(): String {
"Note(id=$id, attributedTo=$attributedTo, content=$content, published=$published, to=$to) ${super.toString()}" return "Note(attributedTo=$attributedTo, attachment=$attachment, content=$content, published=$published, to=$to, cc=$cc, sensitive=$sensitive, inReplyTo=$inReplyTo) ${super.toString()}"
}
} }

View File

@ -84,7 +84,7 @@ class ObjectDeserializer : JsonDeserializer<Object>() {
ExtendedActivityVocabulary.Service -> TODO() ExtendedActivityVocabulary.Service -> TODO()
ExtendedActivityVocabulary.Article -> TODO() ExtendedActivityVocabulary.Article -> TODO()
ExtendedActivityVocabulary.Audio -> TODO() ExtendedActivityVocabulary.Audio -> TODO()
ExtendedActivityVocabulary.Document -> TODO() ExtendedActivityVocabulary.Document -> p.codec.treeToValue(treeNode, Document::class.java)
ExtendedActivityVocabulary.Event -> TODO() ExtendedActivityVocabulary.Event -> TODO()
ExtendedActivityVocabulary.Image -> p.codec.treeToValue(treeNode, Image::class.java) ExtendedActivityVocabulary.Image -> p.codec.treeToValue(treeNode, Image::class.java)
ExtendedActivityVocabulary.Page -> TODO() ExtendedActivityVocabulary.Page -> TODO()

View File

@ -18,6 +18,7 @@ object DeliverPostJob : HideoutJob("DeliverPostJob") {
val post: Prop<DeliverPostJob, String> = string("post") val post: Prop<DeliverPostJob, String> = string("post")
val actor: Prop<DeliverPostJob, String> = string("actor") val actor: Prop<DeliverPostJob, String> = string("actor")
val inbox: Prop<DeliverPostJob, String> = string("inbox") val inbox: Prop<DeliverPostJob, String> = string("inbox")
val media: Prop<DeliverPostJob, String> = string("media")
} }
@Component @Component

View File

@ -0,0 +1,7 @@
package dev.usbharu.hideout.query
import dev.usbharu.hideout.domain.model.hideout.entity.Media
interface MediaQueryService {
suspend fun findByPostId(postId: Long): List<Media>
}

View File

@ -0,0 +1,17 @@
package dev.usbharu.hideout.query
import dev.usbharu.hideout.domain.model.hideout.entity.Media
import dev.usbharu.hideout.repository.PostsMedia
import dev.usbharu.hideout.repository.toMedia
import org.jetbrains.exposed.sql.innerJoin
import org.jetbrains.exposed.sql.select
import org.springframework.stereotype.Repository
@Repository
class MediaQueryServiceImpl : MediaQueryService {
override suspend fun findByPostId(postId: Long): List<Media> {
return dev.usbharu.hideout.repository.Media.innerJoin(PostsMedia, onColumn = { id }, otherColumn = { mediaId })
.select { PostsMedia.postId eq postId }
.map { it.toMedia() }
}
}

View File

@ -4,6 +4,7 @@ import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.module.kotlin.readValue import com.fasterxml.jackson.module.kotlin.readValue
import dev.usbharu.hideout.config.ApplicationConfig import dev.usbharu.hideout.config.ApplicationConfig
import dev.usbharu.hideout.domain.model.ap.Create import dev.usbharu.hideout.domain.model.ap.Create
import dev.usbharu.hideout.domain.model.ap.Document
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.hideout.entity.Visibility
@ -13,6 +14,7 @@ import dev.usbharu.hideout.exception.ap.IllegalActivityPubObjectException
import dev.usbharu.hideout.plugins.getAp import dev.usbharu.hideout.plugins.getAp
import dev.usbharu.hideout.plugins.postAp import dev.usbharu.hideout.plugins.postAp
import dev.usbharu.hideout.query.FollowerQueryService import dev.usbharu.hideout.query.FollowerQueryService
import dev.usbharu.hideout.query.MediaQueryService
import dev.usbharu.hideout.query.PostQueryService import dev.usbharu.hideout.query.PostQueryService
import dev.usbharu.hideout.query.UserQueryService import dev.usbharu.hideout.query.UserQueryService
import dev.usbharu.hideout.repository.PostRepository import dev.usbharu.hideout.repository.PostRepository
@ -46,6 +48,7 @@ class APNoteServiceImpl(
private val userQueryService: UserQueryService, private val userQueryService: UserQueryService,
private val followerQueryService: FollowerQueryService, private val followerQueryService: FollowerQueryService,
private val postQueryService: PostQueryService, private val postQueryService: PostQueryService,
private val mediaQueryService: MediaQueryService,
@Qualifier("activitypub") private val objectMapper: ObjectMapper, @Qualifier("activitypub") private val objectMapper: ObjectMapper,
private val applicationConfig: ApplicationConfig, private val applicationConfig: ApplicationConfig,
private val postService: PostService private val postService: PostService
@ -62,11 +65,13 @@ class APNoteServiceImpl(
val followers = followerQueryService.findFollowersById(post.userId) val followers = followerQueryService.findFollowersById(post.userId)
val userEntity = userQueryService.findById(post.userId) val userEntity = userQueryService.findById(post.userId)
val note = objectMapper.writeValueAsString(post) val note = objectMapper.writeValueAsString(post)
val mediaList = objectMapper.writeValueAsString(mediaQueryService.findByPostId(post.id))
followers.forEach { followerEntity -> followers.forEach { followerEntity ->
jobQueueParentService.schedule(DeliverPostJob) { jobQueueParentService.schedule(DeliverPostJob) {
props[DeliverPostJob.actor] = userEntity.url props[DeliverPostJob.actor] = userEntity.url
props[DeliverPostJob.post] = note props[DeliverPostJob.post] = note
props[DeliverPostJob.inbox] = followerEntity.inbox props[DeliverPostJob.inbox] = followerEntity.inbox
props[DeliverPostJob.media] = mediaList
} }
} }
} }
@ -74,13 +79,17 @@ class APNoteServiceImpl(
override suspend fun createNoteJob(props: JobProps<DeliverPostJob>) { override suspend fun createNoteJob(props: JobProps<DeliverPostJob>) {
val actor = props[DeliverPostJob.actor] val actor = props[DeliverPostJob.actor]
val postEntity = objectMapper.readValue<Post>(props[DeliverPostJob.post]) val postEntity = objectMapper.readValue<Post>(props[DeliverPostJob.post])
val mediaList =
objectMapper.readValue<List<dev.usbharu.hideout.domain.model.hideout.entity.Media>>(props[DeliverPostJob.media])
val note = Note( val note = Note(
name = "Note", name = "Note",
id = postEntity.url, id = postEntity.url,
attributedTo = actor, attributedTo = actor,
content = postEntity.text, content = postEntity.text,
published = Instant.ofEpochMilli(postEntity.createdAt).toString(), published = Instant.ofEpochMilli(postEntity.createdAt).toString(),
to = listOf(public, "$actor/follower") to = listOf(public, "$actor/follower"),
attachment = mediaList.map { Document(mediaType = "image/jpeg", url = it.url) }
) )
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)