From 6b535a042aa645badf86f4a21079f1f30792adf4 Mon Sep 17 00:00:00 2001 From: usbharu <64310155+usbharu@users.noreply.github.com> Date: Tue, 12 Dec 2023 00:03:48 +0900 Subject: [PATCH 1/6] =?UTF-8?q?feat:=20=E3=83=95=E3=82=A9=E3=83=AD?= =?UTF-8?q?=E3=83=BC=E6=89=BF=E8=AA=8D=E5=88=B6=E3=83=A6=E3=83=BC=E3=82=B6?= =?UTF-8?q?=E3=83=BC=E3=81=A8=E3=81=99=E3=82=8B=E3=81=8B=E3=81=AE=E8=A8=AD?= =?UTF-8?q?=E5=AE=9A=E3=82=92Mastodon=20API=E3=81=8B=E3=82=89=E8=A1=8C?= =?UTF-8?q?=E3=81=88=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 --- .../core/service/user/UpdateUserDto.kt | 12 +++ .../hideout/core/service/user/UserService.kt | 2 + .../core/service/user/UserServiceImpl.kt | 21 +++++ .../account/MastodonAccountApiController.kt | 10 +++ .../service/account/AccountApiService.kt | 56 ++++++++++++- src/main/resources/openapi/mastodon.yaml | 80 ++++++++++++++++++- 6 files changed, 176 insertions(+), 5 deletions(-) create mode 100644 src/main/kotlin/dev/usbharu/hideout/core/service/user/UpdateUserDto.kt diff --git a/src/main/kotlin/dev/usbharu/hideout/core/service/user/UpdateUserDto.kt b/src/main/kotlin/dev/usbharu/hideout/core/service/user/UpdateUserDto.kt new file mode 100644 index 00000000..5406fd02 --- /dev/null +++ b/src/main/kotlin/dev/usbharu/hideout/core/service/user/UpdateUserDto.kt @@ -0,0 +1,12 @@ +package dev.usbharu.hideout.core.service.user + +import dev.usbharu.hideout.core.domain.model.media.Media + +data class UpdateUserDto( + val screenName: String, + val description: String, + val avatarMedia: Media?, + val headerMedia: Media?, + val autoAcceptFollowRequest: Boolean, + val autoAcceptFolloweeFollowRequest: Boolean +) diff --git a/src/main/kotlin/dev/usbharu/hideout/core/service/user/UserService.kt b/src/main/kotlin/dev/usbharu/hideout/core/service/user/UserService.kt index 6ef40b6e..06715df5 100644 --- a/src/main/kotlin/dev/usbharu/hideout/core/service/user/UserService.kt +++ b/src/main/kotlin/dev/usbharu/hideout/core/service/user/UserService.kt @@ -11,4 +11,6 @@ interface UserService { suspend fun createLocalUser(user: UserCreateDto): Actor suspend fun createRemoteUser(user: RemoteUserCreateDto): Actor + + suspend fun updateUser(userId: Long, updateUserDto: UpdateUserDto) } diff --git a/src/main/kotlin/dev/usbharu/hideout/core/service/user/UserServiceImpl.kt b/src/main/kotlin/dev/usbharu/hideout/core/service/user/UserServiceImpl.kt index 49ff8091..f126fbf7 100644 --- a/src/main/kotlin/dev/usbharu/hideout/core/service/user/UserServiceImpl.kt +++ b/src/main/kotlin/dev/usbharu/hideout/core/service/user/UserServiceImpl.kt @@ -94,6 +94,27 @@ class UserServiceImpl( } } + override suspend fun updateUser(userId: Long, updateUserDto: UpdateUserDto) { + val userDetail = userDetailRepository.findByActorId(userId) + ?: throw IllegalArgumentException("userId: $userId was not found.") + + val actor = actorRepository.findById(userId) ?: throw IllegalArgumentException("userId $userId was not found.") + + actorRepository.save( + actor.copy( + screenName = updateUserDto.screenName, + description = updateUserDto.description, + ) + ) + + userDetailRepository.save( + userDetail.copy( + autoAcceptFollowRequest = updateUserDto.autoAcceptFollowRequest, + autoAcceptFolloweeFollowRequest = updateUserDto.autoAcceptFolloweeFollowRequest + ) + ) + } + companion object { private val logger = LoggerFactory.getLogger(UserServiceImpl::class.java) } diff --git a/src/main/kotlin/dev/usbharu/hideout/mastodon/interfaces/api/account/MastodonAccountApiController.kt b/src/main/kotlin/dev/usbharu/hideout/mastodon/interfaces/api/account/MastodonAccountApiController.kt index f9af1c7d..b516c3ec 100644 --- a/src/main/kotlin/dev/usbharu/hideout/mastodon/interfaces/api/account/MastodonAccountApiController.kt +++ b/src/main/kotlin/dev/usbharu/hideout/mastodon/interfaces/api/account/MastodonAccountApiController.kt @@ -143,4 +143,14 @@ class MastodonAccountApiController( return ResponseEntity.ok(removeFromFollowers) } + + override suspend fun apiV1AccountsUpdateCredentialsPatch(updateCredentials: UpdateCredentials?): ResponseEntity { + val principal = SecurityContextHolder.getContext().getAuthentication().principal as Jwt + + val userid = principal.getClaim("uid").toLong() + + val removeFromFollowers = accountApiService.updateProfile(userid, updateCredentials) + + return ResponseEntity.ok(removeFromFollowers) + } } diff --git a/src/main/kotlin/dev/usbharu/hideout/mastodon/service/account/AccountApiService.kt b/src/main/kotlin/dev/usbharu/hideout/mastodon/service/account/AccountApiService.kt index 7a19b065..b85b7d67 100644 --- a/src/main/kotlin/dev/usbharu/hideout/mastodon/service/account/AccountApiService.kt +++ b/src/main/kotlin/dev/usbharu/hideout/mastodon/service/account/AccountApiService.kt @@ -2,10 +2,13 @@ package dev.usbharu.hideout.mastodon.service.account import dev.usbharu.hideout.application.external.Transaction import dev.usbharu.hideout.core.domain.model.relationship.RelationshipRepository +import dev.usbharu.hideout.core.service.media.MediaService import dev.usbharu.hideout.core.service.relationship.RelationshipService +import dev.usbharu.hideout.core.service.user.UpdateUserDto import dev.usbharu.hideout.core.service.user.UserCreateDto import dev.usbharu.hideout.core.service.user.UserService import dev.usbharu.hideout.domain.mastodon.model.generated.* +import dev.usbharu.hideout.mastodon.interfaces.api.media.MediaRequest import dev.usbharu.hideout.mastodon.query.StatusQueryService import org.slf4j.LoggerFactory import org.springframework.stereotype.Service @@ -45,6 +48,7 @@ interface AccountApiService { suspend fun unblock(userid: Long, target: Long): Relationship suspend fun unfollow(userid: Long, target: Long): Relationship suspend fun removeFromFollowers(userid: Long, target: Long): Relationship + suspend fun updateProfile(userid: Long, updateCredentials: UpdateCredentials?): Account } @Service @@ -54,7 +58,8 @@ class AccountApiServiceImpl( private val userService: UserService, private val statusQueryService: StatusQueryService, private val relationshipService: RelationshipService, - private val relationshipRepository: RelationshipRepository + private val relationshipRepository: RelationshipRepository, + private val mediaService: MediaService ) : AccountApiService { override suspend fun accountsStatuses( @@ -153,6 +158,51 @@ class AccountApiServiceImpl( return@transaction fetchRelationship(userid, target) } + override suspend fun updateProfile(userid: Long, updateCredentials: UpdateCredentials?): Account = + transaction.transaction { + + val avatarMedia = if (updateCredentials?.avatar != null) { + mediaService.uploadLocalMedia( + MediaRequest( + updateCredentials.avatar, + null, + null, + null + ) + ) + } else { + null + } + + val headerMedia = if (updateCredentials?.header != null) { + mediaService.uploadLocalMedia( + MediaRequest( + updateCredentials.header, + null, + null, + null + ) + ) + } else { + null + } + + + val account = accountService.findById(userid) + + val updateUserDto = UpdateUserDto( + screenName = updateCredentials?.displayName ?: account.displayName, + description = updateCredentials?.note ?: account.note, + avatarMedia = avatarMedia, + headerMedia = headerMedia, + autoAcceptFollowRequest = updateCredentials?.locked ?: account.locked, + autoAcceptFolloweeFollowRequest = false + ) + userService.updateUser(userid, updateUserDto) + + accountService.findById(userid) + } + private fun from(account: Account): CredentialAccount { return CredentialAccount( id = account.id, @@ -180,10 +230,10 @@ class AccountApiServiceImpl( suspendex = account.suspendex, limited = account.limited, followingCount = account.followingCount, - source = CredentialAccountSource( + source = AccountSource( account.note, account.fields, - CredentialAccountSource.Privacy.public, + AccountSource.Privacy.public, false, 0 ), diff --git a/src/main/resources/openapi/mastodon.yaml b/src/main/resources/openapi/mastodon.yaml index a43da7de..49e382f6 100644 --- a/src/main/resources/openapi/mastodon.yaml +++ b/src/main/resources/openapi/mastodon.yaml @@ -237,6 +237,27 @@ paths: items: $ref: "#/components/schemas/Relationship" + /api/v1/accounts/update_credentials: + patch: + tags: + - account + security: + - OAuth2: + - "write:accounts" + requestBody: + required: false + content: + application/json: + schema: + $ref: "#/components/schemas/UpdateCredentials" + responses: + 200: + description: 成功 + content: + application/json: + schema: + $ref: "#/components/schemas/Account" + /api/v1/accounts/{id}: get: tags: @@ -664,6 +685,9 @@ components: type: integer following_count: type: integer + source: + $ref: "#/components/schemas/AccountSource" + required: - id - username @@ -747,7 +771,7 @@ components: following_count: type: integer source: - $ref: "#/components/schemas/CredentialAccountSource" + $ref: "#/components/schemas/AccountSource" role: $ref: "#/components/schemas/Role" required: @@ -774,7 +798,7 @@ components: - followers_count - source - CredentialAccountSource: + AccountSource: type: object properties: note: @@ -1632,6 +1656,58 @@ components: items: type: string + UpdateCredentials: + type: object + properties: + display_name: + type: string + note: + type: string + avatar: + type: string + format: binary + header: + type: string + format: binary + locked: + type: boolean + bot: + type: boolean + discoverable: + type: boolean + hide_collections: + type: boolean + indexable: + type: boolean + fields_attributes: + type: object + additionalProperties: + $ref: "#/components/schemas/UpdateCredentialsFieldsAttributes" + source: + $ref: "#/components/schemas/UpdateCredentialsSource" + + UpdateCredentialsSource: + type: object + properties: + privacy: + type: string + enum: + - public + - unlisted + - private + sensitive: + type: boolean + language: + type: string + + UpdateCredentialsFieldsAttributes: + type: object + properties: + name: + type: string + value: + type: string + securitySchemes: OAuth2: type: oauth2 From fe74d1e30bf08ffa64273d7d9d4e71f0adc3eba6 Mon Sep 17 00:00:00 2001 From: usbharu <64310155+usbharu@users.noreply.github.com> Date: Tue, 12 Dec 2023 01:32:55 +0900 Subject: [PATCH 2/6] =?UTF-8?q?feat:=20=E3=83=95=E3=82=A9=E3=83=AD?= =?UTF-8?q?=E3=83=BC=E6=89=BF=E8=AA=8D=E5=88=B6=E3=83=A6=E3=83=BC=E3=82=B6?= =?UTF-8?q?=E3=83=BC=E3=81=8B=E3=81=AE=E6=83=85=E5=A0=B1=E3=82=92ActivityP?= =?UTF-8?q?ub=E5=81=B4=E3=81=A7=E3=82=82=E6=8C=81=E3=81=9F=E3=81=9B?= =?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 --- src/e2eTest/resources/oauth2/user.sql | 4 +-- ...iV1AccountsIdFollowPost フォローできる.sql | 6 ++--- ...でフォロワーがfollowers投稿を取得できる.sql | 11 +++----- ...証でフォロワーがpublic投稿を取得できる.sql | 12 +++------ ...でフォロワーがunlisted投稿を取得できる.sql | 11 +++----- ...稿はattachmentにDocumentとして画像が存在する.sql | 4 +-- ...イになっている投稿はinReplyToが存在する.sql | 4 +-- ...でfollowers投稿を取得しようとすると404.sql | 4 +-- .../sql/note/匿名でpublic投稿を取得できる.sql | 4 +-- .../note/匿名でunlisted投稿を取得できる.sql | 4 +-- src/intTest/resources/sql/test-user.sql | 4 +-- src/intTest/resources/sql/test-user2.sql | 4 +-- .../activitypub/domain/model/Person.kt | 26 ++++++++++++++++--- .../service/objects/user/APUserService.kt | 9 ++++--- .../hideout/core/domain/model/actor/Actor.kt | 14 +++++++--- .../exposed/UserResultRowMapper.kt | 3 ++- .../exposedrepository/ActorRepositoryImpl.kt | 3 +++ .../core/service/user/RemoteUserCreateDto.kt | 3 ++- .../core/service/user/UserServiceImpl.kt | 6 +++-- .../resources/db/migration/V1__Init_DB.sql | 1 + .../api/actor/ActorAPControllerImplTest.kt | 3 ++- .../objects/note/APNoteServiceImplTest.kt | 1 + .../core/service/user/ActorServiceTest.kt | 3 ++- .../MastodonAccountApiControllerTest.kt | 6 ++--- .../account/AccountApiServiceImplTest.kt | 4 +++ src/test/kotlin/utils/UserBuilder.kt | 7 ++--- 26 files changed, 98 insertions(+), 63 deletions(-) diff --git a/src/e2eTest/resources/oauth2/user.sql b/src/e2eTest/resources/oauth2/user.sql index 50c52058..001f51a7 100644 --- a/src/e2eTest/resources/oauth2/user.sql +++ b/src/e2eTest/resources/oauth2/user.sql @@ -1,5 +1,5 @@ insert into actors (ID, NAME, DOMAIN, SCREEN_NAME, DESCRIPTION, INBOX, OUTBOX, URL, PUBLIC_KEY, PRIVATE_KEY, - CREATED_AT, KEY_ID, FOLLOWING, FOLLOWERS, INSTANCE) + CREATED_AT, KEY_ID, FOLLOWING, FOLLOWERS, INSTANCE, LOCKED) VALUES (1730415786666758144, 'test-user', 'localhost', 'Im test user.', 'THis account is test user.', 'http://localhost/users/test-user/inbox', 'http://localhost/users/test-user/outbox', 'http://localhost/users/test-user', @@ -43,7 +43,7 @@ Ja15+ZWbOA4vJA9pOh3x4XM= -----END PRIVATE KEY----- ', 1701398248417, 'http://localhost/users/test-user#pubkey', 'http://localhost/users/test-user/following', - 'http://localhost/users/test-users/followers', null); + 'http://localhost/users/test-users/followers', null, false); insert into user_details (actor_id, password, auto_accept_follow_request, auto_accept_followee_follow_request) values ( 1730415786666758144 diff --git a/src/intTest/resources/sql/accounts/apiV1AccountsIdFollowPost フォローできる.sql b/src/intTest/resources/sql/accounts/apiV1AccountsIdFollowPost フォローできる.sql index a749a39a..a736a29c 100644 --- a/src/intTest/resources/sql/accounts/apiV1AccountsIdFollowPost フォローできる.sql +++ b/src/intTest/resources/sql/accounts/apiV1AccountsIdFollowPost フォローできる.sql @@ -1,16 +1,16 @@ insert into "actors" (id, name, domain, screen_name, description, inbox, outbox, url, public_key, private_key, - created_at, key_id, following, followers, instance) + created_at, key_id, following, followers, instance, locked) VALUES (3733363, 'follow-test-user-1', 'example.com', 'follow-test-user-1-name', '', 'https://example.com/users/follow-test-user-1/inbox', 'https://example.com/users/follow-test-user-1/outbox', 'https://example.com/users/follow-test-user-1', '-----BEGIN PUBLIC KEY-----...-----END PUBLIC KEY-----', '-----BEGIN PRIVATE KEY-----...-----END PRIVATE KEY-----', 12345678, 'https://example.com/users/follow-test-user-1#pubkey', 'https://example.com/users/follow-test-user-1/following', - 'https://example.com/users/follow-test-user-1/followers', null), + 'https://example.com/users/follow-test-user-1/followers', null, false), (37335363, 'follow-test-user-2', 'example.com', 'follow-test-user-2-name', '', 'https://example.com/users/follow-test-user-2/inbox', 'https://example.com/users/follow-test-user-2/outbox', 'https://example.com/users/follow-test-user-2', '-----BEGIN PUBLIC KEY-----...-----END PUBLIC KEY-----', '-----BEGIN PRIVATE KEY-----...-----END PRIVATE KEY-----', 12345678, 'https://example.com/users/follow-test-user-2#pubkey', 'https://example.com/users/follow-test-user-2/following', - 'https://example.com/users/follow-test-user-2/followers', null); + 'https://example.com/users/follow-test-user-2/followers', null, false); diff --git a/src/intTest/resources/sql/note/httpSignature認証でフォロワーがfollowers投稿を取得できる.sql b/src/intTest/resources/sql/note/httpSignature認証でフォロワーがfollowers投稿を取得できる.sql index 87509d2d..74a08848 100644 --- a/src/intTest/resources/sql/note/httpSignature認証でフォロワーがfollowers投稿を取得できる.sql +++ b/src/intTest/resources/sql/note/httpSignature認証でフォロワーがfollowers投稿を取得できる.sql @@ -1,23 +1,20 @@ insert into "actors" (ID, NAME, DOMAIN, SCREEN_NAME, DESCRIPTION, INBOX, OUTBOX, URL, PUBLIC_KEY, PRIVATE_KEY, - CREATED_AT, KEY_ID, FOLLOWING, FOLLOWERS, INSTANCE) + CREATED_AT, KEY_ID, FOLLOWING, FOLLOWERS, INSTANCE, LOCKED) VALUES (8, 'test-user8', 'example.com', 'Im test-user8.', 'THis account is test-user8.', 'https://example.com/users/test-user8/inbox', 'https://example.com/users/test-user8/outbox', 'https://example.com/users/test-user8', '-----BEGIN PUBLIC KEY-----...-----END PUBLIC KEY-----', '-----BEGIN PRIVATE KEY-----...-----END PRIVATE KEY-----', 12345678, 'https://example.com/users/test-user8#pubkey', 'https://example.com/users/test-user8/following', - 'https://example.com/users/test-user8/followers', null); - -insert into "actors" (ID, NAME, DOMAIN, SCREEN_NAME, DESCRIPTION, INBOX, OUTBOX, URL, PUBLIC_KEY, PRIVATE_KEY, - CREATED_AT, KEY_ID, FOLLOWING, FOLLOWERS, INSTANCE) -VALUES (9, 'test-user9', 'follower.example.com', 'Im test-user9.', 'THis account is test-user9.', + 'https://example.com/users/test-user8/followers', null, false), + (9, 'test-user9', 'follower.example.com', 'Im test-user9.', 'THis account is test-user9.', 'https://follower.example.com/users/test-user9/inbox', 'https://follower.example.com/users/test-user9/outbox', 'https://follower.example.com/users/test-user9', '-----BEGIN PUBLIC KEY-----...-----END PUBLIC KEY-----', null, 12345678, 'https://follower.example.com/users/test-user9#pubkey', 'https://follower.example.com/users/test-user9/following', - 'https://follower.example.com/users/test-user9/followers', null); + 'https://follower.example.com/users/test-user9/followers', null, false); insert into relationships (actor_id, target_actor_id, following, blocking, muting, follow_request, ignore_follow_request) diff --git a/src/intTest/resources/sql/note/httpSignature認証でフォロワーがpublic投稿を取得できる.sql b/src/intTest/resources/sql/note/httpSignature認証でフォロワーがpublic投稿を取得できる.sql index b4a0201d..f5e41dd3 100644 --- a/src/intTest/resources/sql/note/httpSignature認証でフォロワーがpublic投稿を取得できる.sql +++ b/src/intTest/resources/sql/note/httpSignature認証でフォロワーがpublic投稿を取得できる.sql @@ -1,24 +1,20 @@ insert into "actors" (ID, NAME, DOMAIN, SCREEN_NAME, DESCRIPTION, INBOX, OUTBOX, URL, PUBLIC_KEY, PRIVATE_KEY, - CREATED_AT, KEY_ID, FOLLOWING, FOLLOWERS, INSTANCE) + CREATED_AT, KEY_ID, FOLLOWING, FOLLOWERS, INSTANCE, LOCKED) VALUES (4, 'test-user4', 'example.com', 'Im test user4.', 'THis account is test user4.', 'https://example.com/users/test-user4/inbox', 'https://example.com/users/test-user4/outbox', 'https://example.com/users/test-user4', '-----BEGIN PUBLIC KEY-----...-----END PUBLIC KEY-----', '-----BEGIN PRIVATE KEY-----...-----END PRIVATE KEY-----', 12345678, 'https://example.com/users/test-user4#pubkey', 'https://example.com/users/test-user4/following', - 'https://example.com/users/test-user4/followers', null); - -insert into "actors" (ID, NAME, DOMAIN, SCREEN_NAME, DESCRIPTION, INBOX, OUTBOX, URL, PUBLIC_KEY, PRIVATE_KEY, - CREATED_AT, KEY_ID, FOLLOWING, FOLLOWERS, INSTANCE) -VALUES (5, 'test-user5', 'follower.example.com', 'Im test user5.', 'THis account is test user5.', - + 'https://example.com/users/test-user4/followers', null, false), + (5, 'test-user5', 'follower.example.com', 'Im test user5.', 'THis account is test user5.', 'https://follower.example.com/users/test-user5/inbox', 'https://follower.example.com/users/test-user5/outbox', 'https://follower.example.com/users/test-user5', '-----BEGIN PUBLIC KEY-----...-----END PUBLIC KEY-----', null, 12345678, 'https://follower.example.com/users/test-user5#pubkey', 'https://follower.example.com/users/test-user5/following', - 'https://follower.example.com/users/test-user5/followers', null); + 'https://follower.example.com/users/test-user5/followers', null, false); insert into relationships (actor_id, target_actor_id, following, blocking, muting, follow_request, ignore_follow_request) diff --git a/src/intTest/resources/sql/note/httpSignature認証でフォロワーがunlisted投稿を取得できる.sql b/src/intTest/resources/sql/note/httpSignature認証でフォロワーがunlisted投稿を取得できる.sql index a90569a1..3c40f0ca 100644 --- a/src/intTest/resources/sql/note/httpSignature認証でフォロワーがunlisted投稿を取得できる.sql +++ b/src/intTest/resources/sql/note/httpSignature認証でフォロワーがunlisted投稿を取得できる.sql @@ -1,23 +1,20 @@ insert into "actors" (ID, NAME, DOMAIN, SCREEN_NAME, DESCRIPTION, INBOX, OUTBOX, URL, PUBLIC_KEY, PRIVATE_KEY, - CREATED_AT, KEY_ID, FOLLOWING, FOLLOWERS, INSTANCE) + CREATED_AT, KEY_ID, FOLLOWING, FOLLOWERS, INSTANCE, LOCKED) VALUES (6, 'test-user6', 'example.com', 'Im test-user6.', 'THis account is test-user6.', 'https://example.com/users/test-user6/inbox', 'https://example.com/users/test-user6/outbox', 'https://example.com/users/test-user6', '-----BEGIN PUBLIC KEY-----...-----END PUBLIC KEY-----', '-----BEGIN PRIVATE KEY-----...-----END PRIVATE KEY-----', 12345678, 'https://example.com/users/test-user6#pubkey', 'https://example.com/users/test-user6/following', - 'https://example.com/users/test-user6/followers', null); - -insert into "actors" (ID, NAME, DOMAIN, SCREEN_NAME, DESCRIPTION, INBOX, OUTBOX, URL, PUBLIC_KEY, PRIVATE_KEY, - CREATED_AT, KEY_ID, FOLLOWING, FOLLOWERS, INSTANCE) -VALUES (7, 'test-user7', 'follower.example.com', 'Im test-user7.', 'THis account is test-user7.', + 'https://example.com/users/test-user6/followers', null, false), + (7, 'test-user7', 'follower.example.com', 'Im test-user7.', 'THis account is test-user7.', 'https://follower.example.com/users/test-user7/inbox', 'https://follower.example.com/users/test-user7/outbox', 'https://follower.example.com/users/test-user7', '-----BEGIN PUBLIC KEY-----...-----END PUBLIC KEY-----', null, 12345678, 'https://follower.example.com/users/test-user7#pubkey', 'https://follower.example.com/users/test-user7/following', - 'https://follower.example.com/users/test-user7/followers', null); + 'https://follower.example.com/users/test-user7/followers', null, false); insert into relationships (actor_id, target_actor_id, following, blocking, muting, follow_request, ignore_follow_request) diff --git a/src/intTest/resources/sql/note/メディア付き投稿はattachmentにDocumentとして画像が存在する.sql b/src/intTest/resources/sql/note/メディア付き投稿はattachmentにDocumentとして画像が存在する.sql index 499595c8..edc35707 100644 --- a/src/intTest/resources/sql/note/メディア付き投稿はattachmentにDocumentとして画像が存在する.sql +++ b/src/intTest/resources/sql/note/メディア付き投稿はattachmentにDocumentとして画像が存在する.sql @@ -1,12 +1,12 @@ insert into "actors" (ID, NAME, DOMAIN, SCREEN_NAME, DESCRIPTION, INBOX, OUTBOX, URL, PUBLIC_KEY, PRIVATE_KEY, - CREATED_AT, KEY_ID, FOLLOWING, FOLLOWERS, INSTANCE) + CREATED_AT, KEY_ID, FOLLOWING, FOLLOWERS, INSTANCE, LOCKED) VALUES (11, 'test-user11', 'example.com', 'Im test-user11.', 'THis account is test-user11.', 'https://example.com/users/test-user11/inbox', 'https://example.com/users/test-user11/outbox', 'https://example.com/users/test-user11', '-----BEGIN PUBLIC KEY-----...-----END PUBLIC KEY-----', '-----BEGIN PRIVATE KEY-----...-----END PRIVATE KEY-----', 12345678, 'https://example.com/users/test-user11#pubkey', 'https://example.com/users/test-user11/following', - 'https://example.com/users/test-user11/followers', null); + 'https://example.com/users/test-user11/followers', null, false); insert into POSTS (ID, actor_id, OVERVIEW, TEXT, "CREATED_AT", VISIBILITY, URL, "REPOST_ID", "REPLY_ID", SENSITIVE, AP_ID) diff --git a/src/intTest/resources/sql/note/リプライになっている投稿はinReplyToが存在する.sql b/src/intTest/resources/sql/note/リプライになっている投稿はinReplyToが存在する.sql index 76182d59..4df7c878 100644 --- a/src/intTest/resources/sql/note/リプライになっている投稿はinReplyToが存在する.sql +++ b/src/intTest/resources/sql/note/リプライになっている投稿はinReplyToが存在する.sql @@ -1,12 +1,12 @@ insert into "actors" (ID, NAME, DOMAIN, SCREEN_NAME, DESCRIPTION, INBOX, OUTBOX, URL, PUBLIC_KEY, PRIVATE_KEY, - CREATED_AT, KEY_ID, FOLLOWING, FOLLOWERS, INSTANCE) + CREATED_AT, KEY_ID, FOLLOWING, FOLLOWERS, INSTANCE, LOCKED) VALUES (10, 'test-user10', 'example.com', 'Im test-user10.', 'THis account is test-user10.', 'https://example.com/users/test-user10/inbox', 'https://example.com/users/test-user10/outbox', 'https://example.com/users/test-user10', '-----BEGIN PUBLIC KEY-----...-----END PUBLIC KEY-----', '-----BEGIN PRIVATE KEY-----...-----END PRIVATE KEY-----', 12345678, 'https://example.com/users/test-user10#pubkey', 'https://example.com/users/test-user10/following', - 'https://example.com/users/test-user10/followers', null); + 'https://example.com/users/test-user10/followers', null, false); insert into POSTS (ID, actor_id, OVERVIEW, TEXT, "CREATED_AT", VISIBILITY, URL, "REPOST_ID", "REPLY_ID", SENSITIVE, AP_ID) diff --git a/src/intTest/resources/sql/note/匿名でfollowers投稿を取得しようとすると404.sql b/src/intTest/resources/sql/note/匿名でfollowers投稿を取得しようとすると404.sql index e97f9b2d..627372d5 100644 --- a/src/intTest/resources/sql/note/匿名でfollowers投稿を取得しようとすると404.sql +++ b/src/intTest/resources/sql/note/匿名でfollowers投稿を取得しようとすると404.sql @@ -1,12 +1,12 @@ insert into "actors" (ID, NAME, DOMAIN, SCREEN_NAME, DESCRIPTION, INBOX, OUTBOX, URL, PUBLIC_KEY, PRIVATE_KEY, - CREATED_AT, KEY_ID, FOLLOWING, FOLLOWERS, INSTANCE) + CREATED_AT, KEY_ID, FOLLOWING, FOLLOWERS, INSTANCE, LOCKED) VALUES (3, 'test-user3', 'example.com', 'Im test user3.', 'THis account is test user3.', 'https://example.com/users/test-user3/inbox', 'https://example.com/users/test-user3/outbox', 'https://example.com/users/test-user3', '-----BEGIN PUBLIC KEY-----...-----END PUBLIC KEY-----', '-----BEGIN PRIVATE KEY-----...-----END PRIVATE KEY-----', 12345678, 'https://example.com/users/test-user3#pubkey', 'https://example.com/users/test-user3/following', - 'https://example.com/users/test-user3/followers', null); + 'https://example.com/users/test-user3/followers', null, false); insert into POSTS (ID, actor_id, OVERVIEW, TEXT, "CREATED_AT", VISIBILITY, URL, "REPOST_ID", "REPLY_ID", SENSITIVE, AP_ID) diff --git a/src/intTest/resources/sql/note/匿名でpublic投稿を取得できる.sql b/src/intTest/resources/sql/note/匿名でpublic投稿を取得できる.sql index 414a4394..22d44040 100644 --- a/src/intTest/resources/sql/note/匿名でpublic投稿を取得できる.sql +++ b/src/intTest/resources/sql/note/匿名でpublic投稿を取得できる.sql @@ -1,12 +1,12 @@ insert into actors (ID, NAME, DOMAIN, SCREEN_NAME, DESCRIPTION, INBOX, OUTBOX, URL, PUBLIC_KEY, PRIVATE_KEY, - CREATED_AT, KEY_ID, FOLLOWING, FOLLOWERS, INSTANCE) + CREATED_AT, KEY_ID, FOLLOWING, FOLLOWERS, INSTANCE, LOCKED) VALUES (1, 'test-user', 'example.com', 'Im test user.', 'THis account is test user.', 'https://example.com/users/test-user/inbox', 'https://example.com/users/test-user/outbox', 'https://example.com/users/test-user', '-----BEGIN PUBLIC KEY-----...-----END PUBLIC KEY-----', '-----BEGIN PRIVATE KEY-----...-----END PRIVATE KEY-----', 12345678, 'https://example.com/users/test-user#pubkey', 'https://example.com/users/test-user/following', - 'https://example.com/users/test-users/followers', null); + 'https://example.com/users/test-users/followers', null, false); insert into POSTS (ID, actor_id, OVERVIEW, TEXT, "CREATED_AT", VISIBILITY, URL, "REPOST_ID", "REPLY_ID", SENSITIVE, AP_ID) diff --git a/src/intTest/resources/sql/note/匿名でunlisted投稿を取得できる.sql b/src/intTest/resources/sql/note/匿名でunlisted投稿を取得できる.sql index 6a7dc9c5..db8674e5 100644 --- a/src/intTest/resources/sql/note/匿名でunlisted投稿を取得できる.sql +++ b/src/intTest/resources/sql/note/匿名でunlisted投稿を取得できる.sql @@ -1,12 +1,12 @@ insert into actors (ID, NAME, DOMAIN, SCREEN_NAME, DESCRIPTION, INBOX, OUTBOX, URL, PUBLIC_KEY, PRIVATE_KEY, - CREATED_AT, KEY_ID, FOLLOWING, FOLLOWERS, INSTANCE) + CREATED_AT, KEY_ID, FOLLOWING, FOLLOWERS, INSTANCE, LOCKED) VALUES (2, 'test-user2', 'example.com', 'Im test user2.', 'THis account is test user2.', 'https://example.com/users/test-user2/inbox', 'https://example.com/users/test-user2/outbox', 'https://example.com/users/test-user2', '-----BEGIN PUBLIC KEY-----...-----END PUBLIC KEY-----', '-----BEGIN PRIVATE KEY-----...-----END PRIVATE KEY-----', 12345678, 'https://example.com/users/test-user2#pubkey', 'https://example.com/users/test-user2/following', - 'https://example.com/users/test-user2/followers', null); + 'https://example.com/users/test-user2/followers', null, false); insert into POSTS (ID, actor_id, OVERVIEW, TEXT, "CREATED_AT", VISIBILITY, URL, "REPOST_ID", "REPLY_ID", SENSITIVE, AP_ID) diff --git a/src/intTest/resources/sql/test-user.sql b/src/intTest/resources/sql/test-user.sql index 54cb1852..f8239b21 100644 --- a/src/intTest/resources/sql/test-user.sql +++ b/src/intTest/resources/sql/test-user.sql @@ -1,9 +1,9 @@ insert into "actors" (ID, NAME, DOMAIN, SCREEN_NAME, DESCRIPTION, INBOX, OUTBOX, URL, PUBLIC_KEY, PRIVATE_KEY, - CREATED_AT, KEY_ID, FOLLOWING, FOLLOWERS, INSTANCE) + CREATED_AT, KEY_ID, FOLLOWING, FOLLOWERS, INSTANCE, LOCKED) VALUES (1, 'test-user', 'example.com', 'Im test user.', 'THis account is test user.', 'https://example.com/users/test-user/inbox', 'https://example.com/users/test-user/outbox', 'https://example.com/users/test-user', '-----BEGIN PUBLIC KEY-----...-----END PUBLIC KEY-----', '-----BEGIN PRIVATE KEY-----...-----END PRIVATE KEY-----', 12345678, 'https://example.com/users/test-user#pubkey', 'https://example.com/users/test-user/following', - 'https://example.com/users/test-users/followers', null); + 'https://example.com/users/test-users/followers', null, false); diff --git a/src/intTest/resources/sql/test-user2.sql b/src/intTest/resources/sql/test-user2.sql index 284716cb..93a466a2 100644 --- a/src/intTest/resources/sql/test-user2.sql +++ b/src/intTest/resources/sql/test-user2.sql @@ -1,9 +1,9 @@ insert into "actors" (ID, NAME, DOMAIN, SCREEN_NAME, DESCRIPTION, INBOX, OUTBOX, URL, PUBLIC_KEY, PRIVATE_KEY, - CREATED_AT, KEY_ID, FOLLOWING, FOLLOWERS, INSTANCE) + CREATED_AT, KEY_ID, FOLLOWING, FOLLOWERS, INSTANCE, LOCKED) VALUES (2, 'test-user2', 'example.com', 'Im test user.', 'THis account is test user.', 'https://example.com/users/test-user2/inbox', 'https://example.com/users/test-user2/outbox', 'https://example.com/users/test-user2', '-----BEGIN PUBLIC KEY-----...-----END PUBLIC KEY-----', '-----BEGIN PRIVATE KEY-----...-----END PRIVATE KEY-----', 12345678, 'https://example.com/users/test-user2#pubkey', 'https://example.com/users/test-user2/following', - 'https://example.com/users/test-user2s/followers', null); + 'https://example.com/users/test-user2s/followers', null, false); diff --git a/src/main/kotlin/dev/usbharu/hideout/activitypub/domain/model/Person.kt b/src/main/kotlin/dev/usbharu/hideout/activitypub/domain/model/Person.kt index cba9eeaf..49a9f5b4 100644 --- a/src/main/kotlin/dev/usbharu/hideout/activitypub/domain/model/Person.kt +++ b/src/main/kotlin/dev/usbharu/hideout/activitypub/domain/model/Person.kt @@ -17,10 +17,10 @@ constructor( var publicKey: Key, var endpoints: Map = emptyMap(), var followers: String?, - var following: String? + var following: String?, + val manuallyApprovesFollowers: Boolean? = false ) : Object(add(type, "Person")), HasId, HasName { - @Suppress("CyclomaticComplexMethod", "CognitiveComplexMethod") override fun equals(other: Any?): Boolean { if (this === other) return true if (javaClass != other?.javaClass) return false @@ -40,11 +40,11 @@ constructor( if (endpoints != other.endpoints) return false if (followers != other.followers) return false if (following != other.following) return false + if (manuallyApprovesFollowers != other.manuallyApprovesFollowers) return false return true } - @Suppress("CyclomaticComplexMethod") override fun hashCode(): Int { var result = super.hashCode() result = 31 * result + name.hashCode() @@ -59,6 +59,26 @@ constructor( result = 31 * result + endpoints.hashCode() result = 31 * result + (followers?.hashCode() ?: 0) result = 31 * result + (following?.hashCode() ?: 0) + result = 31 * result + manuallyApprovesFollowers.hashCode() return result } + + override fun toString(): String { + return "Person(" + + "name='$name', " + + "id='$id', " + + "preferredUsername=$preferredUsername, " + + "summary=$summary, " + + "inbox='$inbox', " + + "outbox='$outbox', " + + "url='$url', " + + "icon=$icon, " + + "publicKey=$publicKey, " + + "endpoints=$endpoints, " + + "followers=$followers, " + + "following=$following, " + + "manuallyApprovesFollowers=$manuallyApprovesFollowers" + + ")" + + " ${super.toString()}" + } } diff --git a/src/main/kotlin/dev/usbharu/hideout/activitypub/service/objects/user/APUserService.kt b/src/main/kotlin/dev/usbharu/hideout/activitypub/service/objects/user/APUserService.kt index 1729c361..3b540ebb 100644 --- a/src/main/kotlin/dev/usbharu/hideout/activitypub/service/objects/user/APUserService.kt +++ b/src/main/kotlin/dev/usbharu/hideout/activitypub/service/objects/user/APUserService.kt @@ -68,7 +68,8 @@ class APUserServiceImpl( ), endpoints = mapOf("sharedInbox" to "${applicationConfig.url}/inbox"), followers = userEntity.followers, - following = userEntity.following + following = userEntity.following, + manuallyApprovesFollowers = userEntity.locked ) } @@ -104,7 +105,8 @@ class APUserServiceImpl( keyId = person.publicKey.id, following = person.following, followers = person.followers, - sharedInbox = person.endpoints["sharedInbox"] + sharedInbox = person.endpoints["sharedInbox"], + locked = person.manuallyApprovesFollowers ) ) } @@ -134,6 +136,7 @@ class APUserServiceImpl( ), endpoints = mapOf("sharedInbox" to "${applicationConfig.url}/inbox"), followers = actorEntity.followers, - following = actorEntity.following + following = actorEntity.following, + manuallyApprovesFollowers = actorEntity.locked ) } diff --git a/src/main/kotlin/dev/usbharu/hideout/core/domain/model/actor/Actor.kt b/src/main/kotlin/dev/usbharu/hideout/core/domain/model/actor/Actor.kt index fd2e28e3..f83fba96 100644 --- a/src/main/kotlin/dev/usbharu/hideout/core/domain/model/actor/Actor.kt +++ b/src/main/kotlin/dev/usbharu/hideout/core/domain/model/actor/Actor.kt @@ -21,7 +21,8 @@ data class Actor private constructor( val keyId: String, val followers: String? = null, val following: String? = null, - val instance: Long? = null + val instance: Long? = null, + val locked: Boolean ) { @@ -46,7 +47,8 @@ data class Actor private constructor( keyId: String, following: String? = null, followers: String? = null, - instance: Long? = null + instance: Long? = null, + locked: Boolean ): Actor { // idは0未満ではいけない require(id >= 0) { "id must be greater than or equal to 0." } @@ -137,7 +139,8 @@ data class Actor private constructor( keyId = keyId, followers = followers, following = following, - instance = instance + instance = instance, + locked ) } } @@ -158,7 +161,10 @@ data class Actor private constructor( "keyId='$keyId', " + "followers=$followers, " + "following=$following, " + - "instance=$instance" + + "instance=$instance, " + + "locked=$locked" + ")" } + + } diff --git a/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposed/UserResultRowMapper.kt b/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposed/UserResultRowMapper.kt index 01958b82..abf8d880 100644 --- a/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposed/UserResultRowMapper.kt +++ b/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposed/UserResultRowMapper.kt @@ -25,7 +25,8 @@ class UserResultRowMapper(private val actorBuilder: Actor.UserBuilder) : ResultR keyId = resultRow[Actors.keyId], followers = resultRow[Actors.followers], following = resultRow[Actors.following], - instance = resultRow[Actors.instance] + instance = resultRow[Actors.instance], + locked = resultRow[Actors.locked] ) } } diff --git a/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposedrepository/ActorRepositoryImpl.kt b/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposedrepository/ActorRepositoryImpl.kt index 81625cd1..0eb59b59 100644 --- a/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposedrepository/ActorRepositoryImpl.kt +++ b/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposedrepository/ActorRepositoryImpl.kt @@ -34,6 +34,7 @@ class ActorRepositoryImpl( it[following] = actor.following it[followers] = actor.followers it[instance] = actor.instance + it[locked] = actor.locked } } else { Actors.update({ Actors.id eq actor.id }) { @@ -51,6 +52,7 @@ class ActorRepositoryImpl( it[following] = actor.following it[followers] = actor.followers it[instance] = actor.instance + it[locked] = actor.locked } } return actor @@ -88,6 +90,7 @@ object Actors : Table("actors") { val following = varchar("following", length = 1000).nullable() val followers = varchar("followers", length = 1000).nullable() val instance = long("instance").references(Instance.id).nullable() + val locked = bool("locked") override val primaryKey: PrimaryKey = PrimaryKey(id) diff --git a/src/main/kotlin/dev/usbharu/hideout/core/service/user/RemoteUserCreateDto.kt b/src/main/kotlin/dev/usbharu/hideout/core/service/user/RemoteUserCreateDto.kt index de85e74d..8260d050 100644 --- a/src/main/kotlin/dev/usbharu/hideout/core/service/user/RemoteUserCreateDto.kt +++ b/src/main/kotlin/dev/usbharu/hideout/core/service/user/RemoteUserCreateDto.kt @@ -12,5 +12,6 @@ data class RemoteUserCreateDto( val keyId: String, val followers: String?, val following: String?, - val sharedInbox: String? + val sharedInbox: String?, + val locked: Boolean? ) diff --git a/src/main/kotlin/dev/usbharu/hideout/core/service/user/UserServiceImpl.kt b/src/main/kotlin/dev/usbharu/hideout/core/service/user/UserServiceImpl.kt index f126fbf7..3a1d20ba 100644 --- a/src/main/kotlin/dev/usbharu/hideout/core/service/user/UserServiceImpl.kt +++ b/src/main/kotlin/dev/usbharu/hideout/core/service/user/UserServiceImpl.kt @@ -49,7 +49,8 @@ class UserServiceImpl( createdAt = Instant.now(), following = "$userUrl/following", followers = "$userUrl/followers", - keyId = "$userUrl#pubkey" + keyId = "$userUrl#pubkey", + locked = false ) val save = actorRepository.save(userEntity) userDetailRepository.save(UserDetail(nextId, hashedPassword, true, true)) @@ -82,7 +83,8 @@ class UserServiceImpl( followers = user.followers, following = user.following, keyId = user.keyId, - instance = instance?.id + instance = instance?.id, + locked = user.locked ?: false ) return try { val save = actorRepository.save(userEntity) diff --git a/src/main/resources/db/migration/V1__Init_DB.sql b/src/main/resources/db/migration/V1__Init_DB.sql index 57aa0d35..9cdab874 100644 --- a/src/main/resources/db/migration/V1__Init_DB.sql +++ b/src/main/resources/db/migration/V1__Init_DB.sql @@ -30,6 +30,7 @@ create table if not exists actors "following" varchar(1000) null, followers varchar(1000) null, "instance" bigint null, + locked boolean not null, unique ("name", "domain"), constraint fk_actors_instance__id foreign key ("instance") references instance (id) on delete restrict on update restrict ); diff --git a/src/test/kotlin/dev/usbharu/hideout/activitypub/interfaces/api/actor/ActorAPControllerImplTest.kt b/src/test/kotlin/dev/usbharu/hideout/activitypub/interfaces/api/actor/ActorAPControllerImplTest.kt index 14283127..36cfa4c5 100644 --- a/src/test/kotlin/dev/usbharu/hideout/activitypub/interfaces/api/actor/ActorAPControllerImplTest.kt +++ b/src/test/kotlin/dev/usbharu/hideout/activitypub/interfaces/api/actor/ActorAPControllerImplTest.kt @@ -59,7 +59,8 @@ class ActorAPControllerImplTest { ), endpoints = mapOf("sharedInbox" to "https://example.com/inbox"), followers = "https://example.com/users/hoge/followers", - following = "https://example.com/users/hoge/following" + following = "https://example.com/users/hoge/following", + manuallyApprovesFollowers = false ) whenever(apUserService.getPersonByName(eq("hoge"))).doReturn(person) diff --git a/src/test/kotlin/dev/usbharu/hideout/activitypub/service/objects/note/APNoteServiceImplTest.kt b/src/test/kotlin/dev/usbharu/hideout/activitypub/service/objects/note/APNoteServiceImplTest.kt index f7415589..0a709b38 100644 --- a/src/test/kotlin/dev/usbharu/hideout/activitypub/service/objects/note/APNoteServiceImplTest.kt +++ b/src/test/kotlin/dev/usbharu/hideout/activitypub/service/objects/note/APNoteServiceImplTest.kt @@ -133,6 +133,7 @@ class APNoteServiceImplTest { endpoints = mapOf("sharedInbox" to "https://example.com/inbox"), followers = user.followers, following = user.following, + manuallyApprovesFollowers = false ) val apUserService = mock { diff --git a/src/test/kotlin/dev/usbharu/hideout/core/service/user/ActorServiceTest.kt b/src/test/kotlin/dev/usbharu/hideout/core/service/user/ActorServiceTest.kt index ad0959e8..9d5b6f10 100644 --- a/src/test/kotlin/dev/usbharu/hideout/core/service/user/ActorServiceTest.kt +++ b/src/test/kotlin/dev/usbharu/hideout/core/service/user/ActorServiceTest.kt @@ -79,7 +79,8 @@ class ActorServiceTest { keyId = "a", following = "", followers = "", - sharedInbox = null + sharedInbox = null, + locked = false ) userService.createRemoteUser(user) verify(actorRepository, times(1)).save(any()) diff --git a/src/test/kotlin/dev/usbharu/hideout/mastodon/interfaces/api/account/MastodonAccountApiControllerTest.kt b/src/test/kotlin/dev/usbharu/hideout/mastodon/interfaces/api/account/MastodonAccountApiControllerTest.kt index 895f33a4..8356f17e 100644 --- a/src/test/kotlin/dev/usbharu/hideout/mastodon/interfaces/api/account/MastodonAccountApiControllerTest.kt +++ b/src/test/kotlin/dev/usbharu/hideout/mastodon/interfaces/api/account/MastodonAccountApiControllerTest.kt @@ -1,8 +1,8 @@ package dev.usbharu.hideout.mastodon.interfaces.api.account import dev.usbharu.hideout.application.config.ActivityPubConfig +import dev.usbharu.hideout.domain.mastodon.model.generated.AccountSource import dev.usbharu.hideout.domain.mastodon.model.generated.CredentialAccount -import dev.usbharu.hideout.domain.mastodon.model.generated.CredentialAccountSource import dev.usbharu.hideout.domain.mastodon.model.generated.Role import dev.usbharu.hideout.mastodon.service.account.AccountApiService import kotlinx.coroutines.test.runTest @@ -74,10 +74,10 @@ class MastodonAccountApiControllerTest { lastStatusAt = "", statusesCount = 0, followersCount = 0, - source = CredentialAccountSource( + source = AccountSource( note = "", fields = emptyList(), - privacy = CredentialAccountSource.Privacy.public, + privacy = AccountSource.Privacy.public, sensitive = false, followRequestsCount = 0 ), diff --git a/src/test/kotlin/dev/usbharu/hideout/mastodon/service/account/AccountApiServiceImplTest.kt b/src/test/kotlin/dev/usbharu/hideout/mastodon/service/account/AccountApiServiceImplTest.kt index d10e4e77..5beffa32 100644 --- a/src/test/kotlin/dev/usbharu/hideout/mastodon/service/account/AccountApiServiceImplTest.kt +++ b/src/test/kotlin/dev/usbharu/hideout/mastodon/service/account/AccountApiServiceImplTest.kt @@ -4,6 +4,7 @@ import dev.usbharu.hideout.application.external.Transaction import dev.usbharu.hideout.core.domain.model.actor.ActorRepository import dev.usbharu.hideout.core.domain.model.relationship.RelationshipRepository import dev.usbharu.hideout.core.query.FollowerQueryService +import dev.usbharu.hideout.core.service.media.MediaService import dev.usbharu.hideout.core.service.relationship.RelationshipService import dev.usbharu.hideout.core.service.user.UserService import dev.usbharu.hideout.domain.mastodon.model.generated.Account @@ -48,6 +49,9 @@ class AccountApiServiceImplTest { @Mock private lateinit var relationshipRepository: RelationshipRepository + @Mock + private lateinit var mediaService: MediaService + @InjectMocks private lateinit var accountApiServiceImpl: AccountApiServiceImpl diff --git a/src/test/kotlin/utils/UserBuilder.kt b/src/test/kotlin/utils/UserBuilder.kt index c496eed8..107a0613 100644 --- a/src/test/kotlin/utils/UserBuilder.kt +++ b/src/test/kotlin/utils/UserBuilder.kt @@ -19,7 +19,6 @@ object UserBuilder { domain: String = "example.com", screenName: String = name, description: String = "This user is test user.", - password: String = "password-$id", inbox: String = "https://$domain/users/$id/inbox", outbox: String = "https://$domain/users/$id/outbox", url: String = "https://$domain/users/$id", @@ -44,7 +43,8 @@ object UserBuilder { createdAt = createdAt, keyId = keyId, followers = followers, - following = following + following = following, + locked = false ) } @@ -77,7 +77,8 @@ object UserBuilder { createdAt = createdAt, keyId = keyId, followers = followers, - following = following + following = following, + locked = false ) } From 4e8cffdbe2fe9a380787590618e8f75bc3d24389 Mon Sep 17 00:00:00 2001 From: usbharu <64310155+usbharu@users.noreply.github.com> Date: Tue, 12 Dec 2023 12:02:54 +0900 Subject: [PATCH 3/6] =?UTF-8?q?refactor:=20Hideout=E3=83=A6=E3=83=BC?= =?UTF-8?q?=E3=82=B6=E3=83=BC=E5=81=B4=E3=81=AE=E8=A8=AD=E5=AE=9A=E3=82=82?= =?UTF-8?q?ActivityPub=E3=81=A8=E5=90=88=E3=82=8F=E3=81=9B=E3=82=8B?= =?UTF-8?q?=E3=82=88=E3=81=86=E3=81=AB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../hideout/core/domain/model/userdetails/UserDetail.kt | 1 - .../exposedrepository/UserDetailRepositoryImpl.kt | 4 ---- .../dev/usbharu/hideout/core/service/user/UpdateUserDto.kt | 2 +- .../dev/usbharu/hideout/core/service/user/UserServiceImpl.kt | 4 ++-- .../hideout/mastodon/service/account/AccountApiService.kt | 2 +- src/main/resources/db/migration/V1__Init_DB.sql | 1 - 6 files changed, 4 insertions(+), 10 deletions(-) diff --git a/src/main/kotlin/dev/usbharu/hideout/core/domain/model/userdetails/UserDetail.kt b/src/main/kotlin/dev/usbharu/hideout/core/domain/model/userdetails/UserDetail.kt index f4062917..648ccf7e 100644 --- a/src/main/kotlin/dev/usbharu/hideout/core/domain/model/userdetails/UserDetail.kt +++ b/src/main/kotlin/dev/usbharu/hideout/core/domain/model/userdetails/UserDetail.kt @@ -3,6 +3,5 @@ package dev.usbharu.hideout.core.domain.model.userdetails data class UserDetail( val actorId: Long, val password: String, - val autoAcceptFollowRequest: Boolean, val autoAcceptFolloweeFollowRequest: Boolean ) diff --git a/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposedrepository/UserDetailRepositoryImpl.kt b/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposedrepository/UserDetailRepositoryImpl.kt index 10e52cf8..f19e8ecf 100644 --- a/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposedrepository/UserDetailRepositoryImpl.kt +++ b/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposedrepository/UserDetailRepositoryImpl.kt @@ -18,13 +18,11 @@ class UserDetailRepositoryImpl : UserDetailRepository { UserDetails.insert { it[actorId] = userDetail.actorId it[password] = userDetail.password - it[autoAcceptFollowRequest] = userDetail.autoAcceptFollowRequest it[autoAcceptFolloweeFollowRequest] = userDetail.autoAcceptFolloweeFollowRequest } } else { UserDetails.update({ UserDetails.actorId eq userDetail.actorId }) { it[password] = userDetail.password - it[autoAcceptFollowRequest] = userDetail.autoAcceptFollowRequest it[autoAcceptFolloweeFollowRequest] = userDetail.autoAcceptFolloweeFollowRequest } } @@ -43,7 +41,6 @@ class UserDetailRepositoryImpl : UserDetailRepository { UserDetail( it[UserDetails.actorId], it[UserDetails.password], - it[UserDetails.autoAcceptFollowRequest], it[UserDetails.autoAcceptFolloweeFollowRequest] ) } @@ -55,6 +52,5 @@ class UserDetailRepositoryImpl : UserDetailRepository { object UserDetails : LongIdTable("user_details") { val actorId = long("actor_id").references(Actors.id) val password = varchar("password", 255) - val autoAcceptFollowRequest = bool("auto_accept_follow_request") val autoAcceptFolloweeFollowRequest = bool("auto_accept_followee_follow_request") } diff --git a/src/main/kotlin/dev/usbharu/hideout/core/service/user/UpdateUserDto.kt b/src/main/kotlin/dev/usbharu/hideout/core/service/user/UpdateUserDto.kt index 5406fd02..a02c8d1c 100644 --- a/src/main/kotlin/dev/usbharu/hideout/core/service/user/UpdateUserDto.kt +++ b/src/main/kotlin/dev/usbharu/hideout/core/service/user/UpdateUserDto.kt @@ -7,6 +7,6 @@ data class UpdateUserDto( val description: String, val avatarMedia: Media?, val headerMedia: Media?, - val autoAcceptFollowRequest: Boolean, + val locked: Boolean, val autoAcceptFolloweeFollowRequest: Boolean ) diff --git a/src/main/kotlin/dev/usbharu/hideout/core/service/user/UserServiceImpl.kt b/src/main/kotlin/dev/usbharu/hideout/core/service/user/UserServiceImpl.kt index 3a1d20ba..442b676b 100644 --- a/src/main/kotlin/dev/usbharu/hideout/core/service/user/UserServiceImpl.kt +++ b/src/main/kotlin/dev/usbharu/hideout/core/service/user/UserServiceImpl.kt @@ -53,7 +53,7 @@ class UserServiceImpl( locked = false ) val save = actorRepository.save(userEntity) - userDetailRepository.save(UserDetail(nextId, hashedPassword, true, true)) + userDetailRepository.save(UserDetail(nextId, hashedPassword, true)) return save } @@ -106,12 +106,12 @@ class UserServiceImpl( actor.copy( screenName = updateUserDto.screenName, description = updateUserDto.description, + locked = updateUserDto.locked ) ) userDetailRepository.save( userDetail.copy( - autoAcceptFollowRequest = updateUserDto.autoAcceptFollowRequest, autoAcceptFolloweeFollowRequest = updateUserDto.autoAcceptFolloweeFollowRequest ) ) diff --git a/src/main/kotlin/dev/usbharu/hideout/mastodon/service/account/AccountApiService.kt b/src/main/kotlin/dev/usbharu/hideout/mastodon/service/account/AccountApiService.kt index b85b7d67..d94df813 100644 --- a/src/main/kotlin/dev/usbharu/hideout/mastodon/service/account/AccountApiService.kt +++ b/src/main/kotlin/dev/usbharu/hideout/mastodon/service/account/AccountApiService.kt @@ -195,7 +195,7 @@ class AccountApiServiceImpl( description = updateCredentials?.note ?: account.note, avatarMedia = avatarMedia, headerMedia = headerMedia, - autoAcceptFollowRequest = updateCredentials?.locked ?: account.locked, + locked = updateCredentials?.locked ?: account.locked, autoAcceptFolloweeFollowRequest = false ) userService.updateUser(userid, updateUserDto) diff --git a/src/main/resources/db/migration/V1__Init_DB.sql b/src/main/resources/db/migration/V1__Init_DB.sql index 9cdab874..48223f60 100644 --- a/src/main/resources/db/migration/V1__Init_DB.sql +++ b/src/main/resources/db/migration/V1__Init_DB.sql @@ -40,7 +40,6 @@ create table if not exists user_details id bigserial primary key, actor_id bigint not null unique, password varchar(255) not null, - auto_accept_follow_request boolean not null, auto_accept_followee_follow_request boolean not null, constraint fk_user_details_actor_id__id foreign key (actor_id) references actors (id) on delete restrict on update restrict ); From ddaf630ed31d5c4e231c02d43d0877f3e0fabe6d Mon Sep 17 00:00:00 2001 From: usbharu <64310155+usbharu@users.noreply.github.com> Date: Tue, 12 Dec 2023 13:02:19 +0900 Subject: [PATCH 4/6] =?UTF-8?q?feat:=20=E3=83=95=E3=82=A9=E3=83=AD?= =?UTF-8?q?=E3=83=BC=E3=83=AA=E3=82=AF=E3=82=A8=E3=82=B9=E3=83=88API?= =?UTF-8?q?=E3=82=92=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/e2eTest/resources/oauth2/user.sql | 4 +- .../domain/model/relationship/Relationship.kt | 4 +- .../RelationshipRepositoryImpl.kt | 6 +- .../RelationshipQueryServiceImpl.kt | 14 ++++ .../core/query/RelationshipQueryService.kt | 5 ++ .../relationship/RelationshipServiceImpl.kt | 22 ++--- .../service/account/AccountApiService.kt | 31 ++++++- .../service/account/AccountService.kt | 9 +++ src/main/resources/openapi/mastodon.yaml | 80 +++++++++++++++++++ .../RelationshipServiceImplTest.kt | 70 ++++++++-------- .../account/AccountApiServiceImplTest.kt | 10 ++- 11 files changed, 196 insertions(+), 59 deletions(-) diff --git a/src/e2eTest/resources/oauth2/user.sql b/src/e2eTest/resources/oauth2/user.sql index 001f51a7..34ac640f 100644 --- a/src/e2eTest/resources/oauth2/user.sql +++ b/src/e2eTest/resources/oauth2/user.sql @@ -45,6 +45,6 @@ Ja15+ZWbOA4vJA9pOh3x4XM= 'http://localhost/users/test-user#pubkey', 'http://localhost/users/test-user/following', 'http://localhost/users/test-users/followers', null, false); -insert into user_details (actor_id, password, auto_accept_follow_request, auto_accept_followee_follow_request) +insert into user_details (actor_id, password, auto_accept_followee_follow_request) values ( 1730415786666758144 - , '$2a$10$/mWC/n7nC7X3l9qCEOKnredxne2zewoqEsJWTOdlKfg2zXKJ0F9Em', true, true) + , '$2a$10$/mWC/n7nC7X3l9qCEOKnredxne2zewoqEsJWTOdlKfg2zXKJ0F9Em', true) diff --git a/src/main/kotlin/dev/usbharu/hideout/core/domain/model/relationship/Relationship.kt b/src/main/kotlin/dev/usbharu/hideout/core/domain/model/relationship/Relationship.kt index 0090a43a..ad9b9635 100644 --- a/src/main/kotlin/dev/usbharu/hideout/core/domain/model/relationship/Relationship.kt +++ b/src/main/kotlin/dev/usbharu/hideout/core/domain/model/relationship/Relationship.kt @@ -9,7 +9,7 @@ package dev.usbharu.hideout.core.domain.model.relationship * @property blocking ブロックしているか * @property muting ミュートしているか * @property followRequest フォローリクエストを送っているか - * @property ignoreFollowRequestFromTarget フォローリクエストを無視しているか + * @property ignoreFollowRequestToTarget フォローリクエストを無視しているか */ data class Relationship( val actorId: Long, @@ -18,5 +18,5 @@ data class Relationship( val blocking: Boolean, val muting: Boolean, val followRequest: Boolean, - val ignoreFollowRequestFromTarget: Boolean + val ignoreFollowRequestToTarget: Boolean ) diff --git a/src/main/kotlin/dev/usbharu/hideout/core/domain/model/relationship/RelationshipRepositoryImpl.kt b/src/main/kotlin/dev/usbharu/hideout/core/domain/model/relationship/RelationshipRepositoryImpl.kt index 6deef1bf..09be85c8 100644 --- a/src/main/kotlin/dev/usbharu/hideout/core/domain/model/relationship/RelationshipRepositoryImpl.kt +++ b/src/main/kotlin/dev/usbharu/hideout/core/domain/model/relationship/RelationshipRepositoryImpl.kt @@ -25,7 +25,7 @@ class RelationshipRepositoryImpl : RelationshipRepository { it[blocking] = relationship.blocking it[muting] = relationship.muting it[followRequest] = relationship.followRequest - it[ignoreFollowRequestFromTarget] = relationship.ignoreFollowRequestFromTarget + it[ignoreFollowRequestFromTarget] = relationship.ignoreFollowRequestToTarget } } else { Relationships @@ -37,7 +37,7 @@ class RelationshipRepositoryImpl : RelationshipRepository { it[blocking] = relationship.blocking it[muting] = relationship.muting it[followRequest] = relationship.followRequest - it[ignoreFollowRequestFromTarget] = relationship.ignoreFollowRequestFromTarget + it[ignoreFollowRequestFromTarget] = relationship.ignoreFollowRequestToTarget } } return relationship @@ -66,7 +66,7 @@ fun ResultRow.toRelationships(): Relationship = Relationship( blocking = this[Relationships.blocking], muting = this[Relationships.muting], followRequest = this[Relationships.followRequest], - ignoreFollowRequestFromTarget = this[Relationships.ignoreFollowRequestFromTarget] + ignoreFollowRequestToTarget = this[Relationships.ignoreFollowRequestFromTarget] ) object Relationships : LongIdTable("relationships") { diff --git a/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposedquery/RelationshipQueryServiceImpl.kt b/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposedquery/RelationshipQueryServiceImpl.kt index f4f31633..5a5127e5 100644 --- a/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposedquery/RelationshipQueryServiceImpl.kt +++ b/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposedquery/RelationshipQueryServiceImpl.kt @@ -13,4 +13,18 @@ class RelationshipQueryServiceImpl : RelationshipQueryService { override suspend fun findByTargetIdAndFollowing(targetId: Long, following: Boolean): List = Relationships.select { Relationships.targetActorId eq targetId and (Relationships.following eq following) } .map { it.toRelationships() } + + override suspend fun findByTargetIdAndFollowRequestAndIgnoreFollowRequest( + targetId: Long, + followRequest: Boolean, + ignoreFollowRequest: Boolean + ): List { + return Relationships + .select { + Relationships.targetActorId.eq(targetId) + .and(Relationships.followRequest.eq(followRequest)) + .and(Relationships.ignoreFollowRequestFromTarget.eq(ignoreFollowRequest)) + } + .map { it.toRelationships() } + } } diff --git a/src/main/kotlin/dev/usbharu/hideout/core/query/RelationshipQueryService.kt b/src/main/kotlin/dev/usbharu/hideout/core/query/RelationshipQueryService.kt index 5f051397..324dc171 100644 --- a/src/main/kotlin/dev/usbharu/hideout/core/query/RelationshipQueryService.kt +++ b/src/main/kotlin/dev/usbharu/hideout/core/query/RelationshipQueryService.kt @@ -5,4 +5,9 @@ import dev.usbharu.hideout.core.domain.model.relationship.Relationship interface RelationshipQueryService { suspend fun findByTargetIdAndFollowing(targetId: Long, following: Boolean): List + suspend fun findByTargetIdAndFollowRequestAndIgnoreFollowRequest( + targetId: Long, + followRequest: Boolean, + ignoreFollowRequest: Boolean + ): List } diff --git a/src/main/kotlin/dev/usbharu/hideout/core/service/relationship/RelationshipServiceImpl.kt b/src/main/kotlin/dev/usbharu/hideout/core/service/relationship/RelationshipServiceImpl.kt index 9bddbb1a..67bd1bfe 100644 --- a/src/main/kotlin/dev/usbharu/hideout/core/service/relationship/RelationshipServiceImpl.kt +++ b/src/main/kotlin/dev/usbharu/hideout/core/service/relationship/RelationshipServiceImpl.kt @@ -38,7 +38,7 @@ class RelationshipServiceImpl( blocking = false, muting = false, followRequest = true, - ignoreFollowRequestFromTarget = false + ignoreFollowRequestToTarget = false ) val inverseRelationship = relationshipRepository.findByUserIdAndTargetUserId(targetId, actorId) ?: Relationship( @@ -48,7 +48,7 @@ class RelationshipServiceImpl( blocking = false, muting = false, followRequest = false, - ignoreFollowRequestFromTarget = false + ignoreFollowRequestToTarget = false ) if (inverseRelationship.blocking) { @@ -60,7 +60,7 @@ class RelationshipServiceImpl( logger.debug("FAILED Blocking user. userId: {} targetId: {}", actorId, targetId) return } - if (inverseRelationship.ignoreFollowRequestFromTarget) { + if (relationship.ignoreFollowRequestToTarget) { logger.debug("SUCCESS Ignore Follow Request. userId: {} targetId: {}", actorId, targetId) return } @@ -95,7 +95,7 @@ class RelationshipServiceImpl( blocking = true, muting = false, followRequest = false, - ignoreFollowRequestFromTarget = false + ignoreFollowRequestToTarget = false ) val inverseRelationship = relationshipRepository.findByUserIdAndTargetUserId(targetId, actorId) @@ -126,7 +126,7 @@ class RelationshipServiceImpl( blocking = false, muting = false, followRequest = false, - ignoreFollowRequestFromTarget = false + ignoreFollowRequestToTarget = false ) if (relationship == null) { @@ -191,16 +191,16 @@ class RelationshipServiceImpl( } override suspend fun ignoreFollowRequest(actorId: Long, targetId: Long) { - val relationship = relationshipRepository.findByUserIdAndTargetUserId(actorId, targetId) - ?.copy(ignoreFollowRequestFromTarget = true) + val relationship = relationshipRepository.findByUserIdAndTargetUserId(targetId, actorId) + ?.copy(ignoreFollowRequestToTarget = true) ?: Relationship( - actorId = actorId, - targetActorId = targetId, + actorId = targetId, + targetActorId = actorId, following = false, blocking = false, muting = false, followRequest = false, - ignoreFollowRequestFromTarget = true + ignoreFollowRequestToTarget = true ) relationshipRepository.save(relationship) @@ -263,7 +263,7 @@ class RelationshipServiceImpl( blocking = false, muting = true, followRequest = false, - ignoreFollowRequestFromTarget = false + ignoreFollowRequestToTarget = false ) relationshipRepository.save(relationship) diff --git a/src/main/kotlin/dev/usbharu/hideout/mastodon/service/account/AccountApiService.kt b/src/main/kotlin/dev/usbharu/hideout/mastodon/service/account/AccountApiService.kt index d94df813..8bb262c2 100644 --- a/src/main/kotlin/dev/usbharu/hideout/mastodon/service/account/AccountApiService.kt +++ b/src/main/kotlin/dev/usbharu/hideout/mastodon/service/account/AccountApiService.kt @@ -2,6 +2,7 @@ package dev.usbharu.hideout.mastodon.service.account import dev.usbharu.hideout.application.external.Transaction import dev.usbharu.hideout.core.domain.model.relationship.RelationshipRepository +import dev.usbharu.hideout.core.query.RelationshipQueryService import dev.usbharu.hideout.core.service.media.MediaService import dev.usbharu.hideout.core.service.relationship.RelationshipService import dev.usbharu.hideout.core.service.user.UpdateUserDto @@ -49,6 +50,9 @@ interface AccountApiService { suspend fun unfollow(userid: Long, target: Long): Relationship suspend fun removeFromFollowers(userid: Long, target: Long): Relationship suspend fun updateProfile(userid: Long, updateCredentials: UpdateCredentials?): Account + suspend fun followRequests(loginUser: Long): List + suspend fun acceptFollowRequest(loginUser: Long, target: Long): Relationship + suspend fun rejectFollowRequest(loginUser: Long, target: Long): Relationship } @Service @@ -59,7 +63,8 @@ class AccountApiServiceImpl( private val statusQueryService: StatusQueryService, private val relationshipService: RelationshipService, private val relationshipRepository: RelationshipRepository, - private val mediaService: MediaService + private val mediaService: MediaService, + private val relationshipQueryService: RelationshipQueryService ) : AccountApiService { override suspend fun accountsStatuses( @@ -203,6 +208,26 @@ class AccountApiServiceImpl( accountService.findById(userid) } + override suspend fun followRequests(loginUser: Long): List = transaction.transaction { + val actorIdList = relationshipQueryService + .findByTargetIdAndFollowRequestAndIgnoreFollowRequest(loginUser, true, true) + .map { it.actorId } + + return@transaction accountService.findByIds(actorIdList) + } + + override suspend fun acceptFollowRequest(loginUser: Long, target: Long): Relationship = transaction.transaction { + relationshipService.acceptFollowRequest(loginUser, target) + + return@transaction fetchRelationship(loginUser, target) + } + + override suspend fun rejectFollowRequest(loginUser: Long, target: Long): Relationship = transaction.transaction { + relationshipService.rejectFollowRequest(loginUser, target) + + return@transaction fetchRelationship(loginUser, target) + } + private fun from(account: Account): CredentialAccount { return CredentialAccount( id = account.id, @@ -250,7 +275,7 @@ class AccountApiServiceImpl( blocking = false, muting = false, followRequest = false, - ignoreFollowRequestFromTarget = false + ignoreFollowRequestToTarget = false ) val inverseRelationship = relationshipRepository.findByUserIdAndTargetUserId(targetId, userid) @@ -261,7 +286,7 @@ class AccountApiServiceImpl( blocking = false, muting = false, followRequest = false, - ignoreFollowRequestFromTarget = false + ignoreFollowRequestToTarget = false ) return Relationship( diff --git a/src/main/kotlin/dev/usbharu/hideout/mastodon/service/account/AccountService.kt b/src/main/kotlin/dev/usbharu/hideout/mastodon/service/account/AccountService.kt index 0cdc4c2a..330ea164 100644 --- a/src/main/kotlin/dev/usbharu/hideout/mastodon/service/account/AccountService.kt +++ b/src/main/kotlin/dev/usbharu/hideout/mastodon/service/account/AccountService.kt @@ -1,6 +1,7 @@ package dev.usbharu.hideout.mastodon.service.account import dev.usbharu.hideout.application.config.ApplicationConfig +import dev.usbharu.hideout.core.domain.model.actor.Actor import dev.usbharu.hideout.core.query.ActorQueryService import dev.usbharu.hideout.domain.mastodon.model.generated.Account import org.springframework.stereotype.Service @@ -8,6 +9,7 @@ import org.springframework.stereotype.Service @Service interface AccountService { suspend fun findById(id: Long): Account + suspend fun findByIds(ids: List): List } @Service @@ -17,6 +19,10 @@ class AccountServiceImpl( ) : AccountService { override suspend fun findById(id: Long): Account { val findById = actorQueryService.findById(id) + return toAccount(findById) + } + + private fun toAccount(findById: Actor): Account { val userUrl = applicationConfig.url.toString() + "/users/" + findById.id.toString() return Account( @@ -42,4 +48,7 @@ class AccountServiceImpl( followersCount = 0, ) } + + override suspend fun findByIds(ids: List): List = + actorQueryService.findByIds(ids).map { toAccount(it) } } diff --git a/src/main/resources/openapi/mastodon.yaml b/src/main/resources/openapi/mastodon.yaml index 49e382f6..854e8f68 100644 --- a/src/main/resources/openapi/mastodon.yaml +++ b/src/main/resources/openapi/mastodon.yaml @@ -576,6 +576,86 @@ paths: schema: $ref: "#/components/schemas/MediaAttachment" + /api/v1/follow_requests: + get: + tags: + - accounts + security: + - OAuth2: + - "read:follows" + parameters: + - in: query + name: max_id + schema: + type: integer + required: false + - in: query + name: since_id + schema: + type: integer + required: false + - in: query + name: limit + schema: + type: integer + required: false + responses: + 200: + description: 成功 + headers: + Link: + schema: + type: string + description: ページネーション + content: + application/json: + schema: + type: array + items: + $ref: "#/components/schemas/Account" + + /api/v1/follow_requests/{account_id}/authorize: + post: + tags: + - accounts + security: + - OAuth2: + - "write:follows" + parameters: + - in: path + name: account_id + schema: + type: string + required: true + responses: + 200: + description: 成功 + content: + application/json: + schema: + $ref: "#/components/schemas/Relationship" + + /api/v1/follow_requests/{account_id}/reject: + post: + tags: + - accounts + security: + - OAuth2: + - "write:follows" + parameters: + - in: path + name: account_id + schema: + type: string + required: true + responses: + 200: + description: 成功 + content: + application/json: + schema: + $ref: "#/components/schemas/Relationship" + components: schemas: V1MediaRequest: diff --git a/src/test/kotlin/dev/usbharu/hideout/core/service/relationship/RelationshipServiceImplTest.kt b/src/test/kotlin/dev/usbharu/hideout/core/service/relationship/RelationshipServiceImplTest.kt index 8598aae1..93c4216d 100644 --- a/src/test/kotlin/dev/usbharu/hideout/core/service/relationship/RelationshipServiceImplTest.kt +++ b/src/test/kotlin/dev/usbharu/hideout/core/service/relationship/RelationshipServiceImplTest.kt @@ -67,7 +67,7 @@ class RelationshipServiceImplTest { blocking = false, muting = false, followRequest = true, - ignoreFollowRequestFromTarget = false + ignoreFollowRequestToTarget = false ) ) ) @@ -91,7 +91,7 @@ class RelationshipServiceImplTest { blocking = false, muting = false, followRequest = true, - ignoreFollowRequestFromTarget = false + ignoreFollowRequestToTarget = false ) ) ) @@ -110,7 +110,7 @@ class RelationshipServiceImplTest { blocking = true, muting = false, followRequest = false, - ignoreFollowRequestFromTarget = false + ignoreFollowRequestToTarget = false ) ) @@ -129,7 +129,7 @@ class RelationshipServiceImplTest { blocking = true, muting = false, followRequest = false, - ignoreFollowRequestFromTarget = false + ignoreFollowRequestToTarget = false ) ) @@ -152,7 +152,7 @@ class RelationshipServiceImplTest { blocking = false, muting = false, followRequest = false, - ignoreFollowRequestFromTarget = false + ignoreFollowRequestToTarget = false ) ) @@ -167,7 +167,7 @@ class RelationshipServiceImplTest { blocking = false, muting = false, followRequest = false, - ignoreFollowRequestFromTarget = false + ignoreFollowRequestToTarget = false ) ) ) @@ -186,7 +186,7 @@ class RelationshipServiceImplTest { blocking = false, muting = false, followRequest = false, - ignoreFollowRequestFromTarget = false + ignoreFollowRequestToTarget = true ) ) @@ -198,7 +198,7 @@ class RelationshipServiceImplTest { blocking = false, muting = false, followRequest = false, - ignoreFollowRequestFromTarget = true + ignoreFollowRequestToTarget = false ) ) @@ -222,7 +222,7 @@ class RelationshipServiceImplTest { blocking = true, muting = false, followRequest = false, - ignoreFollowRequestFromTarget = false + ignoreFollowRequestToTarget = false ) ) ) @@ -246,7 +246,7 @@ class RelationshipServiceImplTest { blocking = true, muting = false, followRequest = false, - ignoreFollowRequestFromTarget = false + ignoreFollowRequestToTarget = false ) ) ) @@ -266,7 +266,7 @@ class RelationshipServiceImplTest { blocking = false, muting = false, followRequest = true, - ignoreFollowRequestFromTarget = false + ignoreFollowRequestToTarget = false ) ) @@ -280,7 +280,7 @@ class RelationshipServiceImplTest { blocking = false, muting = false, followRequest = false, - ignoreFollowRequestFromTarget = false + ignoreFollowRequestToTarget = false ) ) @@ -302,7 +302,7 @@ class RelationshipServiceImplTest { blocking = false, muting = false, followRequest = true, - ignoreFollowRequestFromTarget = false + ignoreFollowRequestToTarget = false ) ) @@ -317,7 +317,7 @@ class RelationshipServiceImplTest { blocking = false, muting = false, followRequest = false, - ignoreFollowRequestFromTarget = false + ignoreFollowRequestToTarget = false ) ) ) @@ -366,7 +366,7 @@ class RelationshipServiceImplTest { blocking = false, muting = false, followRequest = false, - ignoreFollowRequestFromTarget = false + ignoreFollowRequestToTarget = false ) ) ) @@ -420,7 +420,7 @@ class RelationshipServiceImplTest { blocking = false, muting = false, followRequest = true, - ignoreFollowRequestFromTarget = false + ignoreFollowRequestToTarget = false ) ) @@ -435,7 +435,7 @@ class RelationshipServiceImplTest { blocking = false, muting = false, followRequest = false, - ignoreFollowRequestFromTarget = false + ignoreFollowRequestToTarget = false ) ) ) @@ -459,7 +459,7 @@ class RelationshipServiceImplTest { blocking = false, muting = false, followRequest = true, - ignoreFollowRequestFromTarget = false + ignoreFollowRequestToTarget = false ) ) @@ -474,7 +474,7 @@ class RelationshipServiceImplTest { blocking = false, muting = false, followRequest = false, - ignoreFollowRequestFromTarget = false + ignoreFollowRequestToTarget = false ) ) ) @@ -500,7 +500,7 @@ class RelationshipServiceImplTest { blocking = false, muting = false, followRequest = false, - ignoreFollowRequestFromTarget = false + ignoreFollowRequestToTarget = false ) ) @@ -516,13 +516,13 @@ class RelationshipServiceImplTest { verify(relationshipRepository, times(1)).save( eq( Relationship( - actorId = 1234, - targetActorId = 5678, + actorId = 5678, + targetActorId = 1234, following = false, blocking = false, muting = false, followRequest = false, - ignoreFollowRequestFromTarget = true + ignoreFollowRequestToTarget = true ) ) ) @@ -539,7 +539,7 @@ class RelationshipServiceImplTest { blocking = false, muting = false, followRequest = false, - ignoreFollowRequestFromTarget = false + ignoreFollowRequestToTarget = false ) ) @@ -554,7 +554,7 @@ class RelationshipServiceImplTest { blocking = false, muting = false, followRequest = false, - ignoreFollowRequestFromTarget = false + ignoreFollowRequestToTarget = false ) ) ) @@ -578,7 +578,7 @@ class RelationshipServiceImplTest { blocking = false, muting = false, followRequest = false, - ignoreFollowRequestFromTarget = false + ignoreFollowRequestToTarget = false ) ) @@ -593,7 +593,7 @@ class RelationshipServiceImplTest { blocking = false, muting = false, followRequest = false, - ignoreFollowRequestFromTarget = false + ignoreFollowRequestToTarget = false ) ) ) @@ -618,7 +618,7 @@ class RelationshipServiceImplTest { blocking = false, muting = false, followRequest = false, - ignoreFollowRequestFromTarget = false + ignoreFollowRequestToTarget = false ) ) @@ -638,7 +638,7 @@ class RelationshipServiceImplTest { blocking = true, muting = false, followRequest = false, - ignoreFollowRequestFromTarget = false + ignoreFollowRequestToTarget = false ) ) @@ -653,7 +653,7 @@ class RelationshipServiceImplTest { blocking = false, muting = false, followRequest = false, - ignoreFollowRequestFromTarget = false + ignoreFollowRequestToTarget = false ) ) ) @@ -677,7 +677,7 @@ class RelationshipServiceImplTest { blocking = true, muting = false, followRequest = false, - ignoreFollowRequestFromTarget = false + ignoreFollowRequestToTarget = false ) ) @@ -717,7 +717,7 @@ class RelationshipServiceImplTest { blocking = false, muting = false, followRequest = false, - ignoreFollowRequestFromTarget = false + ignoreFollowRequestToTarget = false ) ) @@ -739,7 +739,7 @@ class RelationshipServiceImplTest { blocking = false, muting = true, followRequest = false, - ignoreFollowRequestFromTarget = false + ignoreFollowRequestToTarget = false ) ) ) @@ -756,7 +756,7 @@ class RelationshipServiceImplTest { blocking = false, muting = true, followRequest = false, - ignoreFollowRequestFromTarget = false + ignoreFollowRequestToTarget = false ) ) @@ -771,7 +771,7 @@ class RelationshipServiceImplTest { blocking = false, muting = false, followRequest = false, - ignoreFollowRequestFromTarget = false + ignoreFollowRequestToTarget = false ) ) ) diff --git a/src/test/kotlin/dev/usbharu/hideout/mastodon/service/account/AccountApiServiceImplTest.kt b/src/test/kotlin/dev/usbharu/hideout/mastodon/service/account/AccountApiServiceImplTest.kt index 5beffa32..652997c5 100644 --- a/src/test/kotlin/dev/usbharu/hideout/mastodon/service/account/AccountApiServiceImplTest.kt +++ b/src/test/kotlin/dev/usbharu/hideout/mastodon/service/account/AccountApiServiceImplTest.kt @@ -4,6 +4,7 @@ import dev.usbharu.hideout.application.external.Transaction import dev.usbharu.hideout.core.domain.model.actor.ActorRepository import dev.usbharu.hideout.core.domain.model.relationship.RelationshipRepository import dev.usbharu.hideout.core.query.FollowerQueryService +import dev.usbharu.hideout.core.query.RelationshipQueryService import dev.usbharu.hideout.core.service.media.MediaService import dev.usbharu.hideout.core.service.relationship.RelationshipService import dev.usbharu.hideout.core.service.user.UserService @@ -49,6 +50,9 @@ class AccountApiServiceImplTest { @Mock private lateinit var relationshipRepository: RelationshipRepository + @Mock + private lateinit var relationshipQueryService: RelationshipQueryService + @Mock private lateinit var mediaService: MediaService @@ -214,7 +218,7 @@ class AccountApiServiceImplTest { blocking = false, muting = false, followRequest = false, - ignoreFollowRequestFromTarget = false + ignoreFollowRequestToTarget = false ) ) @@ -249,7 +253,7 @@ class AccountApiServiceImplTest { blocking = false, muting = false, followRequest = false, - ignoreFollowRequestFromTarget = false + ignoreFollowRequestToTarget = false ) ) whenever(relationshipRepository.findByUserIdAndTargetUserId(eq(userId), eq(followeeId))).doReturn( @@ -260,7 +264,7 @@ class AccountApiServiceImplTest { blocking = false, muting = false, followRequest = false, - ignoreFollowRequestFromTarget = false + ignoreFollowRequestToTarget = false ) ) From ef6c97b08c5b1e7a8ad46658ff5a34f0e20a49b0 Mon Sep 17 00:00:00 2001 From: usbharu <64310155+usbharu@users.noreply.github.com> Date: Tue, 12 Dec 2023 13:54:35 +0900 Subject: [PATCH 5/6] =?UTF-8?q?fix:=20=E3=83=95=E3=82=A9=E3=83=AD=E3=83=BC?= =?UTF-8?q?=E3=83=AA=E3=82=AF=E3=82=A8=E3=82=B9=E3=83=88API=E3=82=92?= =?UTF-8?q?=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../RelationshipQueryServiceImpl.kt | 19 +++++++++-- .../core/query/RelationshipQueryService.kt | 3 ++ .../relationship/RelationshipServiceImpl.kt | 7 ++-- .../account/MastodonAccountApiController.kt | 32 +++++++++++++++++++ .../service/account/AccountApiService.kt | 26 +++++++++++++-- .../service/account/AccountService.kt | 2 +- src/main/resources/openapi/mastodon.yaml | 10 +++--- 7 files changed, 85 insertions(+), 14 deletions(-) diff --git a/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposedquery/RelationshipQueryServiceImpl.kt b/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposedquery/RelationshipQueryServiceImpl.kt index 5a5127e5..43d8206a 100644 --- a/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposedquery/RelationshipQueryServiceImpl.kt +++ b/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposedquery/RelationshipQueryServiceImpl.kt @@ -5,6 +5,7 @@ import dev.usbharu.hideout.core.domain.model.relationship.Relationships import dev.usbharu.hideout.core.domain.model.relationship.toRelationships import dev.usbharu.hideout.core.query.RelationshipQueryService import org.jetbrains.exposed.sql.and +import org.jetbrains.exposed.sql.andWhere import org.jetbrains.exposed.sql.select import org.springframework.stereotype.Service @@ -15,16 +16,28 @@ class RelationshipQueryServiceImpl : RelationshipQueryService { .map { it.toRelationships() } override suspend fun findByTargetIdAndFollowRequestAndIgnoreFollowRequest( + maxId: Long?, + sinceId: Long?, + limit: Int, targetId: Long, followRequest: Boolean, ignoreFollowRequest: Boolean ): List { - return Relationships + val query = Relationships .select { Relationships.targetActorId.eq(targetId) .and(Relationships.followRequest.eq(followRequest)) .and(Relationships.ignoreFollowRequestFromTarget.eq(ignoreFollowRequest)) - } - .map { it.toRelationships() } + }.limit(limit) + + if (maxId != null) { + query.andWhere { Relationships.id greater maxId } + } + + if (sinceId != null) { + query.andWhere { Relationships.id less sinceId } + } + + return query.map { it.toRelationships() } } } diff --git a/src/main/kotlin/dev/usbharu/hideout/core/query/RelationshipQueryService.kt b/src/main/kotlin/dev/usbharu/hideout/core/query/RelationshipQueryService.kt index 324dc171..6278ae30 100644 --- a/src/main/kotlin/dev/usbharu/hideout/core/query/RelationshipQueryService.kt +++ b/src/main/kotlin/dev/usbharu/hideout/core/query/RelationshipQueryService.kt @@ -6,6 +6,9 @@ interface RelationshipQueryService { suspend fun findByTargetIdAndFollowing(targetId: Long, following: Boolean): List suspend fun findByTargetIdAndFollowRequestAndIgnoreFollowRequest( + maxId: Long?, + sinceId: Long?, + limit: Int, targetId: Long, followRequest: Boolean, ignoreFollowRequest: Boolean diff --git a/src/main/kotlin/dev/usbharu/hideout/core/service/relationship/RelationshipServiceImpl.kt b/src/main/kotlin/dev/usbharu/hideout/core/service/relationship/RelationshipServiceImpl.kt index 67bd1bfe..966163e8 100644 --- a/src/main/kotlin/dev/usbharu/hideout/core/service/relationship/RelationshipServiceImpl.kt +++ b/src/main/kotlin/dev/usbharu/hideout/core/service/relationship/RelationshipServiceImpl.kt @@ -73,14 +73,17 @@ class RelationshipServiceImpl( relationshipRepository.save(relationship) + val remoteUser = isRemoteUser(targetId) if (remoteUser != null) { val user = actorQueryService.findById(actorId) apSendFollowService.sendFollow(SendFollowDto(user, remoteUser)) } else { - // TODO: フォロー許可制ユーザーを実装したら消す - acceptFollowRequest(targetId, actorId) + val target = actorQueryService.findById(targetId) + if (target.locked.not()) { + acceptFollowRequest(targetId, actorId) + } } logger.info("SUCCESS Follow Request userId: {} targetId: {}", actorId, targetId) diff --git a/src/main/kotlin/dev/usbharu/hideout/mastodon/interfaces/api/account/MastodonAccountApiController.kt b/src/main/kotlin/dev/usbharu/hideout/mastodon/interfaces/api/account/MastodonAccountApiController.kt index b516c3ec..124f1e75 100644 --- a/src/main/kotlin/dev/usbharu/hideout/mastodon/interfaces/api/account/MastodonAccountApiController.kt +++ b/src/main/kotlin/dev/usbharu/hideout/mastodon/interfaces/api/account/MastodonAccountApiController.kt @@ -153,4 +153,36 @@ class MastodonAccountApiController( return ResponseEntity.ok(removeFromFollowers) } + + override suspend fun apiV1FollowRequestsAccountIdAuthorizePost(accountId: String): ResponseEntity { + val principal = SecurityContextHolder.getContext().getAuthentication().principal as Jwt + + val userid = principal.getClaim("uid").toLong() + + val acceptFollowRequest = accountApiService.acceptFollowRequest(userid, accountId.toLong()) + + return ResponseEntity.ok(acceptFollowRequest) + } + + override suspend fun apiV1FollowRequestsAccountIdRejectPost(accountId: String): ResponseEntity { + val principal = SecurityContextHolder.getContext().getAuthentication().principal as Jwt + + val userid = principal.getClaim("uid").toLong() + + val rejectFollowRequest = accountApiService.rejectFollowRequest(userid, accountId.toLong()) + + return ResponseEntity.ok(rejectFollowRequest) + } + + override fun apiV1FollowRequestsGet(maxId: String?, sinceId: String?, limit: Int?): ResponseEntity> = + runBlocking { + val principal = SecurityContextHolder.getContext().getAuthentication().principal as Jwt + + val userid = principal.getClaim("uid").toLong() + + val accountFlow = + accountApiService.followRequests(userid, maxId?.toLong(), sinceId?.toLong(), limit ?: 20, false) + .asFlow() + ResponseEntity.ok(accountFlow) + } } diff --git a/src/main/kotlin/dev/usbharu/hideout/mastodon/service/account/AccountApiService.kt b/src/main/kotlin/dev/usbharu/hideout/mastodon/service/account/AccountApiService.kt index 8bb262c2..91731004 100644 --- a/src/main/kotlin/dev/usbharu/hideout/mastodon/service/account/AccountApiService.kt +++ b/src/main/kotlin/dev/usbharu/hideout/mastodon/service/account/AccountApiService.kt @@ -50,7 +50,14 @@ interface AccountApiService { suspend fun unfollow(userid: Long, target: Long): Relationship suspend fun removeFromFollowers(userid: Long, target: Long): Relationship suspend fun updateProfile(userid: Long, updateCredentials: UpdateCredentials?): Account - suspend fun followRequests(loginUser: Long): List + suspend fun followRequests( + loginUser: Long, + maxId: Long?, + sinceId: Long?, + limit: Int = 20, + withIgnore: Boolean + ): List + suspend fun acceptFollowRequest(loginUser: Long, target: Long): Relationship suspend fun rejectFollowRequest(loginUser: Long, target: Long): Relationship } @@ -208,9 +215,22 @@ class AccountApiServiceImpl( accountService.findById(userid) } - override suspend fun followRequests(loginUser: Long): List = transaction.transaction { + override suspend fun followRequests( + loginUser: Long, + maxId: Long?, + sinceId: Long?, + limit: Int, + withIgnore: Boolean + ): List = transaction.transaction { val actorIdList = relationshipQueryService - .findByTargetIdAndFollowRequestAndIgnoreFollowRequest(loginUser, true, true) + .findByTargetIdAndFollowRequestAndIgnoreFollowRequest( + maxId = maxId, + sinceId = sinceId, + limit = limit, + targetId = loginUser, + followRequest = true, + ignoreFollowRequest = withIgnore + ) .map { it.actorId } return@transaction accountService.findByIds(actorIdList) diff --git a/src/main/kotlin/dev/usbharu/hideout/mastodon/service/account/AccountService.kt b/src/main/kotlin/dev/usbharu/hideout/mastodon/service/account/AccountService.kt index 330ea164..9904e6ce 100644 --- a/src/main/kotlin/dev/usbharu/hideout/mastodon/service/account/AccountService.kt +++ b/src/main/kotlin/dev/usbharu/hideout/mastodon/service/account/AccountService.kt @@ -36,7 +36,7 @@ class AccountServiceImpl( avatarStatic = "$userUrl/icon.jpg", header = "$userUrl/header.jpg", headerStatic = "$userUrl/header.jpg", - locked = false, + locked = findById.locked, fields = emptyList(), emojis = emptyList(), bot = false, diff --git a/src/main/resources/openapi/mastodon.yaml b/src/main/resources/openapi/mastodon.yaml index 854e8f68..15df90d9 100644 --- a/src/main/resources/openapi/mastodon.yaml +++ b/src/main/resources/openapi/mastodon.yaml @@ -579,7 +579,7 @@ paths: /api/v1/follow_requests: get: tags: - - accounts + - account security: - OAuth2: - "read:follows" @@ -587,12 +587,12 @@ paths: - in: query name: max_id schema: - type: integer + type: string required: false - in: query name: since_id schema: - type: integer + type: string required: false - in: query name: limit @@ -617,7 +617,7 @@ paths: /api/v1/follow_requests/{account_id}/authorize: post: tags: - - accounts + - account security: - OAuth2: - "write:follows" @@ -638,7 +638,7 @@ paths: /api/v1/follow_requests/{account_id}/reject: post: tags: - - accounts + - account security: - OAuth2: - "write:follows" From 69a889a8d70a68ad0afb3d1a7027ffc73308cbef Mon Sep 17 00:00:00 2001 From: usbharu <64310155+usbharu@users.noreply.github.com> Date: Tue, 12 Dec 2023 16:48:25 +0900 Subject: [PATCH 6/6] =?UTF-8?q?feat:=20AccountQueryService=E3=82=92?= =?UTF-8?q?=E4=BD=9C=E6=88=90=E3=81=97=E3=80=81=E3=82=A2=E3=82=AB=E3=82=A6?= =?UTF-8?q?=E3=83=B3=E3=83=88API=E3=81=8B=E3=82=89=E5=8F=96=E5=BE=97?= =?UTF-8?q?=E3=81=95=E3=82=8C=E3=82=8B=E6=83=85=E5=A0=B1=E3=82=92=E6=AD=A3?= =?UTF-8?q?=E7=A2=BA=E3=81=AB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../exposedquery/AccountQueryServiceImpl.kt | 100 ++++++++++++++++++ .../mastodon/query/AccountQueryService.kt | 8 ++ .../service/account/AccountService.kt | 40 +------ 3 files changed, 113 insertions(+), 35 deletions(-) create mode 100644 src/main/kotlin/dev/usbharu/hideout/mastodon/infrastructure/exposedquery/AccountQueryServiceImpl.kt create mode 100644 src/main/kotlin/dev/usbharu/hideout/mastodon/query/AccountQueryService.kt diff --git a/src/main/kotlin/dev/usbharu/hideout/mastodon/infrastructure/exposedquery/AccountQueryServiceImpl.kt b/src/main/kotlin/dev/usbharu/hideout/mastodon/infrastructure/exposedquery/AccountQueryServiceImpl.kt new file mode 100644 index 00000000..13daaf89 --- /dev/null +++ b/src/main/kotlin/dev/usbharu/hideout/mastodon/infrastructure/exposedquery/AccountQueryServiceImpl.kt @@ -0,0 +1,100 @@ +package dev.usbharu.hideout.mastodon.infrastructure.exposedquery + +import dev.usbharu.hideout.application.config.ApplicationConfig +import dev.usbharu.hideout.core.domain.exception.FailedToGetResourcesException +import dev.usbharu.hideout.core.domain.model.relationship.Relationships +import dev.usbharu.hideout.core.infrastructure.exposedrepository.Actors +import dev.usbharu.hideout.core.infrastructure.exposedrepository.Posts +import dev.usbharu.hideout.domain.mastodon.model.generated.Account +import dev.usbharu.hideout.mastodon.query.AccountQueryService +import dev.usbharu.hideout.util.singleOr +import org.jetbrains.exposed.sql.* +import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq +import org.springframework.stereotype.Repository +import java.time.Instant + +@Repository +class AccountQueryServiceImpl(private val applicationConfig: ApplicationConfig) : AccountQueryService { + override suspend fun findById(accountId: Long): Account { + val followingCount = Count(Relationships.actorId.eq(Actors.id), true).alias("following_count") + val followersCount = Count(Relationships.targetActorId.eq(Actors.id), true).alias("followers_count") + val postsCount = Posts.id.countDistinct().alias("posts_count") + val lastCreated = Posts.createdAt.max().alias("last_created") + val query = Actors + .join(Relationships, JoinType.LEFT) { + Actors.id eq Relationships.actorId or (Actors.id eq Relationships.targetActorId) + } + .leftJoin(Posts) + .slice( + followingCount, + followersCount, + *(Actors.realFields.toTypedArray()), + lastCreated, + postsCount + ) + .select { Actors.id eq accountId and (Relationships.following eq true or (Relationships.following.isNull())) } + .groupBy(Actors.id) + + return query + .singleOr { FailedToGetResourcesException("accountId: $accountId wad not exist or duplicate", it) } + .let { toAccount(it, followingCount, followersCount, postsCount, lastCreated) } + } + + override suspend fun findByIds(accountIds: List): List { + val followingCount = Count(Relationships.actorId.eq(Actors.id), true).alias("following_count") + val followersCount = Count(Relationships.targetActorId.eq(Actors.id), true).alias("followers_count") + val postsCount = Posts.id.countDistinct().alias("posts_count") + val lastCreated = Posts.createdAt.max().alias("last_created") + val query = Actors + .join(Relationships, JoinType.LEFT) { + Actors.id eq Relationships.actorId or (Actors.id eq Relationships.targetActorId) + } + .leftJoin(Posts) + .slice( + followingCount, + followersCount, + *(Actors.realFields.toTypedArray()), + lastCreated, + postsCount + ) + .select { Actors.id inList accountIds and (Relationships.following eq true or (Relationships.following.isNull())) } + .groupBy(Actors.id) + + return query + .map { toAccount(it, followingCount, followersCount, postsCount, lastCreated) } + } + + private fun toAccount( + resultRow: ResultRow, + followingCount: ExpressionAlias, + followersCount: ExpressionAlias, + postsCount: ExpressionAlias, + lastCreated: ExpressionAlias + ): Account { + val userUrl = "${applicationConfig.url}/users/${resultRow[Actors.id]}" + + return Account( + id = resultRow[Actors.id].toString(), + username = resultRow[Actors.name], + acct = "${resultRow[Actors.name]}@${resultRow[Actors.domain]}", + url = resultRow[Actors.url], + displayName = resultRow[Actors.screenName], + note = resultRow[Actors.description], + avatar = userUrl + "/icon.jpg", + avatarStatic = userUrl + "/icon.jpg", + header = userUrl + "/header.jpg", + headerStatic = userUrl + "/header.jpg", + locked = resultRow[Actors.locked], + fields = emptyList(), + emojis = emptyList(), + bot = false, + group = false, + discoverable = true, + createdAt = Instant.ofEpochMilli(resultRow[Actors.createdAt]).toString(), + lastStatusAt = resultRow[lastCreated]?.let { Instant.ofEpochMilli(it).toString() }, + statusesCount = resultRow[postsCount].toInt(), + followersCount = resultRow[followersCount].toInt(), + followingCount = resultRow[followingCount].toInt(), + ) + } +} diff --git a/src/main/kotlin/dev/usbharu/hideout/mastodon/query/AccountQueryService.kt b/src/main/kotlin/dev/usbharu/hideout/mastodon/query/AccountQueryService.kt new file mode 100644 index 00000000..37eb2d98 --- /dev/null +++ b/src/main/kotlin/dev/usbharu/hideout/mastodon/query/AccountQueryService.kt @@ -0,0 +1,8 @@ +package dev.usbharu.hideout.mastodon.query + +import dev.usbharu.hideout.domain.mastodon.model.generated.Account + +interface AccountQueryService { + suspend fun findById(accountId: Long): Account + suspend fun findByIds(accountIds: List): List +} diff --git a/src/main/kotlin/dev/usbharu/hideout/mastodon/service/account/AccountService.kt b/src/main/kotlin/dev/usbharu/hideout/mastodon/service/account/AccountService.kt index 9904e6ce..88cb1f88 100644 --- a/src/main/kotlin/dev/usbharu/hideout/mastodon/service/account/AccountService.kt +++ b/src/main/kotlin/dev/usbharu/hideout/mastodon/service/account/AccountService.kt @@ -1,9 +1,7 @@ package dev.usbharu.hideout.mastodon.service.account -import dev.usbharu.hideout.application.config.ApplicationConfig -import dev.usbharu.hideout.core.domain.model.actor.Actor -import dev.usbharu.hideout.core.query.ActorQueryService import dev.usbharu.hideout.domain.mastodon.model.generated.Account +import dev.usbharu.hideout.mastodon.query.AccountQueryService import org.springframework.stereotype.Service @Service @@ -14,41 +12,13 @@ interface AccountService { @Service class AccountServiceImpl( - private val actorQueryService: ActorQueryService, - private val applicationConfig: ApplicationConfig + private val accountQueryService: AccountQueryService ) : AccountService { override suspend fun findById(id: Long): Account { - val findById = actorQueryService.findById(id) - return toAccount(findById) + return accountQueryService.findById(id) } - private fun toAccount(findById: Actor): Account { - val userUrl = applicationConfig.url.toString() + "/users/" + findById.id.toString() - - return Account( - id = findById.id.toString(), - username = findById.name, - acct = "${findById.name}@${findById.domain}", - url = findById.url, - displayName = findById.screenName, - note = findById.description, - avatar = "$userUrl/icon.jpg", - avatarStatic = "$userUrl/icon.jpg", - header = "$userUrl/header.jpg", - headerStatic = "$userUrl/header.jpg", - locked = findById.locked, - fields = emptyList(), - emojis = emptyList(), - bot = false, - group = false, - discoverable = false, - createdAt = findById.createdAt.toString(), - lastStatusAt = findById.createdAt.toString(), - statusesCount = 0, - followersCount = 0, - ) + override suspend fun findByIds(ids: List): List { + return accountQueryService.findByIds(ids) } - - override suspend fun findByIds(ids: List): List = - actorQueryService.findByIds(ids).map { toAccount(it) } }