diff --git a/src/main/kotlin/dev/usbharu/hideout/domain/model/ap/Emoji.kt b/src/main/kotlin/dev/usbharu/hideout/domain/model/ap/Emoji.kt new file mode 100644 index 00000000..8012b4fd --- /dev/null +++ b/src/main/kotlin/dev/usbharu/hideout/domain/model/ap/Emoji.kt @@ -0,0 +1,41 @@ +package dev.usbharu.hideout.domain.model.ap + +open class Emoji : Object { + var updated: String? = null + var icon: Image? = null + + protected constructor() : super() + constructor(type: List, + name: String?, + actor: String?, + id: String?, + updated: String?, + icon: Image?) : super( + type = add(type, "Emoji"), + name = name, + actor = actor, + id = id) { + this.updated = updated + this.icon = icon + } + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other !is Emoji) return false + if (!super.equals(other)) return false + + if (updated != other.updated) return false + return icon == other.icon + } + + override fun hashCode(): Int { + var result = super.hashCode() + result = 31 * result + (updated?.hashCode() ?: 0) + result = 31 * result + (icon?.hashCode() ?: 0) + return result + } + + override fun toString(): String = "Emoji(updated=$updated, icon=$icon) ${super.toString()}" + + +} diff --git a/src/main/kotlin/dev/usbharu/hideout/domain/model/ap/Like.kt b/src/main/kotlin/dev/usbharu/hideout/domain/model/ap/Like.kt new file mode 100644 index 00000000..68a1aa47 --- /dev/null +++ b/src/main/kotlin/dev/usbharu/hideout/domain/model/ap/Like.kt @@ -0,0 +1,51 @@ +package dev.usbharu.hideout.domain.model.ap + +import com.fasterxml.jackson.databind.annotation.JsonDeserialize + +open class Like : Object { + var `object`: String? = null + var content: String? = null + + @JsonDeserialize(contentUsing = ObjectDeserializer::class) + var tag: List = emptyList() + + protected constructor() : super() + constructor(type: List, + name: String?, + actor: String?, + id: String?, + `object`: String?, + content: String?, + tag: List + ) : super( + type = add(type, "Like"), + name = name, + actor = actor, + id = id) { + this.`object` = `object` + this.content = content + this.tag = tag + } + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other !is Like) return false + if (!super.equals(other)) return false + + if (`object` != other.`object`) return false + if (content != other.content) return false + return tag == other.tag + } + + override fun hashCode(): Int { + var result = super.hashCode() + result = 31 * result + (`object`?.hashCode() ?: 0) + result = 31 * result + (content?.hashCode() ?: 0) + result = 31 * result + tag.hashCode() + return result + } + + override fun toString(): String = "Like(`object`=$`object`, content=$content, tag=$tag) ${super.toString()}" + + +} 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 501682ce..4a4db457 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 @@ -42,7 +42,7 @@ class ObjectDeserializer : JsonDeserializer() { } else -> { - TODO() + TODO("$activityType is not implementation") } } } else { diff --git a/src/main/kotlin/dev/usbharu/hideout/domain/model/hideout/entity/Reaction.kt b/src/main/kotlin/dev/usbharu/hideout/domain/model/hideout/entity/Reaction.kt new file mode 100644 index 00000000..af943cf5 --- /dev/null +++ b/src/main/kotlin/dev/usbharu/hideout/domain/model/hideout/entity/Reaction.kt @@ -0,0 +1,3 @@ +package dev.usbharu.hideout.domain.model.hideout.entity + +data class Reaction(val id: Long, val emojiId: Long, val postId: Long, val userId: Long) diff --git a/src/main/kotlin/dev/usbharu/hideout/repository/ReactionRepository.kt b/src/main/kotlin/dev/usbharu/hideout/repository/ReactionRepository.kt new file mode 100644 index 00000000..ec973ddf --- /dev/null +++ b/src/main/kotlin/dev/usbharu/hideout/repository/ReactionRepository.kt @@ -0,0 +1,8 @@ +package dev.usbharu.hideout.repository + +import dev.usbharu.hideout.domain.model.hideout.entity.Reaction + +interface ReactionRepository { + suspend fun generateId(): Long + suspend fun save(reaction: Reaction): Reaction +} diff --git a/src/main/kotlin/dev/usbharu/hideout/repository/ReactionRepositoryImpl.kt b/src/main/kotlin/dev/usbharu/hideout/repository/ReactionRepositoryImpl.kt new file mode 100644 index 00000000..7da36956 --- /dev/null +++ b/src/main/kotlin/dev/usbharu/hideout/repository/ReactionRepositoryImpl.kt @@ -0,0 +1,64 @@ +package dev.usbharu.hideout.repository + +import dev.usbharu.hideout.domain.model.hideout.entity.Reaction +import dev.usbharu.hideout.service.core.IdGenerateService +import kotlinx.coroutines.Dispatchers +import org.jetbrains.exposed.dao.id.LongIdTable +import org.jetbrains.exposed.sql.* +import org.jetbrains.exposed.sql.transactions.experimental.newSuspendedTransaction +import org.jetbrains.exposed.sql.transactions.transaction +import org.koin.core.annotation.Single + +@Single +class ReactionRepositoryImpl(private val database: Database, private val idGenerateService: IdGenerateService) : ReactionRepository { + + init { + transaction(database) { + SchemaUtils.create(Reactions) + SchemaUtils.createMissingTablesAndColumns(Reactions) + } + } + + + @Suppress("InjectDispatcher") + suspend fun query(block: suspend () -> T): T = + newSuspendedTransaction(Dispatchers.IO) { block() } + + override suspend fun generateId(): Long = idGenerateService.generateId() + + override suspend fun save(reaction: Reaction): Reaction { + query { + if (Reactions.select { Reactions.id eq reaction.id }.empty()) { + Reactions.insert { + it[id] = reaction.id + it[emojiId] = reaction.emojiId + it[postId] = reaction.postId + it[userId] = reaction.postId + } + } else { + Reactions.update({ Reactions.id eq reaction.id }) { + it[emojiId] = reaction.emojiId + it[postId] = reaction.postId + it[userId] = reaction.postId + + } + } + } + return reaction + } +} + +fun ResultRow.toReaction(): Reaction { + return Reaction( + this[Reactions.id].value, + this[Reactions.emojiId], + this[Reactions.postId], + this[Reactions.userId] + ) +} + +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) +} diff --git a/src/main/kotlin/dev/usbharu/hideout/service/activitypub/ActivityPubLikeService.kt b/src/main/kotlin/dev/usbharu/hideout/service/activitypub/ActivityPubLikeService.kt new file mode 100644 index 00000000..19d3f341 --- /dev/null +++ b/src/main/kotlin/dev/usbharu/hideout/service/activitypub/ActivityPubLikeService.kt @@ -0,0 +1,8 @@ +package dev.usbharu.hideout.service.activitypub + +import dev.usbharu.hideout.domain.model.ActivityPubResponse +import dev.usbharu.hideout.domain.model.ap.Like + +interface ActivityPubLikeService { + suspend fun receiveLike(like: Like): ActivityPubResponse +} diff --git a/src/main/kotlin/dev/usbharu/hideout/service/activitypub/ActivityPubLikeServiceImpl.kt b/src/main/kotlin/dev/usbharu/hideout/service/activitypub/ActivityPubLikeServiceImpl.kt new file mode 100644 index 00000000..5c1ba744 --- /dev/null +++ b/src/main/kotlin/dev/usbharu/hideout/service/activitypub/ActivityPubLikeServiceImpl.kt @@ -0,0 +1,35 @@ +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.Like +import dev.usbharu.hideout.exception.PostNotFoundException +import dev.usbharu.hideout.exception.ap.IllegalActivityPubObjectException +import dev.usbharu.hideout.repository.IPostRepository +import dev.usbharu.hideout.service.reaction.IReactionService +import dev.usbharu.hideout.service.user.IUserService +import io.ktor.http.* +import org.koin.core.annotation.Single + + +@Single +class ActivityPubLikeServiceImpl(private val reactionService: IReactionService, + private val activityPubUserService: ActivityPubUserService, + private val userService: IUserService, + private val postService: IPostRepository) : 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) + + val user = userService.findByUrl(person.url + ?: throw IllegalActivityPubObjectException("actor is not found")) + + val post = postService.findByUrl(like.`object`!!) + ?: throw PostNotFoundException("${like.`object`} was not found") + + reactionService.receiveReaction(content, actor.substringAfter("://").substringBefore("/"), user.id, post.id) + return ActivityPubStringResponse(HttpStatusCode.OK, "") + } +} 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 058fbede..b153ad19 100644 --- a/src/main/kotlin/dev/usbharu/hideout/service/activitypub/ActivityPubServiceImpl.kt +++ b/src/main/kotlin/dev/usbharu/hideout/service/activitypub/ActivityPubServiceImpl.kt @@ -17,11 +17,12 @@ import org.slf4j.LoggerFactory @Single class ActivityPubServiceImpl( - private val activityPubReceiveFollowService: ActivityPubReceiveFollowService, - private val activityPubNoteService: ActivityPubNoteService, - private val activityPubUndoService: ActivityPubUndoService, - private val activityPubAcceptService: ActivityPubAcceptService, - private val activityPubCreateService: ActivityPubCreateService + private val activityPubReceiveFollowService: ActivityPubReceiveFollowService, + private val activityPubNoteService: ActivityPubNoteService, + private val activityPubUndoService: ActivityPubUndoService, + private val activityPubAcceptService: ActivityPubAcceptService, + private val activityPubCreateService: ActivityPubCreateService, + private val activityPubLikeService: ActivityPubLikeService ) : ActivityPubService { val logger: Logger = LoggerFactory.getLogger(this::class.java) @@ -53,6 +54,7 @@ class ActivityPubServiceImpl( ) ActivityType.Create -> activityPubCreateService.receiveCreate(configData.objectMapper.readValue(json)) + ActivityType.Like -> activityPubLikeService.receiveLike(configData.objectMapper.readValue(json)) ActivityType.Undo -> activityPubUndoService.receiveUndo(configData.objectMapper.readValue(json)) else -> { diff --git a/src/main/kotlin/dev/usbharu/hideout/service/reaction/IReactionService.kt b/src/main/kotlin/dev/usbharu/hideout/service/reaction/IReactionService.kt new file mode 100644 index 00000000..13c52f88 --- /dev/null +++ b/src/main/kotlin/dev/usbharu/hideout/service/reaction/IReactionService.kt @@ -0,0 +1,5 @@ +package dev.usbharu.hideout.service.reaction + +interface IReactionService { + suspend fun receiveReaction(name: String, domain: String, userId: Long, postId: Long) +} diff --git a/src/main/kotlin/dev/usbharu/hideout/service/reaction/ReactionServiceImpl.kt b/src/main/kotlin/dev/usbharu/hideout/service/reaction/ReactionServiceImpl.kt new file mode 100644 index 00000000..1b89e5dc --- /dev/null +++ b/src/main/kotlin/dev/usbharu/hideout/service/reaction/ReactionServiceImpl.kt @@ -0,0 +1,18 @@ +package dev.usbharu.hideout.service.reaction + +import dev.usbharu.hideout.domain.model.hideout.entity.Reaction +import dev.usbharu.hideout.repository.ReactionRepository +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 + ) + ) + } +}