feat: Mastodonの通知APIのNotificationStoreを追加

This commit is contained in:
usbharu 2024-01-27 16:31:06 +09:00
parent cf5c396d32
commit ee3681468b
8 changed files with 381 additions and 0 deletions

View File

@ -0,0 +1,17 @@
package dev.usbharu.hideout.mastodon.domain.model
import org.springframework.data.annotation.Id
import org.springframework.data.mongodb.core.mapping.Document
import java.time.Instant
@Document
data class MastodonNotification(
@Id
val id: Long,
val type: NotificationType,
val createdAt: Instant,
val accountId: Long,
val statusId: Long?,
val reportId: Long?,
val relationshipServeranceEvent: Long?
)

View File

@ -0,0 +1,7 @@
package dev.usbharu.hideout.mastodon.domain.model
interface MastodonNotificationRepository {
suspend fun save(mastodonNotification: MastodonNotification): MastodonNotification
suspend fun deleteById(id: Long)
suspend fun findById(id: Long): MastodonNotification?
}

View File

@ -0,0 +1,15 @@
package dev.usbharu.hideout.mastodon.domain.model
enum class NotificationType {
mention,
status,
reblog,
follow,
follow_request,
favourite,
poll,
update,
admin_sign_up,
admin_report,
severed_relationships;
}

View File

@ -0,0 +1,86 @@
package dev.usbharu.hideout.mastodon.infrastructure.exposedrepository
import dev.usbharu.hideout.core.infrastructure.exposedrepository.AbstractRepository
import dev.usbharu.hideout.mastodon.domain.model.MastodonNotification
import dev.usbharu.hideout.mastodon.domain.model.MastodonNotificationRepository
import dev.usbharu.hideout.mastodon.domain.model.NotificationType
import org.jetbrains.exposed.sql.*
import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq
import org.jetbrains.exposed.sql.javatime.timestamp
import org.slf4j.Logger
import org.slf4j.LoggerFactory
import org.springframework.beans.factory.annotation.Qualifier
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty
import org.springframework.stereotype.Repository
@Repository
@Qualifier("jdbc")
@ConditionalOnProperty("hideout.use-mongodb", havingValue = "false", matchIfMissing = true)
class ExposedMastodonNotificationRepository : MastodonNotificationRepository, AbstractRepository() {
override val logger: Logger
get() = Companion.logger
override suspend fun save(mastodonNotification: MastodonNotification): MastodonNotification = query {
val singleOrNull =
MastodonNotifications.select { MastodonNotifications.id eq mastodonNotification.id }.singleOrNull()
if (singleOrNull == null) {
MastodonNotifications.insert {
it[MastodonNotifications.id] = mastodonNotification.id
it[MastodonNotifications.type] = mastodonNotification.type.name
it[MastodonNotifications.createdAt] = mastodonNotification.createdAt
it[MastodonNotifications.accountId] = mastodonNotification.accountId
it[MastodonNotifications.statusId] = mastodonNotification.statusId
it[MastodonNotifications.reportId] = mastodonNotification.reportId
it[MastodonNotifications.relationshipServeranceEventId] =
mastodonNotification.relationshipServeranceEvent
}
} else {
MastodonNotifications.update({ MastodonNotifications.id eq mastodonNotification.id }) {
it[MastodonNotifications.type] = mastodonNotification.type.name
it[MastodonNotifications.createdAt] = mastodonNotification.createdAt
it[MastodonNotifications.accountId] = mastodonNotification.accountId
it[MastodonNotifications.statusId] = mastodonNotification.statusId
it[MastodonNotifications.reportId] = mastodonNotification.reportId
it[MastodonNotifications.relationshipServeranceEventId] =
mastodonNotification.relationshipServeranceEvent
}
}
mastodonNotification
}
override suspend fun deleteById(id: Long): Unit = query {
MastodonNotifications.deleteWhere {
MastodonNotifications.id eq id
}
}
override suspend fun findById(id: Long): MastodonNotification? = query {
MastodonNotifications.select { MastodonNotifications.id eq id }.singleOrNull()?.toMastodonNotification()
}
companion object {
private val logger = LoggerFactory.getLogger(ExposedMastodonNotificationRepository::class.java)
}
}
fun ResultRow.toMastodonNotification(): MastodonNotification {
return MastodonNotification(
this[MastodonNotifications.id],
NotificationType.valueOf(this[MastodonNotifications.type]),
this[MastodonNotifications.createdAt],
this[MastodonNotifications.accountId],
this[MastodonNotifications.statusId],
this[MastodonNotifications.reportId],
this[MastodonNotifications.relationshipServeranceEventId],
)
}
object MastodonNotifications : Table("mastodon_notifications") {
val id = long("id")
val type = varchar("type", 100)
val createdAt = timestamp("created_at")
val accountId = long("account_id")
val statusId = long("status_id").nullable()
val reportId = long("report_id").nullable()
val relationshipServeranceEventId = long("relationship_serverance_event_id").nullable()
}

View File

@ -0,0 +1,8 @@
package dev.usbharu.hideout.mastodon.infrastructure.mongorepository
import dev.usbharu.hideout.mastodon.domain.model.MastodonNotification
import org.springframework.data.mongodb.repository.MongoRepository
interface MongoMastodonNotificationRepository : MongoRepository<MastodonNotification, Long> {
}

View File

