mirror of https://github.com/usbharu/Hideout.git
feat: GenerateTimelineServiceを作成
This commit is contained in:
parent
5b89c681b0
commit
d9fed726fb
|
@ -0,0 +1,30 @@
|
|||
package dev.usbharu.hideout.controller.mastodon
|
||||
|
||||
import dev.usbharu.hideout.controller.mastodon.generated.TimelineApi
|
||||
import dev.usbharu.hideout.domain.mastodon.model.generated.Status
|
||||
import org.springframework.http.ResponseEntity
|
||||
import org.springframework.stereotype.Controller
|
||||
|
||||
@Controller
|
||||
class MastodonTimelineApiController : TimelineApi {
|
||||
override fun apiV1TimelinesHomeGet(
|
||||
maxId: String?,
|
||||
sinceId: String?,
|
||||
minId: String?,
|
||||
limit: Int?
|
||||
): ResponseEntity<List<Status>> {
|
||||
|
||||
}
|
||||
|
||||
override fun apiV1TimelinesPublicGet(
|
||||
local: Boolean?,
|
||||
remote: Boolean?,
|
||||
onlyMedia: Boolean?,
|
||||
maxId: String?,
|
||||
sinceId: String?,
|
||||
minId: String?,
|
||||
limit: Int?
|
||||
): ResponseEntity<List<Status>> {
|
||||
|
||||
}
|
||||
}
|
|
@ -13,5 +13,6 @@ data class Timeline(
|
|||
val replyId: Long?,
|
||||
val repostId: Long?,
|
||||
val visibility: Visibility,
|
||||
val sensitive: Boolean
|
||||
val sensitive: Boolean,
|
||||
val isLocal: Boolean
|
||||
)
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
package dev.usbharu.hideout.query.mastodon
|
||||
|
||||
import dev.usbharu.hideout.domain.mastodon.model.generated.Status
|
||||
|
||||
interface StatusQueryService {
|
||||
suspend fun findByPostIds(ids: List<Long>): List<Status>
|
||||
}
|
|
@ -0,0 +1,102 @@
|
|||
package dev.usbharu.hideout.query.mastodon
|
||||
|
||||
import dev.usbharu.hideout.domain.mastodon.model.generated.Account
|
||||
import dev.usbharu.hideout.domain.mastodon.model.generated.Status
|
||||
import dev.usbharu.hideout.repository.Posts
|
||||
import dev.usbharu.hideout.repository.Users
|
||||
import org.jetbrains.exposed.sql.innerJoin
|
||||
import org.jetbrains.exposed.sql.select
|
||||
import java.time.Instant
|
||||
|
||||
class StatusQueryServiceImpl : StatusQueryService {
|
||||
override suspend fun findByPostIds(ids: List<Long>): List<Status> {
|
||||
|
||||
val pairs = Posts.innerJoin(Users, onColumn = { userId }, otherColumn = { id })
|
||||
.select { Posts.id inList ids }
|
||||
.map {
|
||||
Status(
|
||||
id = it[Posts.id].toString(),
|
||||
uri = it[Posts.apId],
|
||||
createdAt = Instant.ofEpochMilli(it[Posts.createdAt]).toString(),
|
||||
account = Account(
|
||||
id = it[Users.id].toString(),
|
||||
username = it[Users.name],
|
||||
acct = "${it[Users.name]}@${it[Users.domain]}",
|
||||
url = it[Users.url],
|
||||
displayName = it[Users.screenName],
|
||||
note = it[Users.description],
|
||||
avatar = it[Users.url] + "/icon.jpg",
|
||||
avatarStatic = it[Users.url] + "/icon.jpg",
|
||||
header = it[Users.url] + "/header.jpg",
|
||||
headerStatic = it[Users.url] + "/header.jpg",
|
||||
locked = false,
|
||||
fields = emptyList(),
|
||||
emojis = emptyList(),
|
||||
bot = false,
|
||||
group = false,
|
||||
discoverable = true,
|
||||
createdAt = Instant.ofEpochMilli(it[Users.createdAt]).toString(),
|
||||
lastStatusAt = Instant.ofEpochMilli(it[Users.createdAt]).toString(),
|
||||
statusesCount = 0,
|
||||
followersCount = 0,
|
||||
followingCount = 0,
|
||||
noindex = false,
|
||||
moved = false,
|
||||
suspendex = false,
|
||||
limited = false
|
||||
),
|
||||
content = it[Posts.text],
|
||||
visibility = when (it[Posts.visibility]) {
|
||||
0 -> Status.Visibility.public
|
||||
1 -> Status.Visibility.unlisted
|
||||
2 -> Status.Visibility.private
|
||||
3 -> Status.Visibility.direct
|
||||
else -> Status.Visibility.public
|
||||
},
|
||||
sensitive = it[Posts.sensitive],
|
||||
spoilerText = it[Posts.overview].orEmpty(),
|
||||
mediaAttachments = emptyList(),
|
||||
mentions = emptyList(),
|
||||
tags = emptyList(),
|
||||
emojis = emptyList(),
|
||||
reblogsCount = 0,
|
||||
favouritesCount = 0,
|
||||
repliesCount = 0,
|
||||
url = it[Posts.apId],
|
||||
inReplyToId = it[Posts.replyId].toString(),
|
||||
inReplyToAccountId = null,
|
||||
language = null,
|
||||
text = it[Posts.text],
|
||||
editedAt = null,
|
||||
application = null,
|
||||
poll = null,
|
||||
card = null,
|
||||
favourited = null,
|
||||
reblogged = null,
|
||||
muted = null,
|
||||
bookmarked = null,
|
||||
pinned = null,
|
||||
filtered = null
|
||||
) to it[Posts.repostId]
|
||||
}
|
||||
|
||||
val statuses = pairs.map { it.first }
|
||||
return pairs
|
||||
.map {
|
||||
if (it.second != null) {
|
||||
it.first.copy(reblog = statuses.find { status -> status.id == it.second.toString() })
|
||||
} else {
|
||||
it.first
|
||||
}
|
||||
}
|
||||
.map {
|
||||
if (it.inReplyToId != null) {
|
||||
it.copy(inReplyToAccountId = statuses.find { status -> status.id == it.inReplyToId }?.id)
|
||||
} else {
|
||||
it
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
|
@ -1,11 +1,18 @@
|
|||
package dev.usbharu.hideout.repository
|
||||
|
||||
import dev.usbharu.hideout.domain.model.hideout.entity.Timeline
|
||||
import org.springframework.data.domain.Pageable
|
||||
import org.springframework.data.mongodb.repository.MongoRepository
|
||||
|
||||
interface MongoTimelineRepository : MongoRepository<Timeline, Long> {
|
||||
|
||||
|
||||
fun findByUserId(id: Long): List<Timeline>
|
||||
fun findByUserIdAndTimelineId(userId: Long, timelineId: Long): List<Timeline>
|
||||
fun findByUserIdAndTimelineIdAndPostIdBetweenAndLocal(
|
||||
userId: Long?,
|
||||
timelineId: Long?,
|
||||
postIdMin: Long?,
|
||||
postIdMax: Long?,
|
||||
isLocal: Boolean?,
|
||||
pageable: Pageable
|
||||
): List<Timeline>
|
||||
}
|
||||
|
|
|
@ -0,0 +1,18 @@
|
|||
package dev.usbharu.hideout.service.post
|
||||
|
||||
import dev.usbharu.hideout.domain.mastodon.model.generated.Status
|
||||
import org.springframework.stereotype.Service
|
||||
|
||||
@Service
|
||||
interface GenerateTimelineService {
|
||||
suspend fun getTimeline(
|
||||
forUserId: Long? = null,
|
||||
localOnly: Boolean = false,
|
||||
mediaOnly: Boolean = false,
|
||||
maxId: Long? = null,
|
||||
minId: Long? = null,
|
||||
sinceId: Long? = null,
|
||||
limit: Int = 20
|
||||
): List<Status>
|
||||
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
package dev.usbharu.hideout.service.post
|
||||
|
||||
import dev.usbharu.hideout.domain.mastodon.model.generated.Status
|
||||
import dev.usbharu.hideout.query.mastodon.StatusQueryService
|
||||
import dev.usbharu.hideout.repository.MongoTimelineRepository
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.springframework.data.domain.Pageable
|
||||
|
||||
class MongoGenerateTimelineService(
|
||||
private val mongoTimelineRepository: MongoTimelineRepository,
|
||||
private val statusQueryService: StatusQueryService
|
||||
) :
|
||||
GenerateTimelineService {
|
||||
override suspend fun getTimeline(
|
||||
forUserId: Long?,
|
||||
localOnly: Boolean,
|
||||
mediaOnly: Boolean,
|
||||
maxId: Long?,
|
||||
minId: Long?,
|
||||
sinceId: Long?,
|
||||
limit: Int
|
||||
): List<Status> {
|
||||
val timelines =
|
||||
withContext(Dispatchers.IO) {
|
||||
mongoTimelineRepository.findByUserIdAndTimelineIdAndPostIdBetweenAndLocal(
|
||||
forUserId, 0, maxId, minId, localOnly, Pageable.ofSize(limit)
|
||||
)
|
||||
}
|
||||
return statusQueryService.findByPostIds(timelines.flatMap { setOfNotNull(it.postId, it.replyId, it.repostId) })
|
||||
}
|
||||
}
|
|
@ -18,25 +18,25 @@ class PostServiceImpl(
|
|||
private val interceptors = Collections.synchronizedList(mutableListOf<PostCreateInterceptor>())
|
||||
|
||||
override suspend fun createLocal(post: PostCreateDto): Post {
|
||||
val create = internalCreate(post)
|
||||
val create = internalCreate(post, true)
|
||||
interceptors.forEach { it.run(create) }
|
||||
return create
|
||||
}
|
||||
|
||||
override suspend fun createRemote(post: Post): Post {
|
||||
return internalCreate(post)
|
||||
return internalCreate(post, false)
|
||||
}
|
||||
|
||||
override fun addInterceptor(postCreateInterceptor: PostCreateInterceptor) {
|
||||
interceptors.add(postCreateInterceptor)
|
||||
}
|
||||
|
||||
private suspend fun internalCreate(post: Post): Post {
|
||||
timelineService.publishTimeline(post)
|
||||
private suspend fun internalCreate(post: Post, isLocal: Boolean): Post {
|
||||
timelineService.publishTimeline(post, isLocal)
|
||||
return postRepository.save(post)
|
||||
}
|
||||
|
||||
private suspend fun internalCreate(post: PostCreateDto): Post {
|
||||
private suspend fun internalCreate(post: PostCreateDto, isLocal: Boolean): Post {
|
||||
val user = userRepository.findById(post.userId) ?: throw UserNotFoundException("${post.userId} was not found")
|
||||
val id = postRepository.generateId()
|
||||
val createPost = Post.of(
|
||||
|
@ -50,6 +50,6 @@ class PostServiceImpl(
|
|||
repostId = null,
|
||||
replyId = null
|
||||
)
|
||||
return internalCreate(createPost)
|
||||
return internalCreate(createPost, isLocal)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,7 +11,7 @@ class TimelineService(
|
|||
private val followerQueryService: FollowerQueryService,
|
||||
private val timelineRepository: TimelineRepository
|
||||
) {
|
||||
suspend fun publishTimeline(post: Post) {
|
||||
suspend fun publishTimeline(post: Post, isLocal: Boolean) {
|
||||
val findFollowersById = followerQueryService.findFollowersById(post.userId)
|
||||
timelineRepository.saveAll(findFollowersById.map {
|
||||
Timeline(
|
||||
|
@ -24,7 +24,8 @@ class TimelineService(
|
|||
replyId = post.replyId,
|
||||
repostId = post.repostId,
|
||||
visibility = post.visibility,
|
||||
sensitive = post.sensitive
|
||||
sensitive = post.sensitive,
|
||||
isLocal = isLocal
|
||||
)
|
||||
})
|
||||
}
|
||||
|
|
|
@ -15,6 +15,8 @@ tags:
|
|||
description: app
|
||||
- name: instance
|
||||
description: instance
|
||||
- name: timeline
|
||||
description: timeline
|
||||
|
||||
paths:
|
||||
/api/v2/instance:
|
||||
|
@ -202,6 +204,94 @@ paths:
|
|||
200:
|
||||
description: 成功
|
||||
|
||||
/api/v1/timelines/public:
|
||||
get:
|
||||
tags:
|
||||
- timeline
|
||||
parameters:
|
||||
- in: query
|
||||
name: local
|
||||
required: false
|
||||
schema:
|
||||
type: boolean
|
||||
- in: query
|
||||
name: remote
|
||||
required: false
|
||||
schema:
|
||||
type: boolean
|
||||
- in: query
|
||||
name: only_media
|
||||
required: false
|
||||
schema:
|
||||
type: boolean
|
||||
- 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
|
||||
responses:
|
||||
200:
|
||||
description: 成功
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
$ref: "#/components/schemas/Status"
|
||||
|
||||
/api/v1/timelines/home:
|
||||
get:
|
||||
tags:
|
||||
- timeline
|
||||
security:
|
||||
- OAuth2:
|
||||
- "read:statuses"
|
||||
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
|
||||
responses:
|
||||
200:
|
||||
description: 成功
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
$ref: "#/components/schemas/Status"
|
||||
|
||||
components:
|
||||
schemas:
|
||||
AccountsCreateRequest:
|
||||
|
|
Loading…
Reference in New Issue