From be32f6335f36889ac562bcf714cc2afc8c807b3e Mon Sep 17 00:00:00 2001 From: usbharu <64310155+usbharu@users.noreply.github.com> Date: Fri, 29 Sep 2023 11:04:31 +0900 Subject: [PATCH 01/13] =?UTF-8?q?feat:=20PostService=E3=81=AE=E4=BE=9D?= =?UTF-8?q?=E5=AD=98=E9=96=A2=E4=BF=82=E3=82=92=E8=A6=8B=E7=9B=B4=E3=81=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../hideout/service/ap/APNoteService.kt | 17 ++++++++++--- .../hideout/service/post/PostService.kt | 6 +++++ .../hideout/service/post/PostServiceImpl.kt | 24 ++++++++++++++----- 3 files changed, 38 insertions(+), 9 deletions(-) diff --git a/src/main/kotlin/dev/usbharu/hideout/service/ap/APNoteService.kt b/src/main/kotlin/dev/usbharu/hideout/service/ap/APNoteService.kt index 3c719104..c417a861 100644 --- a/src/main/kotlin/dev/usbharu/hideout/service/ap/APNoteService.kt +++ b/src/main/kotlin/dev/usbharu/hideout/service/ap/APNoteService.kt @@ -17,6 +17,8 @@ import dev.usbharu.hideout.query.PostQueryService import dev.usbharu.hideout.query.UserQueryService import dev.usbharu.hideout.repository.PostRepository import dev.usbharu.hideout.service.job.JobQueueParentService +import dev.usbharu.hideout.service.post.PostCreateInterceptor +import dev.usbharu.hideout.service.post.PostService import io.ktor.client.* import io.ktor.client.statement.* import kjob.core.job.JobProps @@ -45,9 +47,14 @@ class APNoteServiceImpl( private val followerQueryService: FollowerQueryService, private val postQueryService: PostQueryService, @Qualifier("activitypub") private val objectMapper: ObjectMapper, - private val applicationConfig: ApplicationConfig + private val applicationConfig: ApplicationConfig, + private val postService: PostService -) : APNoteService { +) : APNoteService, PostCreateInterceptor { + + init { + postService.addInterceptor(this) + } private val logger = LoggerFactory.getLogger(this::class.java) @@ -161,7 +168,7 @@ class APNoteServiceImpl( postQueryService.findByUrl(it) } - postRepository.save( + postService.createRemote( Post.of( id = postRepository.generateId(), userId = person.second.id, @@ -185,4 +192,8 @@ class APNoteServiceImpl( companion object { const val public: String = "https://www.w3.org/ns/activitystreams#Public" } + + override suspend fun run(post: Post) { + createNote(post) + } } diff --git a/src/main/kotlin/dev/usbharu/hideout/service/post/PostService.kt b/src/main/kotlin/dev/usbharu/hideout/service/post/PostService.kt index 0eed9d72..641ac4cd 100644 --- a/src/main/kotlin/dev/usbharu/hideout/service/post/PostService.kt +++ b/src/main/kotlin/dev/usbharu/hideout/service/post/PostService.kt @@ -7,4 +7,10 @@ import org.springframework.stereotype.Service @Service interface PostService { suspend fun createLocal(post: PostCreateDto): Post + suspend fun createRemote(post: Post): Post + fun addInterceptor(postCreateInterceptor: PostCreateInterceptor) +} + +interface PostCreateInterceptor { + suspend fun run(post: Post) } diff --git a/src/main/kotlin/dev/usbharu/hideout/service/post/PostServiceImpl.kt b/src/main/kotlin/dev/usbharu/hideout/service/post/PostServiceImpl.kt index 0ba6b536..2c7cc38a 100644 --- a/src/main/kotlin/dev/usbharu/hideout/service/post/PostServiceImpl.kt +++ b/src/main/kotlin/dev/usbharu/hideout/service/post/PostServiceImpl.kt @@ -5,17 +5,32 @@ import dev.usbharu.hideout.domain.model.hideout.entity.Post import dev.usbharu.hideout.exception.UserNotFoundException import dev.usbharu.hideout.repository.PostRepository import dev.usbharu.hideout.repository.UserRepository -import dev.usbharu.hideout.service.ap.APNoteService import org.springframework.stereotype.Service import java.time.Instant +import java.util.* @Service class PostServiceImpl( private val postRepository: PostRepository, private val userRepository: UserRepository, - private val apNoteService: APNoteService ) : PostService { + private val interceptors = Collections.synchronizedList(mutableListOf<PostCreateInterceptor>()) + override suspend fun createLocal(post: PostCreateDto): Post { + val create = internalCreate(post) + interceptors.forEach { it.run(create) } + return create + } + + override suspend fun createRemote(post: Post): Post { + return postRepository.save(post) + } + + override fun addInterceptor(postCreateInterceptor: PostCreateInterceptor) { + interceptors.add(postCreateInterceptor) + } + + private suspend fun internalCreate(post: PostCreateDto): Post { val user = userRepository.findById(post.userId) ?: throw UserNotFoundException("${post.userId} was not found") val id = postRepository.generateId() val createPost = Post.of( @@ -29,9 +44,6 @@ class PostServiceImpl( repostId = null, replyId = null ) - apNoteService.createNote(createPost) - return internalCreate(createPost) + return postRepository.save(createPost) } - - private suspend fun internalCreate(post: Post): Post = postRepository.save(post) } From f4716966c7dd0f98f140fc0d7976c7c595558a5c Mon Sep 17 00:00:00 2001 From: usbharu <64310155+usbharu@users.noreply.github.com> Date: Fri, 29 Sep 2023 15:18:29 +0900 Subject: [PATCH 02/13] =?UTF-8?q?feat:=20Timeline=E3=82=92=E8=BF=BD?= =?UTF-8?q?=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle.kts | 1 + .../domain/model/hideout/entity/Timeline.kt | 18 ++++++++++++++++++ .../repository/MongoTimelineRepository.kt | 9 +++++++++ .../hideout/repository/TimelineRepository.kt | 8 ++++++++ .../hideout/service/post/PostServiceImpl.kt | 9 +++++++-- src/main/resources/application.yml | 3 +++ 6 files changed, 46 insertions(+), 2 deletions(-) create mode 100644 src/main/kotlin/dev/usbharu/hideout/domain/model/hideout/entity/Timeline.kt create mode 100644 src/main/kotlin/dev/usbharu/hideout/repository/MongoTimelineRepository.kt create mode 100644 src/main/kotlin/dev/usbharu/hideout/repository/TimelineRepository.kt diff --git a/build.gradle.kts b/build.gradle.kts index ae1ed371..17396d06 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -110,6 +110,7 @@ dependencies { implementation("com.fasterxml.jackson.datatype:jackson-datatype-jsr310") implementation("com.fasterxml.jackson.module:jackson-module-kotlin") implementation("org.springframework.security:spring-security-oauth2-jose") + implementation("org.springframework.boot:spring-boot-starter-data-mongodb") implementation("io.ktor:ktor-client-logging-jvm:$ktor_version") diff --git a/src/main/kotlin/dev/usbharu/hideout/domain/model/hideout/entity/Timeline.kt b/src/main/kotlin/dev/usbharu/hideout/domain/model/hideout/entity/Timeline.kt new file mode 100644 index 00000000..a7536851 --- /dev/null +++ b/src/main/kotlin/dev/usbharu/hideout/domain/model/hideout/entity/Timeline.kt @@ -0,0 +1,18 @@ +package dev.usbharu.hideout.domain.model.hideout.entity + +import org.springframework.data.annotation.Id +import java.time.Instant + +data class Timeline( + @Id + val id: Long, + val userId: Long, + val timelineId: Long, + val postId: Long, + val postUserId: Long, + val createdAt: Instant, + val replyId: Long, + val repostId: Long, + val visibility: Visibility, + val sensitive: Boolean +) diff --git a/src/main/kotlin/dev/usbharu/hideout/repository/MongoTimelineRepository.kt b/src/main/kotlin/dev/usbharu/hideout/repository/MongoTimelineRepository.kt new file mode 100644 index 00000000..7077fe99 --- /dev/null +++ b/src/main/kotlin/dev/usbharu/hideout/repository/MongoTimelineRepository.kt @@ -0,0 +1,9 @@ +package dev.usbharu.hideout.repository + +import dev.usbharu.hideout.domain.model.hideout.entity.Timeline +import org.springframework.data.mongodb.repository.MongoRepository + +interface MongoTimelineRepository : TimelineRepository, MongoRepository<Timeline, Long> { + override fun findByUserId(id: Long): List<Timeline> + override fun findByUserIdAndTimelineId(userId: Long, timelineId: Long): List<Timeline> +} diff --git a/src/main/kotlin/dev/usbharu/hideout/repository/TimelineRepository.kt b/src/main/kotlin/dev/usbharu/hideout/repository/TimelineRepository.kt new file mode 100644 index 00000000..80b88278 --- /dev/null +++ b/src/main/kotlin/dev/usbharu/hideout/repository/TimelineRepository.kt @@ -0,0 +1,8 @@ +package dev.usbharu.hideout.repository + +import dev.usbharu.hideout.domain.model.hideout.entity.Timeline + +interface TimelineRepository { + fun findByUserId(id: Long): List<Timeline> + fun findByUserIdAndTimelineId(userId: Long, timelineId: Long): List<Timeline> +} diff --git a/src/main/kotlin/dev/usbharu/hideout/service/post/PostServiceImpl.kt b/src/main/kotlin/dev/usbharu/hideout/service/post/PostServiceImpl.kt index 2c7cc38a..d0f91b25 100644 --- a/src/main/kotlin/dev/usbharu/hideout/service/post/PostServiceImpl.kt +++ b/src/main/kotlin/dev/usbharu/hideout/service/post/PostServiceImpl.kt @@ -23,13 +23,18 @@ class PostServiceImpl( } override suspend fun createRemote(post: Post): Post { - return postRepository.save(post) + return internalCreate(post) } override fun addInterceptor(postCreateInterceptor: PostCreateInterceptor) { interceptors.add(postCreateInterceptor) } + private suspend fun internalCreate(post: Post): Post { + + return postRepository.save(post) + } + private suspend fun internalCreate(post: PostCreateDto): Post { val user = userRepository.findById(post.userId) ?: throw UserNotFoundException("${post.userId} was not found") val id = postRepository.generateId() @@ -44,6 +49,6 @@ class PostServiceImpl( repostId = null, replyId = null ) - return postRepository.save(createPost) + return internalCreate(post) } } diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 4d052250..876dbe3d 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -22,6 +22,9 @@ spring: url: "jdbc:h2:./test-dev2;MODE=POSTGRESQL" username: "" password: "" + data: + mongodb: + h2: console: From 948590c78fbaaa38ce5103035429c7852758fbe7 Mon Sep 17 00:00:00 2001 From: usbharu <64310155+usbharu@users.noreply.github.com> Date: Fri, 29 Sep 2023 15:44:58 +0900 Subject: [PATCH 03/13] =?UTF-8?q?feat:=20TimelineService=E3=82=92=E4=BD=9C?= =?UTF-8?q?=E6=88=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/model/hideout/entity/Timeline.kt | 7 ++-- .../repository/MongoTimelineRepository.kt | 8 ++-- .../MongoTimelineRepositoryWrapper.kt | 38 +++++++++++++++++++ .../hideout/repository/TimelineRepository.kt | 7 +++- .../hideout/service/post/PostServiceImpl.kt | 5 ++- .../hideout/service/post/TimelineService.kt | 31 +++++++++++++++ src/main/resources/application.yml | 6 ++- 7 files changed, 90 insertions(+), 12 deletions(-) create mode 100644 src/main/kotlin/dev/usbharu/hideout/repository/MongoTimelineRepositoryWrapper.kt create mode 100644 src/main/kotlin/dev/usbharu/hideout/service/post/TimelineService.kt diff --git a/src/main/kotlin/dev/usbharu/hideout/domain/model/hideout/entity/Timeline.kt b/src/main/kotlin/dev/usbharu/hideout/domain/model/hideout/entity/Timeline.kt index a7536851..e8e591ea 100644 --- a/src/main/kotlin/dev/usbharu/hideout/domain/model/hideout/entity/Timeline.kt +++ b/src/main/kotlin/dev/usbharu/hideout/domain/model/hideout/entity/Timeline.kt @@ -1,7 +1,6 @@ package dev.usbharu.hideout.domain.model.hideout.entity import org.springframework.data.annotation.Id -import java.time.Instant data class Timeline( @Id @@ -10,9 +9,9 @@ data class Timeline( val timelineId: Long, val postId: Long, val postUserId: Long, - val createdAt: Instant, - val replyId: Long, - val repostId: Long, + val createdAt: Long, + val replyId: Long?, + val repostId: Long?, val visibility: Visibility, val sensitive: Boolean ) diff --git a/src/main/kotlin/dev/usbharu/hideout/repository/MongoTimelineRepository.kt b/src/main/kotlin/dev/usbharu/hideout/repository/MongoTimelineRepository.kt index 7077fe99..716609f5 100644 --- a/src/main/kotlin/dev/usbharu/hideout/repository/MongoTimelineRepository.kt +++ b/src/main/kotlin/dev/usbharu/hideout/repository/MongoTimelineRepository.kt @@ -3,7 +3,9 @@ package dev.usbharu.hideout.repository import dev.usbharu.hideout.domain.model.hideout.entity.Timeline import org.springframework.data.mongodb.repository.MongoRepository -interface MongoTimelineRepository : TimelineRepository, MongoRepository<Timeline, Long> { - override fun findByUserId(id: Long): List<Timeline> - override fun findByUserIdAndTimelineId(userId: Long, timelineId: Long): List<Timeline> +interface MongoTimelineRepository : MongoRepository<Timeline, Long> { + + + fun findByUserId(id: Long): List<Timeline> + fun findByUserIdAndTimelineId(userId: Long, timelineId: Long): List<Timeline> } diff --git a/src/main/kotlin/dev/usbharu/hideout/repository/MongoTimelineRepositoryWrapper.kt b/src/main/kotlin/dev/usbharu/hideout/repository/MongoTimelineRepositoryWrapper.kt new file mode 100644 index 00000000..98674637 --- /dev/null +++ b/src/main/kotlin/dev/usbharu/hideout/repository/MongoTimelineRepositoryWrapper.kt @@ -0,0 +1,38 @@ +package dev.usbharu.hideout.repository + +import dev.usbharu.hideout.domain.model.hideout.entity.Timeline +import dev.usbharu.hideout.service.core.IdGenerateService +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext +import org.springframework.stereotype.Repository + +@Repository +class MongoTimelineRepositoryWrapper( + private val mongoTimelineRepository: MongoTimelineRepository, + private val idGenerateService: IdGenerateService +) : + TimelineRepository { + override suspend fun generateId(): Long = idGenerateService.generateId() + + override suspend fun save(timeline: Timeline): Timeline { + return withContext(Dispatchers.IO) { + mongoTimelineRepository.save(timeline) + } + } + + override suspend fun saveAll(timelines: List<Timeline>): List<Timeline> { + return mongoTimelineRepository.saveAll(timelines) + } + + override suspend fun findByUserId(id: Long): List<Timeline> { + return withContext(Dispatchers.IO) { + mongoTimelineRepository.findByUserId(id) + } + } + + override suspend fun findByUserIdAndTimelineId(userId: Long, timelineId: Long): List<Timeline> { + return withContext(Dispatchers.IO) { + mongoTimelineRepository.findByUserIdAndTimelineId(userId, timelineId) + } + } +} diff --git a/src/main/kotlin/dev/usbharu/hideout/repository/TimelineRepository.kt b/src/main/kotlin/dev/usbharu/hideout/repository/TimelineRepository.kt index 80b88278..76e9755c 100644 --- a/src/main/kotlin/dev/usbharu/hideout/repository/TimelineRepository.kt +++ b/src/main/kotlin/dev/usbharu/hideout/repository/TimelineRepository.kt @@ -3,6 +3,9 @@ package dev.usbharu.hideout.repository import dev.usbharu.hideout.domain.model.hideout.entity.Timeline interface TimelineRepository { - fun findByUserId(id: Long): List<Timeline> - fun findByUserIdAndTimelineId(userId: Long, timelineId: Long): List<Timeline> + suspend fun generateId(): Long + suspend fun save(timeline: Timeline): Timeline + suspend fun saveAll(timelines: List<Timeline>): List<Timeline> + suspend fun findByUserId(id: Long): List<Timeline> + suspend fun findByUserIdAndTimelineId(userId: Long, timelineId: Long): List<Timeline> } diff --git a/src/main/kotlin/dev/usbharu/hideout/service/post/PostServiceImpl.kt b/src/main/kotlin/dev/usbharu/hideout/service/post/PostServiceImpl.kt index d0f91b25..f2531442 100644 --- a/src/main/kotlin/dev/usbharu/hideout/service/post/PostServiceImpl.kt +++ b/src/main/kotlin/dev/usbharu/hideout/service/post/PostServiceImpl.kt @@ -13,6 +13,7 @@ import java.util.* class PostServiceImpl( private val postRepository: PostRepository, private val userRepository: UserRepository, + private val timelineService: TimelineService ) : PostService { private val interceptors = Collections.synchronizedList(mutableListOf<PostCreateInterceptor>()) @@ -31,7 +32,7 @@ class PostServiceImpl( } private suspend fun internalCreate(post: Post): Post { - + timelineService.publishTimeline(post) return postRepository.save(post) } @@ -49,6 +50,6 @@ class PostServiceImpl( repostId = null, replyId = null ) - return internalCreate(post) + return internalCreate(createPost) } } diff --git a/src/main/kotlin/dev/usbharu/hideout/service/post/TimelineService.kt b/src/main/kotlin/dev/usbharu/hideout/service/post/TimelineService.kt new file mode 100644 index 00000000..681893f7 --- /dev/null +++ b/src/main/kotlin/dev/usbharu/hideout/service/post/TimelineService.kt @@ -0,0 +1,31 @@ +package dev.usbharu.hideout.service.post + +import dev.usbharu.hideout.domain.model.hideout.entity.Post +import dev.usbharu.hideout.domain.model.hideout.entity.Timeline +import dev.usbharu.hideout.query.FollowerQueryService +import dev.usbharu.hideout.repository.TimelineRepository +import org.springframework.stereotype.Service + +@Service +class TimelineService( + private val followerQueryService: FollowerQueryService, + private val timelineRepository: TimelineRepository +) { + suspend fun publishTimeline(post: Post) { + val findFollowersById = followerQueryService.findFollowersById(post.userId) + timelineRepository.saveAll(findFollowersById.map { + Timeline( + id = timelineRepository.generateId(), + userId = it.id, + timelineId = 0, + postId = post.id, + postUserId = post.userId, + createdAt = post.createdAt, + replyId = post.replyId, + repostId = post.repostId, + visibility = post.visibility, + sensitive = post.sensitive + ) + }) + } +} diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 876dbe3d..889d71f4 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -24,7 +24,11 @@ spring: password: "" data: mongodb: - + host: localhost + port: 27017 + database: hideout + # username: hideoutuser + # password: hideoutpass h2: console: From 4268f90fa5b8e205059950d9e48c999544e223c0 Mon Sep 17 00:00:00 2001 From: usbharu <64310155+usbharu@users.noreply.github.com> Date: Fri, 29 Sep 2023 17:49:34 +0900 Subject: [PATCH 04/13] =?UTF-8?q?feat:=20GenerateTimelineService=E3=82=92?= =?UTF-8?q?=E4=BD=9C=E6=88=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../mastodon/MastodonTimelineApiController.kt | 30 ++++++ .../domain/model/hideout/entity/Timeline.kt | 3 +- .../query/mastodon/StatusQueryService.kt | 7 ++ .../query/mastodon/StatusQueryServiceImpl.kt | 102 ++++++++++++++++++ .../repository/MongoTimelineRepository.kt | 11 +- .../service/post/GenerateTimelineService.kt | 18 ++++ .../post/MongoGenerateTimelineService.kt | 32 ++++++ .../hideout/service/post/PostServiceImpl.kt | 12 +-- .../hideout/service/post/TimelineService.kt | 5 +- src/main/resources/openapi/mastodon.yaml | 90 ++++++++++++++++ 10 files changed, 299 insertions(+), 11 deletions(-) create mode 100644 src/main/kotlin/dev/usbharu/hideout/controller/mastodon/MastodonTimelineApiController.kt create mode 100644 src/main/kotlin/dev/usbharu/hideout/query/mastodon/StatusQueryService.kt create mode 100644 src/main/kotlin/dev/usbharu/hideout/query/mastodon/StatusQueryServiceImpl.kt create mode 100644 src/main/kotlin/dev/usbharu/hideout/service/post/GenerateTimelineService.kt create mode 100644 src/main/kotlin/dev/usbharu/hideout/service/post/MongoGenerateTimelineService.kt diff --git a/src/main/kotlin/dev/usbharu/hideout/controller/mastodon/MastodonTimelineApiController.kt b/src/main/kotlin/dev/usbharu/hideout/controller/mastodon/MastodonTimelineApiController.kt new file mode 100644 index 00000000..03f3ace8 --- /dev/null +++ b/src/main/kotlin/dev/usbharu/hideout/controller/mastodon/MastodonTimelineApiController.kt @@ -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>> { + + } +} diff --git a/src/main/kotlin/dev/usbharu/hideout/domain/model/hideout/entity/Timeline.kt b/src/main/kotlin/dev/usbharu/hideout/domain/model/hideout/entity/Timeline.kt index e8e591ea..64f450a8 100644 --- a/src/main/kotlin/dev/usbharu/hideout/domain/model/hideout/entity/Timeline.kt +++ b/src/main/kotlin/dev/usbharu/hideout/domain/model/hideout/entity/Timeline.kt @@ -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 ) diff --git a/src/main/kotlin/dev/usbharu/hideout/query/mastodon/StatusQueryService.kt b/src/main/kotlin/dev/usbharu/hideout/query/mastodon/StatusQueryService.kt new file mode 100644 index 00000000..a4ed048c --- /dev/null +++ b/src/main/kotlin/dev/usbharu/hideout/query/mastodon/StatusQueryService.kt @@ -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> +} diff --git a/src/main/kotlin/dev/usbharu/hideout/query/mastodon/StatusQueryServiceImpl.kt b/src/main/kotlin/dev/usbharu/hideout/query/mastodon/StatusQueryServiceImpl.kt new file mode 100644 index 00000000..fb0cf012 --- /dev/null +++ b/src/main/kotlin/dev/usbharu/hideout/query/mastodon/StatusQueryServiceImpl.kt @@ -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 + } + } + + + } +} diff --git a/src/main/kotlin/dev/usbharu/hideout/repository/MongoTimelineRepository.kt b/src/main/kotlin/dev/usbharu/hideout/repository/MongoTimelineRepository.kt index 716609f5..2e92c7f4 100644 --- a/src/main/kotlin/dev/usbharu/hideout/repository/MongoTimelineRepository.kt +++ b/src/main/kotlin/dev/usbharu/hideout/repository/MongoTimelineRepository.kt @@ -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> } diff --git a/src/main/kotlin/dev/usbharu/hideout/service/post/GenerateTimelineService.kt b/src/main/kotlin/dev/usbharu/hideout/service/post/GenerateTimelineService.kt new file mode 100644 index 00000000..ea1a26a4 --- /dev/null +++ b/src/main/kotlin/dev/usbharu/hideout/service/post/GenerateTimelineService.kt @@ -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> + +} diff --git a/src/main/kotlin/dev/usbharu/hideout/service/post/MongoGenerateTimelineService.kt b/src/main/kotlin/dev/usbharu/hideout/service/post/MongoGenerateTimelineService.kt new file mode 100644 index 00000000..8f1b7b33 --- /dev/null +++ b/src/main/kotlin/dev/usbharu/hideout/service/post/MongoGenerateTimelineService.kt @@ -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) }) + } +} diff --git a/src/main/kotlin/dev/usbharu/hideout/service/post/PostServiceImpl.kt b/src/main/kotlin/dev/usbharu/hideout/service/post/PostServiceImpl.kt index f2531442..2fb9a1e4 100644 --- a/src/main/kotlin/dev/usbharu/hideout/service/post/PostServiceImpl.kt +++ b/src/main/kotlin/dev/usbharu/hideout/service/post/PostServiceImpl.kt @@ -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) } } diff --git a/src/main/kotlin/dev/usbharu/hideout/service/post/TimelineService.kt b/src/main/kotlin/dev/usbharu/hideout/service/post/TimelineService.kt index 681893f7..d0aeefbb 100644 --- a/src/main/kotlin/dev/usbharu/hideout/service/post/TimelineService.kt +++ b/src/main/kotlin/dev/usbharu/hideout/service/post/TimelineService.kt @@ -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 ) }) } diff --git a/src/main/resources/openapi/mastodon.yaml b/src/main/resources/openapi/mastodon.yaml index 94b34dd6..5464a28a 100644 --- a/src/main/resources/openapi/mastodon.yaml +++ b/src/main/resources/openapi/mastodon.yaml @@ -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: From 14022e9301e35ad027ad638bad1874dce32e0663 Mon Sep 17 00:00:00 2001 From: usbharu <64310155+usbharu@users.noreply.github.com> Date: Fri, 29 Sep 2023 18:14:42 +0900 Subject: [PATCH 05/13] =?UTF-8?q?feat:=20Timeline=E3=81=AE=E3=82=A8?= =?UTF-8?q?=E3=83=B3=E3=83=89=E3=83=9D=E3=82=A4=E3=83=B3=E3=83=88=E3=82=92?= =?UTF-8?q?=E4=BD=9C=E6=88=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../mastodon/MastodonTimelineApiController.kt | 32 +++++++-- .../query/mastodon/StatusQueryServiceImpl.kt | 2 + .../repository/MongoTimelineRepository.kt | 2 +- .../api/mastodon/TimelineApiService.kt | 71 +++++++++++++++++++ .../post/MongoGenerateTimelineService.kt | 4 +- 5 files changed, 104 insertions(+), 7 deletions(-) create mode 100644 src/main/kotlin/dev/usbharu/hideout/service/api/mastodon/TimelineApiService.kt diff --git a/src/main/kotlin/dev/usbharu/hideout/controller/mastodon/MastodonTimelineApiController.kt b/src/main/kotlin/dev/usbharu/hideout/controller/mastodon/MastodonTimelineApiController.kt index 03f3ace8..5d7b1bdb 100644 --- a/src/main/kotlin/dev/usbharu/hideout/controller/mastodon/MastodonTimelineApiController.kt +++ b/src/main/kotlin/dev/usbharu/hideout/controller/mastodon/MastodonTimelineApiController.kt @@ -2,18 +2,31 @@ package dev.usbharu.hideout.controller.mastodon import dev.usbharu.hideout.controller.mastodon.generated.TimelineApi import dev.usbharu.hideout.domain.mastodon.model.generated.Status +import dev.usbharu.hideout.service.api.mastodon.TimelineApiService +import kotlinx.coroutines.runBlocking +import org.springframework.http.HttpStatus import org.springframework.http.ResponseEntity +import org.springframework.security.core.context.SecurityContextHolder +import org.springframework.security.oauth2.jwt.Jwt import org.springframework.stereotype.Controller @Controller -class MastodonTimelineApiController : TimelineApi { +class MastodonTimelineApiController(private val timelineApiService: TimelineApiService) : TimelineApi { override fun apiV1TimelinesHomeGet( maxId: String?, sinceId: String?, minId: String?, limit: Int? - ): ResponseEntity<List<Status>> { - + ): ResponseEntity<List<Status>> = runBlocking { + val jwt = SecurityContextHolder.getContext().authentication.principal as Jwt + val homeTimeline = timelineApiService.homeTimeline( + userId = jwt.getClaim<String>("uid").toLong(), + maxId = maxId?.toLongOrNull(), + minId = minId?.toLongOrNull(), + sinceId = sinceId?.toLongOrNull(), + limit = limit ?: 20 + ) + ResponseEntity(homeTimeline, HttpStatus.OK) } override fun apiV1TimelinesPublicGet( @@ -24,7 +37,16 @@ class MastodonTimelineApiController : TimelineApi { sinceId: String?, minId: String?, limit: Int? - ): ResponseEntity<List<Status>> { - + ): ResponseEntity<List<Status>> = runBlocking { + val publicTimeline = timelineApiService.publicTimeline( + localOnly = local ?: false, + remoteOnly = remote ?: false, + mediaOnly = onlyMedia ?: false, + maxId = maxId?.toLongOrNull(), + minId = minId?.toLongOrNull(), + sinceId = sinceId?.toLongOrNull(), + limit = limit ?: 20 + ) + ResponseEntity(publicTimeline, HttpStatus.OK) } } diff --git a/src/main/kotlin/dev/usbharu/hideout/query/mastodon/StatusQueryServiceImpl.kt b/src/main/kotlin/dev/usbharu/hideout/query/mastodon/StatusQueryServiceImpl.kt index fb0cf012..09d0c4a5 100644 --- a/src/main/kotlin/dev/usbharu/hideout/query/mastodon/StatusQueryServiceImpl.kt +++ b/src/main/kotlin/dev/usbharu/hideout/query/mastodon/StatusQueryServiceImpl.kt @@ -6,8 +6,10 @@ 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 org.springframework.stereotype.Repository import java.time.Instant +@Repository class StatusQueryServiceImpl : StatusQueryService { override suspend fun findByPostIds(ids: List<Long>): List<Status> { diff --git a/src/main/kotlin/dev/usbharu/hideout/repository/MongoTimelineRepository.kt b/src/main/kotlin/dev/usbharu/hideout/repository/MongoTimelineRepository.kt index 2e92c7f4..96a69f36 100644 --- a/src/main/kotlin/dev/usbharu/hideout/repository/MongoTimelineRepository.kt +++ b/src/main/kotlin/dev/usbharu/hideout/repository/MongoTimelineRepository.kt @@ -7,7 +7,7 @@ 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( + fun findByUserIdAndTimelineIdAndPostIdBetweenAndIsLocal( userId: Long?, timelineId: Long?, postIdMin: Long?, diff --git a/src/main/kotlin/dev/usbharu/hideout/service/api/mastodon/TimelineApiService.kt b/src/main/kotlin/dev/usbharu/hideout/service/api/mastodon/TimelineApiService.kt new file mode 100644 index 00000000..f27dcd8a --- /dev/null +++ b/src/main/kotlin/dev/usbharu/hideout/service/api/mastodon/TimelineApiService.kt @@ -0,0 +1,71 @@ +package dev.usbharu.hideout.service.api.mastodon + +import dev.usbharu.hideout.domain.mastodon.model.generated.Status +import dev.usbharu.hideout.service.core.Transaction +import dev.usbharu.hideout.service.post.GenerateTimelineService +import org.springframework.stereotype.Service + +interface TimelineApiService { + suspend fun publicTimeline( + localOnly: Boolean = false, + remoteOnly: Boolean = false, + mediaOnly: Boolean = false, + maxId: Long?, + minId: Long?, + sinceId: Long?, + limit: Int = 20 + ): List<Status> + + suspend fun homeTimeline( + userId: Long, + maxId: Long?, + minId: Long?, + sinceId: Long?, + limit: Int = 20 + ): List<Status> +} + + +@Service +class TimelineApiServiceImpl( + private val generateTimelineService: GenerateTimelineService, + private val transaction: Transaction +) : TimelineApiService { + override suspend fun publicTimeline( + localOnly: Boolean, + remoteOnly: Boolean, + mediaOnly: Boolean, + maxId: Long?, + minId: Long?, + sinceId: Long?, + limit: Int + ): List<Status> = transaction.transaction { + generateTimelineService.getTimeline( + forUserId = null, + localOnly = localOnly, + mediaOnly = mediaOnly, + maxId = maxId, + minId = minId, + sinceId = sinceId, + limit = limit + ) + } + + override suspend fun homeTimeline( + userId: Long, + maxId: Long?, + minId: Long?, + sinceId: Long?, + limit: Int + ): List<Status> = transaction.transaction { + generateTimelineService.getTimeline( + forUserId = userId, + localOnly = false, + mediaOnly = false, + maxId = maxId, + minId = minId, + sinceId = sinceId, + limit = limit + ) + } +} diff --git a/src/main/kotlin/dev/usbharu/hideout/service/post/MongoGenerateTimelineService.kt b/src/main/kotlin/dev/usbharu/hideout/service/post/MongoGenerateTimelineService.kt index 8f1b7b33..80ecf75e 100644 --- a/src/main/kotlin/dev/usbharu/hideout/service/post/MongoGenerateTimelineService.kt +++ b/src/main/kotlin/dev/usbharu/hideout/service/post/MongoGenerateTimelineService.kt @@ -6,7 +6,9 @@ import dev.usbharu.hideout.repository.MongoTimelineRepository import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext import org.springframework.data.domain.Pageable +import org.springframework.stereotype.Service +@Service class MongoGenerateTimelineService( private val mongoTimelineRepository: MongoTimelineRepository, private val statusQueryService: StatusQueryService @@ -23,7 +25,7 @@ class MongoGenerateTimelineService( ): List<Status> { val timelines = withContext(Dispatchers.IO) { - mongoTimelineRepository.findByUserIdAndTimelineIdAndPostIdBetweenAndLocal( + mongoTimelineRepository.findByUserIdAndTimelineIdAndPostIdBetweenAndIsLocal( forUserId, 0, maxId, minId, localOnly, Pageable.ofSize(limit) ) } From cab50e61a5a382c5f05381f6caef735794623f0d Mon Sep 17 00:00:00 2001 From: usbharu <64310155+usbharu@users.noreply.github.com> Date: Fri, 29 Sep 2023 19:19:05 +0900 Subject: [PATCH 06/13] =?UTF-8?q?feat:=20=E5=8B=95=E7=9A=84=E3=81=AB?= =?UTF-8?q?=E3=82=AF=E3=82=A8=E3=83=AA=E3=82=92=E6=A7=8B=E7=AF=89=E3=81=99?= =?UTF-8?q?=E3=82=8B=E3=82=88=E3=81=86=E3=81=AB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../post/MongoGenerateTimelineService.kt | 38 ++++++++++++++----- 1 file changed, 28 insertions(+), 10 deletions(-) diff --git a/src/main/kotlin/dev/usbharu/hideout/service/post/MongoGenerateTimelineService.kt b/src/main/kotlin/dev/usbharu/hideout/service/post/MongoGenerateTimelineService.kt index 80ecf75e..d2399c3b 100644 --- a/src/main/kotlin/dev/usbharu/hideout/service/post/MongoGenerateTimelineService.kt +++ b/src/main/kotlin/dev/usbharu/hideout/service/post/MongoGenerateTimelineService.kt @@ -1,17 +1,19 @@ package dev.usbharu.hideout.service.post import dev.usbharu.hideout.domain.mastodon.model.generated.Status +import dev.usbharu.hideout.domain.model.hideout.entity.Timeline 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 +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.Service @Service class MongoGenerateTimelineService( private val mongoTimelineRepository: MongoTimelineRepository, - private val statusQueryService: StatusQueryService + private val statusQueryService: StatusQueryService, + private val mongoTemplate: MongoTemplate ) : GenerateTimelineService { override suspend fun getTimeline( @@ -23,12 +25,28 @@ class MongoGenerateTimelineService( sinceId: Long?, limit: Int ): List<Status> { - val timelines = - withContext(Dispatchers.IO) { - mongoTimelineRepository.findByUserIdAndTimelineIdAndPostIdBetweenAndIsLocal( - forUserId, 0, maxId, minId, localOnly, Pageable.ofSize(limit) - ) - } + + + val query = Query() + if (forUserId != null) { + val criteria = Criteria.where("userId").`is`(forUserId) + query.addCriteria(criteria) + } + if (localOnly) { + val criteria = Criteria.where("isLocal").`is`(true) + query.addCriteria(criteria) + } + if (maxId != null) { + val criteria = Criteria.where("postId").lt(maxId) + query.addCriteria(criteria) + } + if (minId != null) { + val criteria = Criteria.where("postId").gt(minId) + query.addCriteria(criteria) + } + + val timelines = mongoTemplate.find(query.limit(limit), Timeline::class.java) + return statusQueryService.findByPostIds(timelines.flatMap { setOfNotNull(it.postId, it.replyId, it.repostId) }) } } From 41f7fc605cdf7551551e5d8d745a6dd97546d25f Mon Sep 17 00:00:00 2001 From: usbharu <64310155+usbharu@users.noreply.github.com> Date: Fri, 29 Sep 2023 19:19:30 +0900 Subject: [PATCH 07/13] =?UTF-8?q?feat:=20=E3=82=BF=E3=82=A4=E3=83=A0?= =?UTF-8?q?=E3=83=A9=E3=82=A4=E3=83=B3=E3=81=AE=E6=A7=8B=E7=AF=89=E6=99=82?= =?UTF-8?q?=E3=81=AB=E8=87=AA=E5=88=86=E8=87=AA=E8=BA=AB=E3=82=92=E5=AF=BE?= =?UTF-8?q?=E8=B1=A1=E3=81=AB=E5=90=AB=E3=82=81=E3=82=8B=E3=82=88=E3=81=86?= =?UTF-8?q?=E3=81=AB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../hideout/service/post/TimelineService.kt | 29 +++++++++++++++++-- 1 file changed, 26 insertions(+), 3 deletions(-) diff --git a/src/main/kotlin/dev/usbharu/hideout/service/post/TimelineService.kt b/src/main/kotlin/dev/usbharu/hideout/service/post/TimelineService.kt index d0aeefbb..944b1dda 100644 --- a/src/main/kotlin/dev/usbharu/hideout/service/post/TimelineService.kt +++ b/src/main/kotlin/dev/usbharu/hideout/service/post/TimelineService.kt @@ -2,18 +2,23 @@ package dev.usbharu.hideout.service.post import dev.usbharu.hideout.domain.model.hideout.entity.Post import dev.usbharu.hideout.domain.model.hideout.entity.Timeline +import dev.usbharu.hideout.domain.model.hideout.entity.Visibility import dev.usbharu.hideout.query.FollowerQueryService +import dev.usbharu.hideout.query.UserQueryService import dev.usbharu.hideout.repository.TimelineRepository import org.springframework.stereotype.Service @Service class TimelineService( private val followerQueryService: FollowerQueryService, + private val userQueryService: UserQueryService, private val timelineRepository: TimelineRepository ) { suspend fun publishTimeline(post: Post, isLocal: Boolean) { - val findFollowersById = followerQueryService.findFollowersById(post.userId) - timelineRepository.saveAll(findFollowersById.map { + // 自分自身も含める必要がある + val user = userQueryService.findById(post.userId) + val findFollowersById = followerQueryService.findFollowersById(post.userId).plus(user) + val timelines = findFollowersById.map { Timeline( id = timelineRepository.generateId(), userId = it.id, @@ -27,6 +32,24 @@ class TimelineService( sensitive = post.sensitive, isLocal = isLocal ) - }) + }.toMutableList() + if (post.visibility == Visibility.PUBLIC) { + timelines.add( + Timeline( + id = timelineRepository.generateId(), + userId = 0, + timelineId = 0, + postId = post.id, + postUserId = post.userId, + createdAt = post.createdAt, + replyId = post.replyId, + repostId = post.repostId, + visibility = post.visibility, + sensitive = post.sensitive, + isLocal = isLocal + ) + ) + } + timelineRepository.saveAll(timelines) } } From af0d97f1a8dcf92e36d0a4d2be52005ed12baf1e Mon Sep 17 00:00:00 2001 From: usbharu <64310155+usbharu@users.noreply.github.com> Date: Fri, 29 Sep 2023 19:20:57 +0900 Subject: [PATCH 08/13] =?UTF-8?q?fix:=20=E3=83=AA=E3=83=A2=E3=83=BC?= =?UTF-8?q?=E3=83=88=E3=83=A6=E3=83=BC=E3=82=B6=E3=83=BC=E3=81=AE=E5=A0=B4?= =?UTF-8?q?=E5=90=88=E3=81=AF=E3=82=BF=E3=82=A4=E3=83=A0=E3=83=A9=E3=82=A4?= =?UTF-8?q?=E3=83=B3=E3=81=AB=E8=87=AA=E5=88=86=E8=87=AA=E8=BA=AB=E3=82=92?= =?UTF-8?q?=E5=90=AB=E3=82=81=E3=81=AA=E3=81=84=E3=82=88=E3=81=86=E3=81=AB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../dev/usbharu/hideout/service/post/TimelineService.kt | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/main/kotlin/dev/usbharu/hideout/service/post/TimelineService.kt b/src/main/kotlin/dev/usbharu/hideout/service/post/TimelineService.kt index 944b1dda..97f83a08 100644 --- a/src/main/kotlin/dev/usbharu/hideout/service/post/TimelineService.kt +++ b/src/main/kotlin/dev/usbharu/hideout/service/post/TimelineService.kt @@ -15,9 +15,12 @@ class TimelineService( private val timelineRepository: TimelineRepository ) { suspend fun publishTimeline(post: Post, isLocal: Boolean) { - // 自分自身も含める必要がある - val user = userQueryService.findById(post.userId) - val findFollowersById = followerQueryService.findFollowersById(post.userId).plus(user) + val findFollowersById = followerQueryService.findFollowersById(post.userId).toMutableList() + if (isLocal) { + // 自分自身も含める必要がある + val user = userQueryService.findById(post.userId) + findFollowersById.add(user) + } val timelines = findFollowersById.map { Timeline( id = timelineRepository.generateId(), From 6f9aa2a37801f717c0e13aacfb9297cf625dbd54 Mon Sep 17 00:00:00 2001 From: usbharu <64310155+usbharu@users.noreply.github.com> Date: Sat, 30 Sep 2023 09:55:34 +0900 Subject: [PATCH 09/13] =?UTF-8?q?refactor:=20=E4=B8=8D=E8=A6=81=E3=81=AA?= =?UTF-8?q?=E4=BE=9D=E5=AD=98=E3=82=92=E5=89=8A=E9=99=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../hideout/service/post/MongoGenerateTimelineService.kt | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/main/kotlin/dev/usbharu/hideout/service/post/MongoGenerateTimelineService.kt b/src/main/kotlin/dev/usbharu/hideout/service/post/MongoGenerateTimelineService.kt index d2399c3b..4c4a3808 100644 --- a/src/main/kotlin/dev/usbharu/hideout/service/post/MongoGenerateTimelineService.kt +++ b/src/main/kotlin/dev/usbharu/hideout/service/post/MongoGenerateTimelineService.kt @@ -3,7 +3,6 @@ package dev.usbharu.hideout.service.post import dev.usbharu.hideout.domain.mastodon.model.generated.Status import dev.usbharu.hideout.domain.model.hideout.entity.Timeline import dev.usbharu.hideout.query.mastodon.StatusQueryService -import dev.usbharu.hideout.repository.MongoTimelineRepository import org.springframework.data.mongodb.core.MongoTemplate import org.springframework.data.mongodb.core.query.Criteria import org.springframework.data.mongodb.core.query.Query @@ -11,7 +10,6 @@ import org.springframework.stereotype.Service @Service class MongoGenerateTimelineService( - private val mongoTimelineRepository: MongoTimelineRepository, private val statusQueryService: StatusQueryService, private val mongoTemplate: MongoTemplate ) : From cca71aa9413fc911950b5bdd5d356dbcc2e86967 Mon Sep 17 00:00:00 2001 From: usbharu <64310155+usbharu@users.noreply.github.com> Date: Sat, 30 Sep 2023 10:38:23 +0900 Subject: [PATCH 10/13] =?UTF-8?q?feat:=20public=E3=82=BF=E3=82=A4=E3=83=A0?= =?UTF-8?q?=E3=83=A9=E3=82=A4=E3=83=B3=E3=81=AF=E5=B0=82=E7=94=A8=E3=81=AE?= =?UTF-8?q?=E7=89=B9=E6=AE=8A=E3=83=A6=E3=83=BC=E3=82=B6=E3=83=BC=E3=81=A7?= =?UTF-8?q?=E8=A6=8B=E3=82=8B=E3=82=88=E3=81=86=E3=81=AB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../usbharu/hideout/service/api/mastodon/TimelineApiService.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/kotlin/dev/usbharu/hideout/service/api/mastodon/TimelineApiService.kt b/src/main/kotlin/dev/usbharu/hideout/service/api/mastodon/TimelineApiService.kt index f27dcd8a..677b65ba 100644 --- a/src/main/kotlin/dev/usbharu/hideout/service/api/mastodon/TimelineApiService.kt +++ b/src/main/kotlin/dev/usbharu/hideout/service/api/mastodon/TimelineApiService.kt @@ -41,7 +41,7 @@ class TimelineApiServiceImpl( limit: Int ): List<Status> = transaction.transaction { generateTimelineService.getTimeline( - forUserId = null, + forUserId = 0, localOnly = localOnly, mediaOnly = mediaOnly, maxId = maxId, From 8297d578bcf7bb599c66bd30a37f13a947627db0 Mon Sep 17 00:00:00 2001 From: usbharu <64310155+usbharu@users.noreply.github.com> Date: Sat, 30 Sep 2023 11:14:30 +0900 Subject: [PATCH 11/13] =?UTF-8?q?test:=20=E3=83=86=E3=82=B9=E3=83=88?= =?UTF-8?q?=E3=82=92=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../service/ap/APNoteServiceImplTest.kt | 20 ++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/src/test/kotlin/dev/usbharu/hideout/service/ap/APNoteServiceImplTest.kt b/src/test/kotlin/dev/usbharu/hideout/service/ap/APNoteServiceImplTest.kt index 26e1ae4e..4c40d000 100644 --- a/src/test/kotlin/dev/usbharu/hideout/service/ap/APNoteServiceImplTest.kt +++ b/src/test/kotlin/dev/usbharu/hideout/service/ap/APNoteServiceImplTest.kt @@ -80,15 +80,16 @@ class APNoteServiceImplTest { val jobQueueParentService = mock<JobQueueParentService>() val activityPubNoteService = APNoteServiceImpl( - mock(), - jobQueueParentService, - mock(), - mock(), - userQueryService, - followerQueryService, - mock(), + httpClient = mock(), + jobQueueParentService = jobQueueParentService, + postRepository = mock(), + apUserService = mock(), + userQueryService = userQueryService, + followerQueryService = followerQueryService, + postQueryService = mock(), objectMapper = objectMapper, - applicationConfig = testApplicationConfig + applicationConfig = testApplicationConfig, + postService = mock() ) val postEntity = Post.of( 1L, @@ -121,7 +122,8 @@ class APNoteServiceImplTest { followerQueryService = mock(), postQueryService = mock(), objectMapper = objectMapper, - applicationConfig = testApplicationConfig + applicationConfig = testApplicationConfig, + postService = mock() ) activityPubNoteService.createNoteJob( JobProps( From 0d7e4aaedcbcf490417d501a109c642ca0b303cf Mon Sep 17 00:00:00 2001 From: usbharu <64310155+usbharu@users.noreply.github.com> Date: Sat, 30 Sep 2023 11:19:06 +0900 Subject: [PATCH 12/13] =?UTF-8?q?style:=20=E3=82=B9=E3=82=BF=E3=82=A4?= =?UTF-8?q?=E3=83=AB=E3=82=92=E8=AA=BF=E6=95=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/mastodon/MastodonStatusesApiContoller.kt | 2 +- .../usbharu/hideout/query/mastodon/StatusQueryServiceImpl.kt | 4 +--- .../dev/usbharu/hideout/repository/MongoTimelineRepository.kt | 1 + .../hideout/repository/MongoTimelineRepositoryWrapper.kt | 1 + .../kotlin/dev/usbharu/hideout/service/ap/APNoteService.kt | 1 + .../hideout/service/api/mastodon/TimelineApiService.kt | 2 +- .../usbharu/hideout/service/post/GenerateTimelineService.kt | 2 +- .../hideout/service/post/MongoGenerateTimelineService.kt | 2 -- 8 files changed, 7 insertions(+), 8 deletions(-) diff --git a/src/main/kotlin/dev/usbharu/hideout/controller/mastodon/MastodonStatusesApiContoller.kt b/src/main/kotlin/dev/usbharu/hideout/controller/mastodon/MastodonStatusesApiContoller.kt index 0d40471e..d5111577 100644 --- a/src/main/kotlin/dev/usbharu/hideout/controller/mastodon/MastodonStatusesApiContoller.kt +++ b/src/main/kotlin/dev/usbharu/hideout/controller/mastodon/MastodonStatusesApiContoller.kt @@ -22,5 +22,5 @@ class MastodonStatusesApiContoller(private val statusesApiService: StatusesApiSe statusesApiService.postStatus(statusesRequest, jwt.getClaim<String>("uid").toLong()), HttpStatus.OK ) - } + } } diff --git a/src/main/kotlin/dev/usbharu/hideout/query/mastodon/StatusQueryServiceImpl.kt b/src/main/kotlin/dev/usbharu/hideout/query/mastodon/StatusQueryServiceImpl.kt index 09d0c4a5..71d3e0e1 100644 --- a/src/main/kotlin/dev/usbharu/hideout/query/mastodon/StatusQueryServiceImpl.kt +++ b/src/main/kotlin/dev/usbharu/hideout/query/mastodon/StatusQueryServiceImpl.kt @@ -11,8 +11,8 @@ import java.time.Instant @Repository class StatusQueryServiceImpl : StatusQueryService { + @Suppress("LongMethod") override suspend fun findByPostIds(ids: List<Long>): List<Status> { - val pairs = Posts.innerJoin(Users, onColumn = { userId }, otherColumn = { id }) .select { Posts.id inList ids } .map { @@ -98,7 +98,5 @@ class StatusQueryServiceImpl : StatusQueryService { it } } - - } } diff --git a/src/main/kotlin/dev/usbharu/hideout/repository/MongoTimelineRepository.kt b/src/main/kotlin/dev/usbharu/hideout/repository/MongoTimelineRepository.kt index 96a69f36..3dcd35b1 100644 --- a/src/main/kotlin/dev/usbharu/hideout/repository/MongoTimelineRepository.kt +++ b/src/main/kotlin/dev/usbharu/hideout/repository/MongoTimelineRepository.kt @@ -4,6 +4,7 @@ import dev.usbharu.hideout.domain.model.hideout.entity.Timeline import org.springframework.data.domain.Pageable import org.springframework.data.mongodb.repository.MongoRepository +@Suppress("LongParameterList") interface MongoTimelineRepository : MongoRepository<Timeline, Long> { fun findByUserId(id: Long): List<Timeline> fun findByUserIdAndTimelineId(userId: Long, timelineId: Long): List<Timeline> diff --git a/src/main/kotlin/dev/usbharu/hideout/repository/MongoTimelineRepositoryWrapper.kt b/src/main/kotlin/dev/usbharu/hideout/repository/MongoTimelineRepositoryWrapper.kt index 98674637..d4908948 100644 --- a/src/main/kotlin/dev/usbharu/hideout/repository/MongoTimelineRepositoryWrapper.kt +++ b/src/main/kotlin/dev/usbharu/hideout/repository/MongoTimelineRepositoryWrapper.kt @@ -7,6 +7,7 @@ import kotlinx.coroutines.withContext import org.springframework.stereotype.Repository @Repository +@Suppress("InjectDispatcher") class MongoTimelineRepositoryWrapper( private val mongoTimelineRepository: MongoTimelineRepository, private val idGenerateService: IdGenerateService diff --git a/src/main/kotlin/dev/usbharu/hideout/service/ap/APNoteService.kt b/src/main/kotlin/dev/usbharu/hideout/service/ap/APNoteService.kt index c417a861..b4c260e1 100644 --- a/src/main/kotlin/dev/usbharu/hideout/service/ap/APNoteService.kt +++ b/src/main/kotlin/dev/usbharu/hideout/service/ap/APNoteService.kt @@ -38,6 +38,7 @@ interface APNoteService { } @Service +@Suppress("LongParameterList") class APNoteServiceImpl( private val httpClient: HttpClient, private val jobQueueParentService: JobQueueParentService, diff --git a/src/main/kotlin/dev/usbharu/hideout/service/api/mastodon/TimelineApiService.kt b/src/main/kotlin/dev/usbharu/hideout/service/api/mastodon/TimelineApiService.kt index 677b65ba..2dfc1f24 100644 --- a/src/main/kotlin/dev/usbharu/hideout/service/api/mastodon/TimelineApiService.kt +++ b/src/main/kotlin/dev/usbharu/hideout/service/api/mastodon/TimelineApiService.kt @@ -5,6 +5,7 @@ import dev.usbharu.hideout.service.core.Transaction import dev.usbharu.hideout.service.post.GenerateTimelineService import org.springframework.stereotype.Service +@Suppress("LongParameterList") interface TimelineApiService { suspend fun publicTimeline( localOnly: Boolean = false, @@ -25,7 +26,6 @@ interface TimelineApiService { ): List<Status> } - @Service class TimelineApiServiceImpl( private val generateTimelineService: GenerateTimelineService, diff --git a/src/main/kotlin/dev/usbharu/hideout/service/post/GenerateTimelineService.kt b/src/main/kotlin/dev/usbharu/hideout/service/post/GenerateTimelineService.kt index ea1a26a4..34f42678 100644 --- a/src/main/kotlin/dev/usbharu/hideout/service/post/GenerateTimelineService.kt +++ b/src/main/kotlin/dev/usbharu/hideout/service/post/GenerateTimelineService.kt @@ -4,6 +4,7 @@ import dev.usbharu.hideout.domain.mastodon.model.generated.Status import org.springframework.stereotype.Service @Service +@Suppress("LongParameterList") interface GenerateTimelineService { suspend fun getTimeline( forUserId: Long? = null, @@ -14,5 +15,4 @@ interface GenerateTimelineService { sinceId: Long? = null, limit: Int = 20 ): List<Status> - } diff --git a/src/main/kotlin/dev/usbharu/hideout/service/post/MongoGenerateTimelineService.kt b/src/main/kotlin/dev/usbharu/hideout/service/post/MongoGenerateTimelineService.kt index 4c4a3808..fa5c9800 100644 --- a/src/main/kotlin/dev/usbharu/hideout/service/post/MongoGenerateTimelineService.kt +++ b/src/main/kotlin/dev/usbharu/hideout/service/post/MongoGenerateTimelineService.kt @@ -23,8 +23,6 @@ class MongoGenerateTimelineService( sinceId: Long?, limit: Int ): List<Status> { - - val query = Query() if (forUserId != null) { val criteria = Criteria.where("userId").`is`(forUserId) From 4ca9fe6cfbf45317b57895b365883364848b0b2a Mon Sep 17 00:00:00 2001 From: usbharu <64310155+usbharu@users.noreply.github.com> Date: Sat, 30 Sep 2023 11:25:31 +0900 Subject: [PATCH 13/13] =?UTF-8?q?style:=20=E3=82=B9=E3=82=BF=E3=82=A4?= =?UTF-8?q?=E3=83=AB=E3=82=92=E8=AA=BF=E6=95=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../usbharu/hideout/repository/MongoTimelineRepository.kt | 2 +- .../hideout/repository/MongoTimelineRepositoryWrapper.kt | 5 ++--- .../dev/usbharu/hideout/service/ap/APNoteService.kt | 8 ++++---- .../dev/usbharu/hideout/service/post/PostServiceImpl.kt | 4 +--- 4 files changed, 8 insertions(+), 11 deletions(-) diff --git a/src/main/kotlin/dev/usbharu/hideout/repository/MongoTimelineRepository.kt b/src/main/kotlin/dev/usbharu/hideout/repository/MongoTimelineRepository.kt index 3dcd35b1..0b8937cf 100644 --- a/src/main/kotlin/dev/usbharu/hideout/repository/MongoTimelineRepository.kt +++ b/src/main/kotlin/dev/usbharu/hideout/repository/MongoTimelineRepository.kt @@ -4,7 +4,7 @@ import dev.usbharu.hideout.domain.model.hideout.entity.Timeline import org.springframework.data.domain.Pageable import org.springframework.data.mongodb.repository.MongoRepository -@Suppress("LongParameterList") +@Suppress("LongParameterList", "FunctionMaxLength") interface MongoTimelineRepository : MongoRepository<Timeline, Long> { fun findByUserId(id: Long): List<Timeline> fun findByUserIdAndTimelineId(userId: Long, timelineId: Long): List<Timeline> diff --git a/src/main/kotlin/dev/usbharu/hideout/repository/MongoTimelineRepositoryWrapper.kt b/src/main/kotlin/dev/usbharu/hideout/repository/MongoTimelineRepositoryWrapper.kt index d4908948..a3e31b6f 100644 --- a/src/main/kotlin/dev/usbharu/hideout/repository/MongoTimelineRepositoryWrapper.kt +++ b/src/main/kotlin/dev/usbharu/hideout/repository/MongoTimelineRepositoryWrapper.kt @@ -21,9 +21,8 @@ class MongoTimelineRepositoryWrapper( } } - override suspend fun saveAll(timelines: List<Timeline>): List<Timeline> { - return mongoTimelineRepository.saveAll(timelines) - } + override suspend fun saveAll(timelines: List<Timeline>): List<Timeline> = + mongoTimelineRepository.saveAll(timelines) override suspend fun findByUserId(id: Long): List<Timeline> { return withContext(Dispatchers.IO) { diff --git a/src/main/kotlin/dev/usbharu/hideout/service/ap/APNoteService.kt b/src/main/kotlin/dev/usbharu/hideout/service/ap/APNoteService.kt index b4c260e1..2b6ce229 100644 --- a/src/main/kotlin/dev/usbharu/hideout/service/ap/APNoteService.kt +++ b/src/main/kotlin/dev/usbharu/hideout/service/ap/APNoteService.kt @@ -190,11 +190,11 @@ class APNoteServiceImpl( override suspend fun fetchNote(note: Note, targetActor: String?): Note = note(note, targetActor, note.id ?: throw IllegalArgumentException("note.id is null")) - companion object { - const val public: String = "https://www.w3.org/ns/activitystreams#Public" - } - override suspend fun run(post: Post) { createNote(post) } + + companion object { + const val public: String = "https://www.w3.org/ns/activitystreams#Public" + } } diff --git a/src/main/kotlin/dev/usbharu/hideout/service/post/PostServiceImpl.kt b/src/main/kotlin/dev/usbharu/hideout/service/post/PostServiceImpl.kt index 2fb9a1e4..a86c2227 100644 --- a/src/main/kotlin/dev/usbharu/hideout/service/post/PostServiceImpl.kt +++ b/src/main/kotlin/dev/usbharu/hideout/service/post/PostServiceImpl.kt @@ -23,9 +23,7 @@ class PostServiceImpl( return create } - override suspend fun createRemote(post: Post): Post { - return internalCreate(post, false) - } + override suspend fun createRemote(post: Post): Post = internalCreate(post, false) override fun addInterceptor(postCreateInterceptor: PostCreateInterceptor) { interceptors.add(postCreateInterceptor)