@ -0,0 +1,24 @@
package dev.usbharu.hideout.mastodon.infrastructure.mongorepository
import dev.usbharu.hideout.mastodon.domain.model.MastodonNotification
import dev.usbharu.hideout.mastodon.domain.model.MastodonNotificationRepository
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty
import org.springframework.stereotype.Repository
import kotlin.jvm.optionals.getOrNull
@Repository
@ConditionalOnProperty("hideout.use-mongodb", havingValue = "true", matchIfMissing = false)
class MongoMastodonNotificationRepositoryWrapper(private val mongoMastodonNotificationRepository: MongoMastodonNotificationRepository) :
MastodonNotificationRepository {
override suspend fun save(mastodonNotification: MastodonNotification): MastodonNotification {
return mongoMastodonNotificationRepository.save(mastodonNotification)
}
override suspend fun deleteById(id: Long) {
mongoMastodonNotificationRepository.deleteById(id)
}
override suspend fun findById(id: Long): MastodonNotification? {
return mongoMastodonNotificationRepository.findById(id).getOrNull()
}
}

View File

@ -0,0 +1,65 @@
package dev.usbharu.hideout.mastodon.service.notification
import dev.usbharu.hideout.core.domain.model.actor.Actor
import dev.usbharu.hideout.core.domain.model.notification.Notification
import dev.usbharu.hideout.core.domain.model.post.Post
import dev.usbharu.hideout.core.domain.model.reaction.Reaction
import dev.usbharu.hideout.core.service.notification.NotificationStore
import dev.usbharu.hideout.mastodon.domain.model.MastodonNotification
import dev.usbharu.hideout.mastodon.domain.model.MastodonNotificationRepository
import dev.usbharu.hideout.mastodon.domain.model.NotificationType
import org.slf4j.LoggerFactory
import org.springframework.stereotype.Component
@Component
class MastodonNotificationStore(private val mastodonNotificationRepository: MastodonNotificationRepository) :
NotificationStore {
override suspend fun publishNotification(
notification: Notification,
user: Actor,
sourceActor: Actor?,
post: Post?,
reaction: Reaction?
): Boolean {
val notificationType = when (notification.type) {
"mention" -> NotificationType.mention
"post" -> NotificationType.status
"repost" -> NotificationType.reblog
"follow" -> NotificationType.follow
"follow-request" -> NotificationType.follow_request
"reaction" -> NotificationType.favourite
else -> {
logger.debug("Notification type does not support. type: {}", notification.type)
return false
}
}
if (notification.sourceActorId == null) {
logger.debug("Notification does not support. notification.sourceActorId is null")
return false
}
val mastodonNotification = MastodonNotification(
id = notification.id,
type = notificationType,
createdAt = notification.createdAt,
accountId = notification.sourceActorId,
statusId = notification.postId,
reportId = null,
relationshipServeranceEvent = null
)
mastodonNotificationRepository.save(mastodonNotification)
return true
}
override suspend fun unpulishNotification(notificationId: Long): Boolean {
mastodonNotificationRepository.deleteById(notificationId)
return true
}
companion object {
private val logger = LoggerFactory.getLogger(MastodonNotificationStore::class.java)
}
}

View File

@ -19,6 +19,8 @@ tags:
description: timeline
- name: media
description: media
- name: notification
description: notification
paths:
/api/v2/instance:
@ -803,6 +805,122 @@ paths:
schema:
$ref: "#/components/schemas/Relationship"
/api/v1/notifications:
get:
tags:
- notifications
security:
- OAuth2:
- "read:notifications"
parameters:
- in: query
name: max_id
required: false
schema:
type: string
- in: query
name: since_id
required: false
schema:
type: string
- in: query
name: min_id
required: false
schema:
type: string
- in: query
name: limit
required: false
schema:
type: integer
- in: query
name: types[]
required: false
schema:
type: array
items:
type: string
- in: query
name: exclude_types[]
required: false
schema:
type: array
items:
type: string
- in: query
name: account_id
required: false
schema:
type: array
items:
type: string
responses:
200:
description: 成功
content:
application/json:
schema:
type: array
items:
$ref: "#/components/schemas/Notification"
/api/v1/notifications/{id}:
get:
tags:
- notifications
security:
- OAuth2:
- "read:notifications"
parameters:
- in: path
required: true
name: id
schema:
type: string
responses:
200:
description: 成功
content:
application/json:
schema:
$ref: "#/components/schemas/Notification"
/api/v1/notifications/clear:
post:
tags:
- notifications
security:
- OAuth2:
- "write:notifications"
responses:
200:
description: 成功
content:
application/json:
schema:
type: object
/api/v1/notifications/{id}/dismiss:
post:
tags:
- notifications
security:
- OAuth2:
- "write:notifications"
parameters:
- in: path
name: id
required: true
schema:
type: string
responses:
200:
description: 成功
content:
application/json:
schema:
type: object
components:
schemas:
V1MediaRequest:
@ -1935,6 +2053,47 @@ components:
value:
type: string
Report:
type: object
RelationshipServeranceEvent:
type: object
Notification:
type: object
properties:
id:
type: string
type:
type: string
enum:
- mention
- status
- reblog
- follow
- follow_request
- favourite
- poll
- update
- admin.sign_up
- admin.report
- severed_relationships
created_at:
type: string
account:
$ref: "#/components/schemas/Account"
status:
$ref: "#/components/schemas/Status"
report:
$ref: "#/components/schemas/Report"
relationship_severance_event:
$ref: "#/components/schemas/RelationshipServeranceEvent"
required:
- id
- type
- created_at
- account
securitySchemes:
OAuth2:
type: oauth2