diff --git a/src/main/kotlin/dev/usbharu/hideout/Application.kt b/src/main/kotlin/dev/usbharu/hideout/Application.kt index 254d7a59..aec8f304 100644 --- a/src/main/kotlin/dev/usbharu/hideout/Application.kt +++ b/src/main/kotlin/dev/usbharu/hideout/Application.kt @@ -78,6 +78,7 @@ fun Application.parent() { install(httpSignaturePlugin) { keyMap = KtorKeyMap(get()) } + expectSuccess = true } } single { TwitterSnowflakeIdGenerateService } diff --git a/src/main/kotlin/dev/usbharu/hideout/domain/model/ap/ObjectDeserializer.kt b/src/main/kotlin/dev/usbharu/hideout/domain/model/ap/ObjectDeserializer.kt index 4a4db457..ba2f9868 100644 --- a/src/main/kotlin/dev/usbharu/hideout/domain/model/ap/ObjectDeserializer.kt +++ b/src/main/kotlin/dev/usbharu/hideout/domain/model/ap/ObjectDeserializer.kt @@ -4,7 +4,7 @@ import com.fasterxml.jackson.core.JsonParser import com.fasterxml.jackson.databind.DeserializationContext import com.fasterxml.jackson.databind.JsonDeserializer import com.fasterxml.jackson.databind.JsonNode -import dev.usbharu.hideout.service.activitypub.ActivityVocabulary +import dev.usbharu.hideout.service.activitypub.ExtendedActivityVocabulary class ObjectDeserializer : JsonDeserializer() { override fun deserialize(p: JsonParser?, ctxt: DeserializationContext?): Object { @@ -22,28 +22,78 @@ class ObjectDeserializer : JsonDeserializer() { val type = treeNode["type"] val activityType = if (type.isArray) { type.firstNotNullOf { jsonNode: JsonNode -> - ActivityVocabulary.values().firstOrNull { it.name.equals(jsonNode.asText(), true) } + ExtendedActivityVocabulary.values().firstOrNull { it.name.equals(jsonNode.asText(), true) } } } else if (type.isValueNode) { - ActivityVocabulary.values().first { it.name.equals(type.asText(), true) } + ExtendedActivityVocabulary.values().first { it.name.equals(type.asText(), true) } } else { TODO() } return when (activityType) { - ActivityVocabulary.Follow -> { + ExtendedActivityVocabulary.Follow -> { val readValue = p.codec.treeToValue(treeNode, Follow::class.java) println(readValue) readValue } - ActivityVocabulary.Note -> { + ExtendedActivityVocabulary.Note -> { p.codec.treeToValue(treeNode, Note::class.java) } - else -> { - TODO("$activityType is not implementation") - } + ExtendedActivityVocabulary.Object -> p.codec.treeToValue(treeNode, Object::class.java) + ExtendedActivityVocabulary.Link -> TODO() + ExtendedActivityVocabulary.Activity -> TODO() + ExtendedActivityVocabulary.IntransitiveActivity -> TODO() + ExtendedActivityVocabulary.Collection -> TODO() + ExtendedActivityVocabulary.OrderedCollection -> TODO() + ExtendedActivityVocabulary.CollectionPage -> TODO() + ExtendedActivityVocabulary.OrderedCollectionPage -> TODO() + ExtendedActivityVocabulary.Accept -> p.codec.treeToValue(treeNode, Accept::class.java) + ExtendedActivityVocabulary.Add -> TODO() + ExtendedActivityVocabulary.Announce -> TODO() + ExtendedActivityVocabulary.Arrive -> TODO() + ExtendedActivityVocabulary.Block -> TODO() + ExtendedActivityVocabulary.Create -> p.codec.treeToValue(treeNode, Create::class.java) + ExtendedActivityVocabulary.Delete -> TODO() + ExtendedActivityVocabulary.Dislike -> TODO() + ExtendedActivityVocabulary.Flag -> TODO() + ExtendedActivityVocabulary.Ignore -> TODO() + ExtendedActivityVocabulary.Invite -> TODO() + ExtendedActivityVocabulary.Join -> TODO() + ExtendedActivityVocabulary.Leave -> TODO() + ExtendedActivityVocabulary.Like -> p.codec.treeToValue(treeNode, Like::class.java) + ExtendedActivityVocabulary.Listen -> TODO() + ExtendedActivityVocabulary.Move -> TODO() + ExtendedActivityVocabulary.Offer -> TODO() + ExtendedActivityVocabulary.Question -> TODO() + ExtendedActivityVocabulary.Reject -> TODO() + ExtendedActivityVocabulary.Read -> TODO() + ExtendedActivityVocabulary.Remove -> TODO() + ExtendedActivityVocabulary.TentativeReject -> TODO() + ExtendedActivityVocabulary.TentativeAccept -> TODO() + ExtendedActivityVocabulary.Travel -> TODO() + ExtendedActivityVocabulary.Undo -> p.codec.treeToValue(treeNode, Undo::class.java) + ExtendedActivityVocabulary.Update -> TODO() + ExtendedActivityVocabulary.View -> TODO() + ExtendedActivityVocabulary.Application -> TODO() + ExtendedActivityVocabulary.Group -> TODO() + ExtendedActivityVocabulary.Organization -> TODO() + ExtendedActivityVocabulary.Person -> p.codec.treeToValue(treeNode, Person::class.java) + ExtendedActivityVocabulary.Service -> TODO() + ExtendedActivityVocabulary.Article -> TODO() + ExtendedActivityVocabulary.Audio -> TODO() + ExtendedActivityVocabulary.Document -> TODO() + ExtendedActivityVocabulary.Event -> TODO() + ExtendedActivityVocabulary.Image -> p.codec.treeToValue(treeNode, Image::class.java) + ExtendedActivityVocabulary.Page -> TODO() + ExtendedActivityVocabulary.Place -> TODO() + ExtendedActivityVocabulary.Profile -> TODO() + ExtendedActivityVocabulary.Relationship -> TODO() + ExtendedActivityVocabulary.Tombstone -> TODO() + ExtendedActivityVocabulary.Video -> TODO() + ExtendedActivityVocabulary.Mention -> TODO() + ExtendedActivityVocabulary.Emoji -> p.codec.treeToValue(treeNode, Emoji::class.java) } } else { TODO() diff --git a/src/main/kotlin/dev/usbharu/hideout/plugins/ActivityPub.kt b/src/main/kotlin/dev/usbharu/hideout/plugins/ActivityPub.kt index f2eb9f85..cbaec3a5 100644 --- a/src/main/kotlin/dev/usbharu/hideout/plugins/ActivityPub.kt +++ b/src/main/kotlin/dev/usbharu/hideout/plugins/ActivityPub.kt @@ -44,10 +44,12 @@ suspend fun HttpClient.postAp(urlString: String, username: String, jsonLd: JsonL } } -suspend fun HttpClient.getAp(urlString: String, username: String): HttpResponse { +suspend fun HttpClient.getAp(urlString: String, username: String?): HttpResponse { return this.get(urlString) { header("Accept", ContentType.Application.Activity) - header("Signature", "keyId=\"$username\",algorithm=\"rsa-sha256\",headers=\"(request-target) host date\"") + username?.let { + header("Signature", "keyId=\"$username\",algorithm=\"rsa-sha256\",headers=\"(request-target) host date\"") + } } } diff --git a/src/main/kotlin/dev/usbharu/hideout/repository/PostRepositoryImpl.kt b/src/main/kotlin/dev/usbharu/hideout/repository/PostRepositoryImpl.kt index 859bbd66..a92889c1 100644 --- a/src/main/kotlin/dev/usbharu/hideout/repository/PostRepositoryImpl.kt +++ b/src/main/kotlin/dev/usbharu/hideout/repository/PostRepositoryImpl.kt @@ -63,7 +63,9 @@ class PostRepositoryImpl(database: Database, private val idGenerateService: IdGe } override suspend fun findOneById(id: Long, userId: Long?): Post? { - TODO("Not yet implemented") + return query { + Posts.select { Posts.id eq id }.singleOrNull()?.toPost() + } } override suspend fun findByUrl(url: String): Post? { diff --git a/src/main/kotlin/dev/usbharu/hideout/repository/ReactionRepository.kt b/src/main/kotlin/dev/usbharu/hideout/repository/ReactionRepository.kt index ec973ddf..f1c6a540 100644 --- a/src/main/kotlin/dev/usbharu/hideout/repository/ReactionRepository.kt +++ b/src/main/kotlin/dev/usbharu/hideout/repository/ReactionRepository.kt @@ -5,4 +5,5 @@ import dev.usbharu.hideout.domain.model.hideout.entity.Reaction interface ReactionRepository { suspend fun generateId(): Long suspend fun save(reaction: Reaction): Reaction + suspend fun reactionAlreadyExist(postId: Long, userId: Long, emojiId: Long): Boolean } diff --git a/src/main/kotlin/dev/usbharu/hideout/repository/ReactionRepositoryImpl.kt b/src/main/kotlin/dev/usbharu/hideout/repository/ReactionRepositoryImpl.kt index 7da36956..bb87ec37 100644 --- a/src/main/kotlin/dev/usbharu/hideout/repository/ReactionRepositoryImpl.kt +++ b/src/main/kotlin/dev/usbharu/hideout/repository/ReactionRepositoryImpl.kt @@ -33,19 +33,25 @@ class ReactionRepositoryImpl(private val database: Database, private val idGener it[id] = reaction.id it[emojiId] = reaction.emojiId it[postId] = reaction.postId - it[userId] = reaction.postId + it[userId] = reaction.userId } } else { Reactions.update({ Reactions.id eq reaction.id }) { it[emojiId] = reaction.emojiId it[postId] = reaction.postId - it[userId] = reaction.postId + it[userId] = reaction.userId } } } return reaction } + + override suspend fun reactionAlreadyExist(postId: Long, userId: Long, emojiId: Long): Boolean { + return query { + Reactions.select { Reactions.postId.eq(postId).and(Reactions.userId.eq(userId)).and(Reactions.emojiId.eq(emojiId)) }.empty().not() + } + } } fun ResultRow.toReaction(): Reaction { @@ -61,4 +67,8 @@ object Reactions : LongIdTable("reactions") { val emojiId = long("emoji_id") val postId = long("post_id").references(Posts.id) val userId = long("user_id").references(Users.id) + + init { + uniqueIndex(emojiId, postId, userId) + } } diff --git a/src/main/kotlin/dev/usbharu/hideout/service/activitypub/ActivityPubLikeServiceImpl.kt b/src/main/kotlin/dev/usbharu/hideout/service/activitypub/ActivityPubLikeServiceImpl.kt index 5c1ba744..8b006df2 100644 --- a/src/main/kotlin/dev/usbharu/hideout/service/activitypub/ActivityPubLikeServiceImpl.kt +++ b/src/main/kotlin/dev/usbharu/hideout/service/activitypub/ActivityPubLikeServiceImpl.kt @@ -16,12 +16,14 @@ import org.koin.core.annotation.Single class ActivityPubLikeServiceImpl(private val reactionService: IReactionService, private val activityPubUserService: ActivityPubUserService, private val userService: IUserService, - private val postService: IPostRepository) : ActivityPubLikeService { + private val postService: IPostRepository, + private val activityPubNoteService: ActivityPubNoteService) : ActivityPubLikeService { override suspend fun receiveLike(like: Like): ActivityPubResponse { val actor = like.actor ?: throw IllegalActivityPubObjectException("actor is null") val content = like.content ?: throw IllegalActivityPubObjectException("content is null") like.`object` ?: throw IllegalActivityPubObjectException("object is null") val person = activityPubUserService.fetchPerson(actor) + activityPubNoteService.fetchNote(like.`object`!!) val user = userService.findByUrl(person.url ?: throw IllegalActivityPubObjectException("actor is not found")) diff --git a/src/main/kotlin/dev/usbharu/hideout/service/activitypub/ActivityPubNoteServiceImpl.kt b/src/main/kotlin/dev/usbharu/hideout/service/activitypub/ActivityPubNoteServiceImpl.kt index 4ccd223f..117b207a 100644 --- a/src/main/kotlin/dev/usbharu/hideout/service/activitypub/ActivityPubNoteServiceImpl.kt +++ b/src/main/kotlin/dev/usbharu/hideout/service/activitypub/ActivityPubNoteServiceImpl.kt @@ -14,7 +14,7 @@ import dev.usbharu.hideout.repository.IPostRepository import dev.usbharu.hideout.service.job.JobQueueParentService import dev.usbharu.hideout.service.user.IUserService import io.ktor.client.* -import io.ktor.client.call.* +import io.ktor.client.statement.* import kjob.core.job.JobProps import org.koin.core.annotation.Single import org.slf4j.LoggerFactory @@ -75,10 +75,9 @@ class ActivityPubNoteServiceImpl( return postToNote(post) } val response = httpClient.getAp( - url, - "$targetActor#pubkey" + url, targetActor?.let { "$targetActor#pubkey" } ) - val note = response.body() + val note = Config.configData.objectMapper.readValue(response.bodyAsText()) return note(note, targetActor, url) } 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 f61e825d..bec7f2fb 100644 --- a/src/main/kotlin/dev/usbharu/hideout/service/activitypub/ActivityPubService.kt +++ b/src/main/kotlin/dev/usbharu/hideout/service/activitypub/ActivityPubService.kt @@ -100,3 +100,65 @@ enum class ActivityVocabulary { Video, Mention, } + +enum class ExtendedActivityVocabulary { + 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, + Emoji +} + +enum class ExtendedVocabulary { + Emoji +} diff --git a/src/main/kotlin/dev/usbharu/hideout/service/reaction/ReactionServiceImpl.kt b/src/main/kotlin/dev/usbharu/hideout/service/reaction/ReactionServiceImpl.kt index 1b89e5dc..89c039f1 100644 --- a/src/main/kotlin/dev/usbharu/hideout/service/reaction/ReactionServiceImpl.kt +++ b/src/main/kotlin/dev/usbharu/hideout/service/reaction/ReactionServiceImpl.kt @@ -7,12 +7,10 @@ import org.koin.core.annotation.Single @Single class ReactionServiceImpl(private val reactionRepository: ReactionRepository) : IReactionService { override suspend fun receiveReaction(name: String, domain: String, userId: Long, postId: Long) { - reactionRepository.save( - Reaction( - reactionRepository.generateId(), - 0, - postId, userId - ) - ) + if (reactionRepository.reactionAlreadyExist(postId, userId, 0).not()) { + reactionRepository.save( + Reaction(reactionRepository.generateId(), 0, postId, userId) + ) + } } }