mirror of https://github.com/usbharu/Hideout.git
feat: 通知のMastodon互換APIを実装
This commit is contained in:
parent
ee3681468b
commit
31240b8797
|
@ -8,6 +8,7 @@ import java.time.Instant
|
|||
data class MastodonNotification(
|
||||
@Id
|
||||
val id: Long,
|
||||
val userId: Long,
|
||||
val type: NotificationType,
|
||||
val createdAt: Instant,
|
||||
val accountId: Long,
|
||||
|
|
|
@ -4,4 +4,16 @@ interface MastodonNotificationRepository {
|
|||
suspend fun save(mastodonNotification: MastodonNotification): MastodonNotification
|
||||
suspend fun deleteById(id: Long)
|
||||
suspend fun findById(id: Long): MastodonNotification?
|
||||
suspend fun findByUserIdAndMaxIdAndMinIdAndSinceIdAndInTypesAndInSourceActorId(
|
||||
loginUser: Long,
|
||||
maxId: Long?,
|
||||
minId: Long?,
|
||||
sinceId: Long?,
|
||||
limit: Int,
|
||||
typesTmp: MutableList<NotificationType>,
|
||||
accountId: List<Long>
|
||||
): List<MastodonNotification>
|
||||
|
||||
suspend fun deleteByUserId(userId: Long)
|
||||
suspend fun deleteByUserIdAndId(userId: Long, id: Long)
|
||||
}
|
||||
|
|
|
@ -12,4 +12,22 @@ enum class NotificationType {
|
|||
admin_sign_up,
|
||||
admin_report,
|
||||
severed_relationships;
|
||||
|
||||
companion object {
|
||||
fun parse(string: String): NotificationType? = when (string) {
|
||||
|
||||
"mention" -> mention
|
||||
"status" -> status
|
||||
"reblog" -> reblog
|
||||
"follow" -> follow
|
||||
"follow_request" -> follow_request
|
||||
"favourite" -> favourite
|
||||
"poll" -> poll
|
||||
"update" -> update
|
||||
"admin.aign_up" -> admin_sign_up
|
||||
"admin.report" -> admin_report
|
||||
"servered_relationships" -> severed_relationships
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package dev.usbharu.hideout.mastodon.infrastructure.exposedrepository
|
||||
|
||||
import dev.usbharu.hideout.core.infrastructure.exposedrepository.AbstractRepository
|
||||
import dev.usbharu.hideout.core.infrastructure.exposedrepository.Timelines
|
||||
import dev.usbharu.hideout.mastodon.domain.model.MastodonNotification
|
||||
import dev.usbharu.hideout.mastodon.domain.model.MastodonNotificationRepository
|
||||
import dev.usbharu.hideout.mastodon.domain.model.NotificationType
|
||||
|
@ -58,6 +59,48 @@ class ExposedMastodonNotificationRepository : MastodonNotificationRepository, Ab
|
|||
MastodonNotifications.select { MastodonNotifications.id eq id }.singleOrNull()?.toMastodonNotification()
|
||||
}
|
||||
|
||||
override suspend fun findByUserIdAndMaxIdAndMinIdAndSinceIdAndInTypesAndInSourceActorId(
|
||||
loginUser: Long,
|
||||
maxId: Long?,
|
||||
minId: Long?,
|
||||
sinceId: Long?,
|
||||
limit: Int,
|
||||
typesTmp: MutableList<NotificationType>,
|
||||
accountId: List<Long>
|
||||
): List<MastodonNotification> = query {
|
||||
val query = MastodonNotifications.select {
|
||||
MastodonNotifications.userId eq loginUser
|
||||
}
|
||||
|
||||
|
||||
if (maxId != null) {
|
||||
query.andWhere { MastodonNotifications.id lessEq maxId }
|
||||
}
|
||||
if (minId != null) {
|
||||
query.andWhere { MastodonNotifications.id greaterEq minId }
|
||||
}
|
||||
if (sinceId != null) {
|
||||
query.andWhere { MastodonNotifications.id greaterEq sinceId }
|
||||
}
|
||||
val result = query
|
||||
.limit(limit)
|
||||
.orderBy(Timelines.createdAt, SortOrder.DESC)
|
||||
|
||||
return@query result.map { it.toMastodonNotification() }
|
||||
}
|
||||
|
||||
override suspend fun deleteByUserId(userId: Long) {
|
||||
MastodonNotifications.deleteWhere {
|
||||
MastodonNotifications.userId eq userId
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun deleteByUserIdAndId(userId: Long, id: Long) {
|
||||
MastodonNotifications.deleteWhere {
|
||||
MastodonNotifications.userId eq userId and (MastodonNotifications.id eq id)
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val logger = LoggerFactory.getLogger(ExposedMastodonNotificationRepository::class.java)
|
||||
}
|
||||
|
@ -66,6 +109,7 @@ class ExposedMastodonNotificationRepository : MastodonNotificationRepository, Ab
|
|||
fun ResultRow.toMastodonNotification(): MastodonNotification {
|
||||
return MastodonNotification(
|
||||
this[MastodonNotifications.id],
|
||||
this[MastodonNotifications.userId],
|
||||
NotificationType.valueOf(this[MastodonNotifications.type]),
|
||||
this[MastodonNotifications.createdAt],
|
||||
this[MastodonNotifications.accountId],
|
||||
|
@ -77,6 +121,7 @@ fun ResultRow.toMastodonNotification(): MastodonNotification {
|
|||
|
||||
object MastodonNotifications : Table("mastodon_notifications") {
|
||||
val id = long("id")
|
||||
val userId = long("user_id")
|
||||
val type = varchar("type", 100)
|
||||
val createdAt = timestamp("created_at")
|
||||
val accountId = long("account_id")
|
||||
|
|
|
@ -5,4 +5,9 @@ import org.springframework.data.mongodb.repository.MongoRepository
|
|||
|
||||
interface MongoMastodonNotificationRepository : MongoRepository<MastodonNotification, Long> {
|
||||
|
||||
|
||||
fun deleteByUserId(userId: Long): Long
|
||||
|
||||
|
||||
fun deleteByIdAndUserId(id: Long, userId: Long): Long
|
||||
}
|
||||
|
|
|
@ -2,13 +2,21 @@ package dev.usbharu.hideout.mastodon.infrastructure.mongorepository
|
|||
|
||||
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.springframework.boot.autoconfigure.condition.ConditionalOnProperty
|
||||
import org.springframework.data.domain.Sort
|
||||
import org.springframework.data.mongodb.core.MongoTemplate
|
||||
import org.springframework.data.mongodb.core.query.Criteria
|
||||
import org.springframework.data.mongodb.core.query.Query
|
||||
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) :
|
||||
class MongoMastodonNotificationRepositoryWrapper(
|
||||
private val mongoMastodonNotificationRepository: MongoMastodonNotificationRepository,
|
||||
private val mongoTemplate: MongoTemplate
|
||||
) :
|
||||
MastodonNotificationRepository {
|
||||
override suspend fun save(mastodonNotification: MastodonNotification): MastodonNotification {
|
||||
return mongoMastodonNotificationRepository.save(mastodonNotification)
|
||||
|
@ -21,4 +29,43 @@ class MongoMastodonNotificationRepositoryWrapper(private val mongoMastodonNotifi
|
|||
override suspend fun findById(id: Long): MastodonNotification? {
|
||||
return mongoMastodonNotificationRepository.findById(id).getOrNull()
|
||||
}
|
||||
|
||||
override suspend fun findByUserIdAndMaxIdAndMinIdAndSinceIdAndInTypesAndInSourceActorId(
|
||||
loginUser: Long,
|
||||
maxId: Long?,
|
||||
minId: Long?,
|
||||
sinceId: Long?,
|
||||
limit: Int,
|
||||
typesTmp: MutableList<NotificationType>,
|
||||
accountId: List<Long>
|
||||
): List<MastodonNotification> {
|
||||
val query = Query()
|
||||
|
||||
if (maxId != null) {
|
||||
val criteria = Criteria.where("id").lte(maxId)
|
||||
query.addCriteria(criteria)
|
||||
}
|
||||
|
||||
if (minId != null) {
|
||||
val criteria = Criteria.where("id").gte(minId)
|
||||
query.addCriteria(criteria)
|
||||
}
|
||||
if (sinceId != null) {
|
||||
val criteria = Criteria.where("id").gte(sinceId)
|
||||
query.addCriteria(criteria)
|
||||
}
|
||||
|
||||
query.limit(limit)
|
||||
query.with(Sort.by(Sort.Direction.DESC, "createdAt"))
|
||||
|
||||
return mongoTemplate.find(query, MastodonNotification::class.java)
|
||||
}
|
||||
|
||||
override suspend fun deleteByUserId(userId: Long) {
|
||||
mongoMastodonNotificationRepository.deleteByUserId(userId)
|
||||
}
|
||||
|
||||
override suspend fun deleteByUserIdAndId(userId: Long, id: Long) {
|
||||
mongoMastodonNotificationRepository.deleteByIdAndUserId(id, userId)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,56 @@
|
|||
package dev.usbharu.hideout.mastodon.interfaces.api.notification
|
||||
|
||||
import dev.usbharu.hideout.controller.mastodon.generated.NotificationsApi
|
||||
import dev.usbharu.hideout.core.infrastructure.springframework.security.LoginUserContextHolder
|
||||
import dev.usbharu.hideout.domain.mastodon.model.generated.Notification
|
||||
import dev.usbharu.hideout.mastodon.domain.model.NotificationType
|
||||
import dev.usbharu.hideout.mastodon.service.notification.NotificationApiService
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.asFlow
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import org.springframework.http.ResponseEntity
|
||||
import org.springframework.stereotype.Controller
|
||||
|
||||
@Controller
|
||||
class MastodonNotificationApiController(
|
||||
private val loginUserContextHolder: LoginUserContextHolder,
|
||||
private val notificationApiService: NotificationApiService
|
||||
) : NotificationsApi {
|
||||
override suspend fun apiV1NotificationsClearPost(): ResponseEntity<Any> {
|
||||
notificationApiService.clearAll(loginUserContextHolder.getLoginUserId())
|
||||
return ResponseEntity.ok(null)
|
||||
}
|
||||
|
||||
override fun apiV1NotificationsGet(
|
||||
maxId: String?,
|
||||
sinceId: String?,
|
||||
minId: String?,
|
||||
limit: Int?,
|
||||
types: List<String>?,
|
||||
excludeTypes: List<String>?,
|
||||
accountId: List<String>?
|
||||
): ResponseEntity<Flow<Notification>> = runBlocking {
|
||||
val notificationFlow = notificationApiService.notifications(
|
||||
loginUserContextHolder.getLoginUserId(),
|
||||
maxId?.toLong(),
|
||||
minId?.toLong(),
|
||||
sinceId?.toLong(),
|
||||
limit ?: 20,
|
||||
types.orEmpty().mapNotNull { NotificationType.parse(it) },
|
||||
excludeTypes = excludeTypes.orEmpty().mapNotNull { NotificationType.parse(it) },
|
||||
accountId = accountId.orEmpty().mapNotNull { it.toLongOrNull() }
|
||||
).asFlow()
|
||||
ResponseEntity.ok(notificationFlow)
|
||||
}
|
||||
|
||||
override suspend fun apiV1NotificationsIdDismissPost(id: String): ResponseEntity<Any> {
|
||||
notificationApiService.dismiss(loginUserContextHolder.getLoginUserId(), id.toLong())
|
||||
return ResponseEntity.ok(null)
|
||||
}
|
||||
|
||||
override suspend fun apiV1NotificationsIdGet(id: String): ResponseEntity<Notification> {
|
||||
val notification = notificationApiService.fingById(loginUserContextHolder.getLoginUserId(), id.toLong())
|
||||
|
||||
return ResponseEntity.ok(notification)
|
||||
}
|
||||
}
|
|
@ -41,6 +41,7 @@ class MastodonNotificationStore(private val mastodonNotificationRepository: Mast
|
|||
|
||||
val mastodonNotification = MastodonNotification(
|
||||
id = notification.id,
|
||||
notification.userId,
|
||||
type = notificationType,
|
||||
createdAt = notification.createdAt,
|
||||
accountId = notification.sourceActorId,
|
||||
|
|
|
@ -0,0 +1,23 @@
|
|||
package dev.usbharu.hideout.mastodon.service.notification
|
||||
|
||||
import dev.usbharu.hideout.domain.mastodon.model.generated.Notification
|
||||
import dev.usbharu.hideout.mastodon.domain.model.NotificationType
|
||||
|
||||
interface NotificationApiService {
|
||||
suspend fun notifications(
|
||||
loginUser: Long,
|
||||
maxId: Long?,
|
||||
minId: Long?,
|
||||
sinceId: Long?,
|
||||
limit: Int,
|
||||
types: List<NotificationType>,
|
||||
excludeTypes: List<NotificationType>,
|
||||
accountId: List<Long>
|
||||
): List<Notification>
|
||||
|
||||
suspend fun fingById(loginUser: Long, notificationId: Long): Notification?
|
||||
|
||||
suspend fun clearAll(loginUser: Long)
|
||||
|
||||
suspend fun dismiss(loginUser: Long, notificationId: Long)
|
||||
}
|
|
@ -0,0 +1,111 @@
|
|||
package dev.usbharu.hideout.mastodon.service.notification
|
||||
|
||||
import dev.usbharu.hideout.application.external.Transaction
|
||||
import dev.usbharu.hideout.domain.mastodon.model.generated.Notification
|
||||
import dev.usbharu.hideout.mastodon.domain.model.MastodonNotificationRepository
|
||||
import dev.usbharu.hideout.mastodon.domain.model.NotificationType
|
||||
import dev.usbharu.hideout.mastodon.domain.model.NotificationType.*
|
||||
import dev.usbharu.hideout.mastodon.query.StatusQueryService
|
||||
import dev.usbharu.hideout.mastodon.service.account.AccountService
|
||||
import org.springframework.stereotype.Service
|
||||
|
||||
@Service
|
||||
class NotificationApiServiceImpl(
|
||||
private val mastodonNotificationRepository: MastodonNotificationRepository,
|
||||
private val transaction: Transaction,
|
||||
private val accountService: AccountService,
|
||||
private val statusQueryService: StatusQueryService
|
||||
) :
|
||||
NotificationApiService {
|
||||
override suspend fun notifications(
|
||||
loginUser: Long,
|
||||
maxId: Long?,
|
||||
minId: Long?,
|
||||
sinceId: Long?,
|
||||
limit: Int,
|
||||
types: List<NotificationType>,
|
||||
excludeTypes: List<NotificationType>,
|
||||
accountId: List<Long>
|
||||
): List<Notification> = transaction.transaction {
|
||||
|
||||
val typesTmp = mutableListOf<NotificationType>()
|
||||
|
||||
typesTmp.addAll(types)
|
||||
typesTmp.removeAll(excludeTypes)
|
||||
|
||||
val mastodonNotifications =
|
||||
mastodonNotificationRepository.findByUserIdAndMaxIdAndMinIdAndSinceIdAndInTypesAndInSourceActorId(
|
||||
loginUser,
|
||||
maxId,
|
||||
minId,
|
||||
sinceId,
|
||||
limit,
|
||||
typesTmp,
|
||||
accountId
|
||||
)
|
||||
|
||||
val accounts = accountService.findByIds(mastodonNotifications.map {
|
||||
it.accountId
|
||||
}).associateBy { it.id.toLong() }
|
||||
|
||||
val statuses = statusQueryService.findByPostIds(mastodonNotifications.mapNotNull { it.statusId })
|
||||
.associateBy { it.id.toLong() }
|
||||
|
||||
mastodonNotifications.map {
|
||||
Notification(
|
||||
id = it.id.toString(),
|
||||
type = convertNotificationType(it.type),
|
||||
createdAt = it.createdAt.toString(),
|
||||
account = accounts.getValue(it.accountId),
|
||||
status = statuses[it.statusId],
|
||||
report = null,
|
||||
relationshipSeveranceEvent = null
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun fingById(loginUser: Long, notificationId: Long): Notification? {
|
||||
val findById = mastodonNotificationRepository.findById(notificationId) ?: return null
|
||||
|
||||
if (findById.userId != loginUser) {
|
||||
return null
|
||||
}
|
||||
|
||||
val account = accountService.findById(findById.accountId)
|
||||
val status = findById.statusId?.let { statusQueryService.findByPostId(it) }
|
||||
|
||||
return Notification(
|
||||
id = findById.id.toString(),
|
||||
type = convertNotificationType(findById.type),
|
||||
createdAt = findById.createdAt.toString(),
|
||||
account = account,
|
||||
status = status,
|
||||
report = null,
|
||||
relationshipSeveranceEvent = null
|
||||
)
|
||||
}
|
||||
|
||||
override suspend fun clearAll(loginUser: Long) {
|
||||
mastodonNotificationRepository.deleteByUserId(loginUser)
|
||||
}
|
||||
|
||||
override suspend fun dismiss(loginUser: Long, notificationId: Long) {
|
||||
mastodonNotificationRepository.deleteByUserIdAndId(loginUser, notificationId)
|
||||
}
|
||||
|
||||
|
||||
private fun convertNotificationType(notificationType: NotificationType): Notification.Type =
|
||||
when (notificationType) {
|
||||
mention -> Notification.Type.mention
|
||||
status -> Notification.Type.status
|
||||
reblog -> Notification.Type.reblog
|
||||
follow -> Notification.Type.follow
|
||||
follow_request -> Notification.Type.follow
|
||||
favourite -> Notification.Type.followRequest
|
||||
poll -> Notification.Type.poll
|
||||
update -> Notification.Type.update
|
||||
admin_sign_up -> Notification.Type.adminPeriodSignUp
|
||||
admin_report -> Notification.Type.adminPeriodReport
|
||||
severed_relationships -> Notification.Type.severedRelationships
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue