From 869ef1e111d58d9b897c25645061ff698a16e21c Mon Sep 17 00:00:00 2001 From: usbharu <64310155+usbharu@users.noreply.github.com> Date: Sat, 1 Jun 2024 18:23:44 +0900 Subject: [PATCH] wip --- hideout-activitypub/build.gradle.kts | 21 + .../gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 60756 bytes .../gradle/wrapper/gradle-wrapper.properties | 6 + hideout-activitypub/gradlew | 234 +++++++++++ hideout-activitypub/gradlew.bat | 89 ++++ hideout-activitypub/settings.gradle.kts | 5 + hideout-activitypub/src/main/kotlin/Main.kt | 5 + .../src/e2eTest/kotlin/AssertionUtil.kt | 1 - .../kotlin/mastodon/account/AccountApiTest.kt | 2 - ...WithHttpSignatureSecurityContextFactory.kt | 1 - .../hideout/activitypub/domain/Constant.kt | 41 -- .../exception/ActivityPubProcessException.kt | 37 -- .../exception/FailedProcessException.kt | 37 -- ...FailedToGetActivityPubResourceException.kt | 32 -- .../HttpSignatureUnauthorizedException.kt | 37 -- .../IllegalActivityPubObjectException.kt | 31 -- .../domain/exception/JsonParseException.kt | 31 -- .../activitypub/domain/model/Accept.kt | 62 --- .../activitypub/domain/model/Announce.kt | 76 ---- .../activitypub/domain/model/Create.kt | 79 ---- .../activitypub/domain/model/Delete.kt | 78 ---- .../activitypub/domain/model/Document.kt | 57 --- .../hideout/activitypub/domain/model/Emoji.kt | 66 --- .../activitypub/domain/model/Follow.kt | 61 --- .../activitypub/domain/model/HasActor.kt | 21 - .../hideout/activitypub/domain/model/HasId.kt | 21 - .../hideout/activitypub/domain/model/Image.kt | 55 --- .../activitypub/domain/model/JsonLd.kt | 101 ----- .../hideout/activitypub/domain/model/Key.kt | 53 --- .../hideout/activitypub/domain/model/Like.kt | 73 ---- .../hideout/activitypub/domain/model/Note.kt | 110 ----- .../activitypub/domain/model/Person.kt | 102 ----- .../activitypub/domain/model/Reject.kt | 59 --- .../domain/model/StringOrObject.kt | 76 ---- .../activitypub/domain/model/Tombstone.kt | 41 -- .../hideout/activitypub/domain/model/Undo.kt | 66 --- .../domain/model/nodeinfo/Nodeinfo.kt | 26 -- .../domain/model/nodeinfo/Nodeinfo2_0.kt | 67 ---- .../domain/model/objects/Object.kt | 77 ---- .../model/objects/ObjectDeserializer.kt | 110 ----- .../domain/model/objects/ObjectValue.kt | 43 -- .../domain/model/webfinger/WebFinger.kt | 21 - .../ExposedAnnounceQueryService.kt | 82 ---- .../exposedquery/NoteQueryServiceImpl.kt | 129 ------ .../interfaces/api/actor/UserAPController.kt | 29 -- .../api/actor/UserAPControllerImpl.kt | 38 -- .../api/hostmeta/HostMetaController.kt | 52 --- .../interfaces/api/inbox/InboxController.kt | 38 -- .../api/inbox/InboxControllerImpl.kt | 133 ------ .../api/nodeinfo/NodeinfoController.kt | 82 ---- .../interfaces/api/note/NoteApController.kt | 29 -- .../api/note/NoteApControllerImpl.kt | 49 --- .../interfaces/api/outbox/OutboxController.kt | 28 -- .../api/outbox/OutboxControllerImpl.kt | 27 -- .../api/webfinger/WebFingerController.kt | 68 ---- .../activitypub/query/AnnounceQueryService.kt | 27 -- .../activitypub/query/NoteQueryService.kt | 25 -- .../activity/accept/ApAcceptProcessor.kt | 62 --- .../activity/accept/ApSendAcceptService.kt | 49 --- .../activity/announce/ApAnnounceProcessor.kt | 37 -- .../activity/create/ApSendCreateService.kt | 23 -- .../create/ApSendCreateServiceImpl.kt | 66 --- .../create/CreateActivityProcessor.kt | 38 -- .../activity/delete/APDeleteProcessor.kt | 66 --- .../activity/delete/APSendDeleteService.kt | 86 ---- .../activity/follow/APFollowProcessor.kt | 49 --- .../activity/follow/APReceiveFollowService.kt | 42 -- .../activity/follow/APSendFollowService.kt | 43 -- .../service/activity/like/APLikeProcessor.kt | 77 ---- .../activity/like/APReactionService.kt | 93 ----- .../activity/reject/ApRejectProcessor.kt | 68 ---- .../activity/reject/ApSendRejectService.kt | 23 -- .../reject/ApSendRejectServiceImpl.kt | 45 --- .../activity/undo/APSendUndoService.kt | 23 -- .../activity/undo/APSendUndoServiceImpl.kt | 50 --- .../service/activity/undo/APUndoProcessor.kt | 145 ------- .../service/common/APRequestService.kt | 41 -- .../service/common/APRequestServiceImpl.kt | 251 ------------ .../common/APResourceResolveService.kt | 31 -- .../common/APResourceResolveServiceImpl.kt | 104 ----- .../activitypub/service/common/APService.kt | 103 ----- .../common/AbstractActivityPubProcessor.kt | 56 --- .../common/ActivityPubProcessContext.kt | 30 -- .../service/common/ActivityPubProcessor.kt | 27 -- .../service/common/ActivityType.kt | 49 --- .../service/common/ActivityVocabulary.kt | 74 ---- .../common/ExtendedActivityVocabulary.kt | 75 ---- .../service/common/ExtendedVocabulary.kt | 21 - .../service/objects/emoji/EmojiService.kt | 26 -- .../service/objects/emoji/EmojiServiceImpl.kt | 95 ----- .../service/objects/note/APNoteService.kt | 275 ------------- .../service/objects/note/NoteApApiService.kt | 23 -- .../objects/note/NoteApApiServiceImpl.kt | 72 ---- .../service/objects/user/APUserService.kt | 166 -------- .../service/webfinger/WebFingerApiService.kt | 44 -- .../application/config/SecurityConfig.kt | 1 - .../event/relationship/RelationshipEvent.kt | 40 ++ .../hideout/core/domain/model/actor/Acct.kt | 19 - .../hideout/core/domain/model/actor/Actor.kt | 269 ------------- .../domain/model/actor/ActorRepository.kt | 49 --- .../core/domain/model/emoji/EmojiId.kt | 2 +- .../ExposedNotificationRepository.kt | 102 ----- .../hideout/core/domain/model/post/Post.kt | 220 ---------- .../core/domain/model/post/Post2Repository.kt | 2 +- .../core/domain/model/post/PostContent.kt | 2 + .../core/domain/model/post/PostOverview.kt | 6 +- .../core/domain/model/post/PostRepository.kt | 37 -- .../model/relationship/Relationship2.kt | 106 +++++ .../relationship/Relationship2Repository.kt} | 9 +- .../relationship/RelationshipRepository.kt | 72 ---- .../RelationshipRepositoryImpl.kt | 1 - .../shared/domainevent/DomainEventStorable.kt | 29 -- .../infrastructure/exposed/PostQueryMapper.kt | 41 -- .../exposed/PostResultRowMapper.kt | 44 -- .../infrastructure/exposed/UserQueryMapper.kt | 28 -- .../exposed/UserResultRowMapper.kt | 53 --- .../exposedquery/FollowerQueryServiceImpl.kt | 38 -- .../exposedrepository/ActorRepositoryImpl.kt | 182 --------- .../ExposedPost2Repository.kt | 175 ++++++++ .../exposedrepository/PostRepositoryImpl.kt | 232 ----------- .../HttpSignatureUserDetailsService.kt | 1 - .../oauth2/UserDetailsServiceImpl.kt | 1 - .../interfaces/api/auth/AuthController.kt | 1 - .../core/query/FollowerQueryService.kt | 25 -- .../core/service/auth/AuthApiService.kt | 23 -- .../core/service/auth/AuthApiServiceImpl.kt | 67 ---- .../core/service/filter/MuteProcessService.kt | 30 -- .../service/filter/MuteProcessServiceImpl.kt | 139 ------- .../core/service/follow/SendFollowDto.kt | 21 - .../notification/NotificationServiceImpl.kt | 3 - .../service/notification/NotificationStore.kt | 34 -- .../hideout/core/service/post/PostService.kt | 30 -- .../core/service/post/PostServiceImpl.kt | 138 ------- .../service/reaction/ReactionServiceImpl.kt | 1 - .../relationship/RelationshipService.kt | 38 -- .../relationship/RelationshipServiceImpl.kt | 341 ---------------- .../core/service/timeline/TimelineService.kt | 87 ---- .../core/service/user/UserAuthServiceImpl.kt | 1 - .../hideout/core/service/user/UserService.kt | 40 -- .../core/service/user/UserServiceImpl.kt | 227 ----------- .../config/MastodonApiSecurityConfig.kt | 173 -------- .../exception/AccountNotFoundException.kt | 54 --- .../domain/exception/ClientException.kt | 38 -- .../domain/exception/MastodonApiException.kt | 56 --- .../domain/exception/ServerException.kt | 21 - .../exception/StatusNotFoundException.kt | 56 --- .../domain/model/MastodonApiErrorResponse.kt | 19 - .../domain/model/MastodonNotification.kt | 34 -- .../model/MastodonNotificationRepository.kt | 37 -- .../mastodon/domain/model/NotificationType.kt | 49 --- .../exposedquery/AccountQueryServiceImpl.kt | 74 ---- .../exposedquery/StatusQueryServiceImpl.kt | 250 ------------ .../ExposedMastodonNotificationRepository.kt | 131 ------ .../MongoMastodonNotificationRepository.kt | 27 -- ...goMastodonNotificationRepositoryWrapper.kt | 83 ---- .../springweb/MastodonApiControllerAdvice.kt | 111 ----- .../account/MastodonAccountApiController.kt | 250 ------------ .../api/apps/MastodonAppsApiController.kt | 53 --- .../api/filter/MastodonFilterApiController.kt | 174 -------- .../instance/MastodonInstanceApiController.kt | 30 -- .../api/media/MastodonMediaApiController.kt | 45 --- .../interfaces/api/media/MediaRequest.kt | 26 -- .../MastodonNotificationApiController.kt | 86 ---- .../status/MastodonStatusesApiContoller.kt | 65 --- .../interfaces/api/status/StatusQuery.kt | 25 -- .../interfaces/api/status/StatusesRequest.kt | 124 ------ .../timeline/MastodonTimelineApiController.kt | 95 ----- .../mastodon/query/AccountQueryService.kt | 24 -- .../mastodon/query/StatusQueryService.kt | 41 -- .../service/account/AccountApiService.kt | 379 ------------------ .../service/account/AccountService.kt | 37 -- .../mastodon/service/app/AppApiService.kt | 85 ---- .../filter/MastodonFilterApiService.kt | 301 -------------- .../service/instance/InstanceApiService.kt | 99 ----- .../mastodon/service/media/MediaApiService.kt | 26 -- .../service/media/MediaApiServiceImpl.kt | 50 --- .../notification/MastodonNotificationStore.kt | 82 ---- .../notification/NotificationApiService.kt | 39 -- .../NotificationApiServiceImpl.kt | 126 ------ .../service/status/StatusesApiService.kt | 212 ---------- .../service/timeline/TimelineApiService.kt | 61 --- .../dev/usbharu/hideout/util/AcctUtil.kt | 58 --- .../activity/accept/ApAcceptProcessorTest.kt | 2 - .../follow/APSendFollowServiceImplTest.kt | 1 - .../APResourceResolveServiceImplTest.kt | 1 - .../objects/note/APNoteServiceImplTest.kt | 3 - .../NotificationServiceImplTest.kt | 3 - .../core/service/post/PostServiceImplTest.kt | 4 - .../reaction/ReactionServiceImplTest.kt | 1 - .../RelationshipServiceImplTest.kt | 3 - .../service/timeline/TimelineServiceTest.kt | 3 - .../core/service/user/ActorServiceTest.kt | 5 - .../account/AccountApiServiceImplTest.kt | 5 - .../src/test/kotlin/utils/PostBuilder.kt | 1 - .../src/test/kotlin/utils/UserBuilder.kt | 1 - hideout-mastodon/build.gradle.kts | 21 + .../gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 60756 bytes .../gradle/wrapper/gradle-wrapper.properties | 6 + hideout-mastodon/gradlew | 234 +++++++++++ hideout-mastodon/gradlew.bat | 89 ++++ hideout-mastodon/settings.gradle.kts | 5 + hideout-mastodon/src/main/kotlin/Main.kt | 5 + .../hideout/worker/DeliverAcceptTaskRunner.kt | 1 - .../hideout/worker/DeliverCreateTaskRunner.kt | 1 - .../hideout/worker/DeliverDeleteTaskRunner.kt | 1 - .../worker/DeliverReactionTaskRunner.kt | 1 - .../hideout/worker/DeliverRejectTaskRunner.kt | 1 - .../hideout/worker/DeliverUndoTaskRunner.kt | 1 - .../hideout/worker/ReceiveFollowTaskRunner.kt | 2 - .../hideout/worker/UpdateActorWorker.kt | 1 - 210 files changed, 1055 insertions(+), 11855 deletions(-) create mode 100644 hideout-activitypub/build.gradle.kts create mode 100644 hideout-activitypub/gradle/wrapper/gradle-wrapper.jar create mode 100644 hideout-activitypub/gradle/wrapper/gradle-wrapper.properties create mode 100644 hideout-activitypub/gradlew create mode 100644 hideout-activitypub/gradlew.bat create mode 100644 hideout-activitypub/settings.gradle.kts create mode 100644 hideout-activitypub/src/main/kotlin/Main.kt delete mode 100644 hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/domain/Constant.kt delete mode 100644 hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/domain/exception/ActivityPubProcessException.kt delete mode 100644 hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/domain/exception/FailedProcessException.kt delete mode 100644 hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/domain/exception/FailedToGetActivityPubResourceException.kt delete mode 100644 hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/domain/exception/HttpSignatureUnauthorizedException.kt delete mode 100644 hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/domain/exception/IllegalActivityPubObjectException.kt delete mode 100644 hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/domain/exception/JsonParseException.kt delete mode 100644 hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/domain/model/Accept.kt delete mode 100644 hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/domain/model/Announce.kt delete mode 100644 hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/domain/model/Create.kt delete mode 100644 hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/domain/model/Delete.kt delete mode 100644 hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/domain/model/Document.kt delete mode 100644 hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/domain/model/Emoji.kt delete mode 100644 hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/domain/model/Follow.kt delete mode 100644 hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/domain/model/HasActor.kt delete mode 100644 hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/domain/model/HasId.kt delete mode 100644 hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/domain/model/Image.kt delete mode 100644 hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/domain/model/JsonLd.kt delete mode 100644 hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/domain/model/Key.kt delete mode 100644 hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/domain/model/Like.kt delete mode 100644 hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/domain/model/Note.kt delete mode 100644 hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/domain/model/Person.kt delete mode 100644 hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/domain/model/Reject.kt delete mode 100644 hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/domain/model/StringOrObject.kt delete mode 100644 hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/domain/model/Tombstone.kt delete mode 100644 hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/domain/model/Undo.kt delete mode 100644 hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/domain/model/nodeinfo/Nodeinfo.kt delete mode 100644 hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/domain/model/nodeinfo/Nodeinfo2_0.kt delete mode 100644 hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/domain/model/objects/Object.kt delete mode 100644 hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/domain/model/objects/ObjectDeserializer.kt delete mode 100644 hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/domain/model/objects/ObjectValue.kt delete mode 100644 hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/domain/model/webfinger/WebFinger.kt delete mode 100644 hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/infrastructure/exposedquery/ExposedAnnounceQueryService.kt delete mode 100644 hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/infrastructure/exposedquery/NoteQueryServiceImpl.kt delete mode 100644 hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/interfaces/api/actor/UserAPController.kt delete mode 100644 hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/interfaces/api/actor/UserAPControllerImpl.kt delete mode 100644 hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/interfaces/api/hostmeta/HostMetaController.kt delete mode 100644 hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/interfaces/api/inbox/InboxController.kt delete mode 100644 hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/interfaces/api/inbox/InboxControllerImpl.kt delete mode 100644 hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/interfaces/api/nodeinfo/NodeinfoController.kt delete mode 100644 hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/interfaces/api/note/NoteApController.kt delete mode 100644 hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/interfaces/api/note/NoteApControllerImpl.kt delete mode 100644 hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/interfaces/api/outbox/OutboxController.kt delete mode 100644 hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/interfaces/api/outbox/OutboxControllerImpl.kt delete mode 100644 hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/interfaces/api/webfinger/WebFingerController.kt delete mode 100644 hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/query/AnnounceQueryService.kt delete mode 100644 hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/query/NoteQueryService.kt delete mode 100644 hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/service/activity/accept/ApAcceptProcessor.kt delete mode 100644 hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/service/activity/accept/ApSendAcceptService.kt delete mode 100644 hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/service/activity/announce/ApAnnounceProcessor.kt delete mode 100644 hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/service/activity/create/ApSendCreateService.kt delete mode 100644 hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/service/activity/create/ApSendCreateServiceImpl.kt delete mode 100644 hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/service/activity/create/CreateActivityProcessor.kt delete mode 100644 hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/service/activity/delete/APDeleteProcessor.kt delete mode 100644 hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/service/activity/delete/APSendDeleteService.kt delete mode 100644 hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/service/activity/follow/APFollowProcessor.kt delete mode 100644 hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/service/activity/follow/APReceiveFollowService.kt delete mode 100644 hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/service/activity/follow/APSendFollowService.kt delete mode 100644 hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/service/activity/like/APLikeProcessor.kt delete mode 100644 hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/service/activity/like/APReactionService.kt delete mode 100644 hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/service/activity/reject/ApRejectProcessor.kt delete mode 100644 hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/service/activity/reject/ApSendRejectService.kt delete mode 100644 hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/service/activity/reject/ApSendRejectServiceImpl.kt delete mode 100644 hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/service/activity/undo/APSendUndoService.kt delete mode 100644 hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/service/activity/undo/APSendUndoServiceImpl.kt delete mode 100644 hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/service/activity/undo/APUndoProcessor.kt delete mode 100644 hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/service/common/APRequestService.kt delete mode 100644 hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/service/common/APRequestServiceImpl.kt delete mode 100644 hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/service/common/APResourceResolveService.kt delete mode 100644 hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/service/common/APResourceResolveServiceImpl.kt delete mode 100644 hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/service/common/APService.kt delete mode 100644 hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/service/common/AbstractActivityPubProcessor.kt delete mode 100644 hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/service/common/ActivityPubProcessContext.kt delete mode 100644 hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/service/common/ActivityPubProcessor.kt delete mode 100644 hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/service/common/ActivityType.kt delete mode 100644 hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/service/common/ActivityVocabulary.kt delete mode 100644 hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/service/common/ExtendedActivityVocabulary.kt delete mode 100644 hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/service/common/ExtendedVocabulary.kt delete mode 100644 hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/service/objects/emoji/EmojiService.kt delete mode 100644 hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/service/objects/emoji/EmojiServiceImpl.kt delete mode 100644 hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/service/objects/note/APNoteService.kt delete mode 100644 hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/service/objects/note/NoteApApiService.kt delete mode 100644 hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/service/objects/note/NoteApApiServiceImpl.kt delete mode 100644 hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/service/objects/user/APUserService.kt delete mode 100644 hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/service/webfinger/WebFingerApiService.kt create mode 100644 hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/event/relationship/RelationshipEvent.kt delete mode 100644 hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/actor/Acct.kt delete mode 100644 hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/actor/Actor.kt delete mode 100644 hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/actor/ActorRepository.kt delete mode 100644 hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/notification/ExposedNotificationRepository.kt delete mode 100644 hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/post/Post.kt delete mode 100644 hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/post/PostRepository.kt create mode 100644 hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/relationship/Relationship2.kt rename hideout-core/src/main/kotlin/dev/usbharu/hideout/{activitypub/domain/model/HasName.kt => core/domain/model/relationship/Relationship2Repository.kt} (73%) delete mode 100644 hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/relationship/RelationshipRepository.kt delete mode 100644 hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/shared/domainevent/DomainEventStorable.kt delete mode 100644 hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposed/PostQueryMapper.kt delete mode 100644 hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposed/PostResultRowMapper.kt delete mode 100644 hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposed/UserQueryMapper.kt delete mode 100644 hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposed/UserResultRowMapper.kt delete mode 100644 hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposedquery/FollowerQueryServiceImpl.kt delete mode 100644 hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposedrepository/ActorRepositoryImpl.kt create mode 100644 hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposedrepository/ExposedPost2Repository.kt delete mode 100644 hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposedrepository/PostRepositoryImpl.kt delete mode 100644 hideout-core/src/main/kotlin/dev/usbharu/hideout/core/query/FollowerQueryService.kt delete mode 100644 hideout-core/src/main/kotlin/dev/usbharu/hideout/core/service/auth/AuthApiService.kt delete mode 100644 hideout-core/src/main/kotlin/dev/usbharu/hideout/core/service/auth/AuthApiServiceImpl.kt delete mode 100644 hideout-core/src/main/kotlin/dev/usbharu/hideout/core/service/filter/MuteProcessService.kt delete mode 100644 hideout-core/src/main/kotlin/dev/usbharu/hideout/core/service/filter/MuteProcessServiceImpl.kt delete mode 100644 hideout-core/src/main/kotlin/dev/usbharu/hideout/core/service/follow/SendFollowDto.kt delete mode 100644 hideout-core/src/main/kotlin/dev/usbharu/hideout/core/service/notification/NotificationStore.kt delete mode 100644 hideout-core/src/main/kotlin/dev/usbharu/hideout/core/service/post/PostService.kt delete mode 100644 hideout-core/src/main/kotlin/dev/usbharu/hideout/core/service/post/PostServiceImpl.kt delete mode 100644 hideout-core/src/main/kotlin/dev/usbharu/hideout/core/service/relationship/RelationshipService.kt delete mode 100644 hideout-core/src/main/kotlin/dev/usbharu/hideout/core/service/relationship/RelationshipServiceImpl.kt delete mode 100644 hideout-core/src/main/kotlin/dev/usbharu/hideout/core/service/timeline/TimelineService.kt delete mode 100644 hideout-core/src/main/kotlin/dev/usbharu/hideout/core/service/user/UserService.kt delete mode 100644 hideout-core/src/main/kotlin/dev/usbharu/hideout/core/service/user/UserServiceImpl.kt delete mode 100644 hideout-core/src/main/kotlin/dev/usbharu/hideout/mastodon/config/MastodonApiSecurityConfig.kt delete mode 100644 hideout-core/src/main/kotlin/dev/usbharu/hideout/mastodon/domain/exception/AccountNotFoundException.kt delete mode 100644 hideout-core/src/main/kotlin/dev/usbharu/hideout/mastodon/domain/exception/ClientException.kt delete mode 100644 hideout-core/src/main/kotlin/dev/usbharu/hideout/mastodon/domain/exception/MastodonApiException.kt delete mode 100644 hideout-core/src/main/kotlin/dev/usbharu/hideout/mastodon/domain/exception/ServerException.kt delete mode 100644 hideout-core/src/main/kotlin/dev/usbharu/hideout/mastodon/domain/exception/StatusNotFoundException.kt delete mode 100644 hideout-core/src/main/kotlin/dev/usbharu/hideout/mastodon/domain/model/MastodonApiErrorResponse.kt delete mode 100644 hideout-core/src/main/kotlin/dev/usbharu/hideout/mastodon/domain/model/MastodonNotification.kt delete mode 100644 hideout-core/src/main/kotlin/dev/usbharu/hideout/mastodon/domain/model/MastodonNotificationRepository.kt delete mode 100644 hideout-core/src/main/kotlin/dev/usbharu/hideout/mastodon/domain/model/NotificationType.kt delete mode 100644 hideout-core/src/main/kotlin/dev/usbharu/hideout/mastodon/infrastructure/exposedquery/AccountQueryServiceImpl.kt delete mode 100644 hideout-core/src/main/kotlin/dev/usbharu/hideout/mastodon/infrastructure/exposedquery/StatusQueryServiceImpl.kt delete mode 100644 hideout-core/src/main/kotlin/dev/usbharu/hideout/mastodon/infrastructure/exposedrepository/ExposedMastodonNotificationRepository.kt delete mode 100644 hideout-core/src/main/kotlin/dev/usbharu/hideout/mastodon/infrastructure/mongorepository/MongoMastodonNotificationRepository.kt delete mode 100644 hideout-core/src/main/kotlin/dev/usbharu/hideout/mastodon/infrastructure/mongorepository/MongoMastodonNotificationRepositoryWrapper.kt delete mode 100644 hideout-core/src/main/kotlin/dev/usbharu/hideout/mastodon/infrastructure/springweb/MastodonApiControllerAdvice.kt delete mode 100644 hideout-core/src/main/kotlin/dev/usbharu/hideout/mastodon/interfaces/api/account/MastodonAccountApiController.kt delete mode 100644 hideout-core/src/main/kotlin/dev/usbharu/hideout/mastodon/interfaces/api/apps/MastodonAppsApiController.kt delete mode 100644 hideout-core/src/main/kotlin/dev/usbharu/hideout/mastodon/interfaces/api/filter/MastodonFilterApiController.kt delete mode 100644 hideout-core/src/main/kotlin/dev/usbharu/hideout/mastodon/interfaces/api/instance/MastodonInstanceApiController.kt delete mode 100644 hideout-core/src/main/kotlin/dev/usbharu/hideout/mastodon/interfaces/api/media/MastodonMediaApiController.kt delete mode 100644 hideout-core/src/main/kotlin/dev/usbharu/hideout/mastodon/interfaces/api/media/MediaRequest.kt delete mode 100644 hideout-core/src/main/kotlin/dev/usbharu/hideout/mastodon/interfaces/api/notification/MastodonNotificationApiController.kt delete mode 100644 hideout-core/src/main/kotlin/dev/usbharu/hideout/mastodon/interfaces/api/status/MastodonStatusesApiContoller.kt delete mode 100644 hideout-core/src/main/kotlin/dev/usbharu/hideout/mastodon/interfaces/api/status/StatusQuery.kt delete mode 100644 hideout-core/src/main/kotlin/dev/usbharu/hideout/mastodon/interfaces/api/status/StatusesRequest.kt delete mode 100644 hideout-core/src/main/kotlin/dev/usbharu/hideout/mastodon/interfaces/api/timeline/MastodonTimelineApiController.kt delete mode 100644 hideout-core/src/main/kotlin/dev/usbharu/hideout/mastodon/query/AccountQueryService.kt delete mode 100644 hideout-core/src/main/kotlin/dev/usbharu/hideout/mastodon/query/StatusQueryService.kt delete mode 100644 hideout-core/src/main/kotlin/dev/usbharu/hideout/mastodon/service/account/AccountApiService.kt delete mode 100644 hideout-core/src/main/kotlin/dev/usbharu/hideout/mastodon/service/account/AccountService.kt delete mode 100644 hideout-core/src/main/kotlin/dev/usbharu/hideout/mastodon/service/app/AppApiService.kt delete mode 100644 hideout-core/src/main/kotlin/dev/usbharu/hideout/mastodon/service/filter/MastodonFilterApiService.kt delete mode 100644 hideout-core/src/main/kotlin/dev/usbharu/hideout/mastodon/service/instance/InstanceApiService.kt delete mode 100644 hideout-core/src/main/kotlin/dev/usbharu/hideout/mastodon/service/media/MediaApiService.kt delete mode 100644 hideout-core/src/main/kotlin/dev/usbharu/hideout/mastodon/service/media/MediaApiServiceImpl.kt delete mode 100644 hideout-core/src/main/kotlin/dev/usbharu/hideout/mastodon/service/notification/MastodonNotificationStore.kt delete mode 100644 hideout-core/src/main/kotlin/dev/usbharu/hideout/mastodon/service/notification/NotificationApiService.kt delete mode 100644 hideout-core/src/main/kotlin/dev/usbharu/hideout/mastodon/service/notification/NotificationApiServiceImpl.kt delete mode 100644 hideout-core/src/main/kotlin/dev/usbharu/hideout/mastodon/service/status/StatusesApiService.kt delete mode 100644 hideout-core/src/main/kotlin/dev/usbharu/hideout/mastodon/service/timeline/TimelineApiService.kt delete mode 100644 hideout-core/src/main/kotlin/dev/usbharu/hideout/util/AcctUtil.kt create mode 100644 hideout-mastodon/build.gradle.kts create mode 100644 hideout-mastodon/gradle/wrapper/gradle-wrapper.jar create mode 100644 hideout-mastodon/gradle/wrapper/gradle-wrapper.properties create mode 100644 hideout-mastodon/gradlew create mode 100644 hideout-mastodon/gradlew.bat create mode 100644 hideout-mastodon/settings.gradle.kts create mode 100644 hideout-mastodon/src/main/kotlin/Main.kt diff --git a/hideout-activitypub/build.gradle.kts b/hideout-activitypub/build.gradle.kts new file mode 100644 index 00000000..45be2756 --- /dev/null +++ b/hideout-activitypub/build.gradle.kts @@ -0,0 +1,21 @@ +plugins { + kotlin("jvm") version "1.9.23" +} + +group = "dev.usbharu" +version = "1.0-SNAPSHOT" + +repositories { + mavenCentral() +} + +dependencies { + testImplementation(kotlin("test")) +} + +tasks.test { + useJUnitPlatform() +} +kotlin { + jvmToolchain(21) +} \ No newline at end of file diff --git a/hideout-activitypub/gradle/wrapper/gradle-wrapper.jar b/hideout-activitypub/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000000000000000000000000000000000000..249e5832f090a2944b7473328c07c9755baa3196 GIT binary patch literal 60756 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 '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit + +APP_NAME="Gradle" +APP_BASE_NAME=${0##*/} + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD=$JAVA_HOME/jre/sh/java + else + JAVACMD=$JAVA_HOME/bin/java + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD=java + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + +# Collect all arguments for the java command; +# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of +# shell script including quotes and variable substitutions, so put them in +# double quotes to make sure that they get re-expanded; and +# * put everything else in single quotes, so that it's not re-expanded. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/hideout-activitypub/gradlew.bat b/hideout-activitypub/gradlew.bat new file mode 100644 index 00000000..107acd32 --- /dev/null +++ b/hideout-activitypub/gradlew.bat @@ -0,0 +1,89 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto execute + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/hideout-activitypub/settings.gradle.kts b/hideout-activitypub/settings.gradle.kts new file mode 100644 index 00000000..1fd1691d --- /dev/null +++ b/hideout-activitypub/settings.gradle.kts @@ -0,0 +1,5 @@ +plugins { + id("org.gradle.toolchains.foojay-resolver-convention") version "0.5.0" +} +rootProject.name = "hideout-activitypub" + diff --git a/hideout-activitypub/src/main/kotlin/Main.kt b/hideout-activitypub/src/main/kotlin/Main.kt new file mode 100644 index 00000000..27f6ee1a --- /dev/null +++ b/hideout-activitypub/src/main/kotlin/Main.kt @@ -0,0 +1,5 @@ +package dev.usbharu + +fun main() { + println("Hello World!") +} \ No newline at end of file diff --git a/hideout-core/src/e2eTest/kotlin/AssertionUtil.kt b/hideout-core/src/e2eTest/kotlin/AssertionUtil.kt index 8d73d7c6..a93ba706 100644 --- a/hideout-core/src/e2eTest/kotlin/AssertionUtil.kt +++ b/hideout-core/src/e2eTest/kotlin/AssertionUtil.kt @@ -14,7 +14,6 @@ * limitations under the License. */ -import dev.usbharu.hideout.core.infrastructure.exposedrepository.Actors import org.jetbrains.exposed.sql.and import org.jetbrains.exposed.sql.selectAll import java.net.MalformedURLException diff --git a/hideout-core/src/intTest/kotlin/mastodon/account/AccountApiTest.kt b/hideout-core/src/intTest/kotlin/mastodon/account/AccountApiTest.kt index 6b482f85..d48c72d8 100644 --- a/hideout-core/src/intTest/kotlin/mastodon/account/AccountApiTest.kt +++ b/hideout-core/src/intTest/kotlin/mastodon/account/AccountApiTest.kt @@ -17,8 +17,6 @@ package mastodon.account import dev.usbharu.hideout.SpringApplication -import dev.usbharu.hideout.core.domain.model.actor.ActorRepository -import dev.usbharu.hideout.core.infrastructure.exposedquery.FollowerQueryServiceImpl import dev.usbharu.owl.producer.api.OwlProducer import kotlinx.coroutines.runBlocking import kotlinx.coroutines.test.runTest diff --git a/hideout-core/src/intTest/kotlin/util/WithHttpSignatureSecurityContextFactory.kt b/hideout-core/src/intTest/kotlin/util/WithHttpSignatureSecurityContextFactory.kt index 6d809267..f20da72f 100644 --- a/hideout-core/src/intTest/kotlin/util/WithHttpSignatureSecurityContextFactory.kt +++ b/hideout-core/src/intTest/kotlin/util/WithHttpSignatureSecurityContextFactory.kt @@ -18,7 +18,6 @@ package util import dev.usbharu.hideout.application.external.Transaction import dev.usbharu.hideout.core.domain.exception.resource.UserNotFoundException -import dev.usbharu.hideout.core.domain.model.actor.ActorRepository import dev.usbharu.hideout.core.infrastructure.springframework.httpsignature.HttpSignatureUser import dev.usbharu.httpsignature.common.HttpHeaders import dev.usbharu.httpsignature.common.HttpMethod diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/domain/Constant.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/domain/Constant.kt deleted file mode 100644 index 6c19c683..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/domain/Constant.kt +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright (C) 2024 usbharu - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package dev.usbharu.hideout.activitypub.domain - -import dev.usbharu.hideout.activitypub.domain.model.StringOrObject - -object Constant { - val context = listOf( - StringOrObject("https://www.w3.org/ns/activitystreams"), - StringOrObject("https://w3id.org/security/v1"), - StringOrObject( - mapOf( - "manuallyApprovesFollowers" to "as:manuallyApprovesFollowers", - "sensitive" to "as:sensitive", - "Hashtag" to "as:Hashtag", - "quoteUrl" to "as:quoteUrl", - "toot" to "http://joinmastodon.org/ns#", - "Emoji" to "toot:Emoji", - "featured" to "toot:featured", - "discoverable" to "toot:discoverable", - "schema" to "http://schema.org#", - "PropertyValue" to "schema:PropertyValue", - "value" to "schema:value", - ) - ) - ) -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/domain/exception/ActivityPubProcessException.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/domain/exception/ActivityPubProcessException.kt deleted file mode 100644 index 8ce12a19..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/domain/exception/ActivityPubProcessException.kt +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright (C) 2024 usbharu - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package dev.usbharu.hideout.activitypub.domain.exception - -import java.io.Serial - -class ActivityPubProcessException : RuntimeException { - constructor() : super() - constructor(message: String?) : super(message) - constructor(message: String?, cause: Throwable?) : super(message, cause) - constructor(cause: Throwable?) : super(cause) - constructor(message: String?, cause: Throwable?, enableSuppression: Boolean, writableStackTrace: Boolean) : super( - message, - cause, - enableSuppression, - writableStackTrace - ) - - companion object { - @Serial - private const val serialVersionUID: Long = 5370068873167636639L - } -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/domain/exception/FailedProcessException.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/domain/exception/FailedProcessException.kt deleted file mode 100644 index d3125395..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/domain/exception/FailedProcessException.kt +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright (C) 2024 usbharu - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package dev.usbharu.hideout.activitypub.domain.exception - -import java.io.Serial - -class FailedProcessException : RuntimeException { - constructor() : super() - constructor(message: String?) : super(message) - constructor(message: String?, cause: Throwable?) : super(message, cause) - constructor(cause: Throwable?) : super(cause) - constructor(message: String?, cause: Throwable?, enableSuppression: Boolean, writableStackTrace: Boolean) : super( - message, - cause, - enableSuppression, - writableStackTrace - ) - - companion object { - @Serial - private const val serialVersionUID: Long = -1305337651143409144L - } -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/domain/exception/FailedToGetActivityPubResourceException.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/domain/exception/FailedToGetActivityPubResourceException.kt deleted file mode 100644 index 528277a8..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/domain/exception/FailedToGetActivityPubResourceException.kt +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright (C) 2024 usbharu - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package dev.usbharu.hideout.activitypub.domain.exception - -import dev.usbharu.hideout.core.domain.exception.FailedToGetResourcesException -import java.io.Serial - -class FailedToGetActivityPubResourceException : FailedToGetResourcesException { - constructor() : super() - constructor(s: String?) : super(s) - constructor(message: String?, cause: Throwable?) : super(message, cause) - constructor(cause: Throwable?) : super(cause) - - companion object { - @Serial - private const val serialVersionUID: Long = 6420233106776818052L - } -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/domain/exception/HttpSignatureUnauthorizedException.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/domain/exception/HttpSignatureUnauthorizedException.kt deleted file mode 100644 index aa50d3db..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/domain/exception/HttpSignatureUnauthorizedException.kt +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright (C) 2024 usbharu - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package dev.usbharu.hideout.activitypub.domain.exception - -import java.io.Serial - -class HttpSignatureUnauthorizedException : RuntimeException { - constructor() : super() - constructor(message: String?) : super(message) - constructor(message: String?, cause: Throwable?) : super(message, cause) - constructor(cause: Throwable?) : super(cause) - constructor(message: String?, cause: Throwable?, enableSuppression: Boolean, writableStackTrace: Boolean) : super( - message, - cause, - enableSuppression, - writableStackTrace - ) - - companion object { - @Serial - private const val serialVersionUID: Long = -6449793151674654501L - } -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/domain/exception/IllegalActivityPubObjectException.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/domain/exception/IllegalActivityPubObjectException.kt deleted file mode 100644 index ae84181d..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/domain/exception/IllegalActivityPubObjectException.kt +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright (C) 2024 usbharu - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package dev.usbharu.hideout.activitypub.domain.exception - -import java.io.Serial - -class IllegalActivityPubObjectException : IllegalArgumentException { - constructor() : super() - constructor(s: String?) : super(s) - constructor(message: String?, cause: Throwable?) : super(message, cause) - constructor(cause: Throwable?) : super(cause) - - companion object { - @Serial - private const val serialVersionUID: Long = 7216998115771415263L - } -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/domain/exception/JsonParseException.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/domain/exception/JsonParseException.kt deleted file mode 100644 index 841641cc..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/domain/exception/JsonParseException.kt +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright (C) 2024 usbharu - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package dev.usbharu.hideout.activitypub.domain.exception - -import java.io.Serial - -class JsonParseException : IllegalArgumentException { - constructor() : super() - constructor(s: String?) : super(s) - constructor(message: String?, cause: Throwable?) : super(message, cause) - constructor(cause: Throwable?) : super(cause) - - companion object { - @Serial - private const val serialVersionUID: Long = 7975567796830950692L - } -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/domain/model/Accept.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/domain/model/Accept.kt deleted file mode 100644 index 84c090cc..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/domain/model/Accept.kt +++ /dev/null @@ -1,62 +0,0 @@ -/* - * Copyright (C) 2024 usbharu - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package dev.usbharu.hideout.activitypub.domain.model - -import com.fasterxml.jackson.annotation.JsonCreator -import com.fasterxml.jackson.annotation.JsonProperty -import com.fasterxml.jackson.databind.annotation.JsonDeserialize -import dev.usbharu.hideout.activitypub.domain.model.objects.Object -import dev.usbharu.hideout.activitypub.domain.model.objects.ObjectDeserializer - -open class Accept @JsonCreator constructor( - type: List = emptyList(), - @JsonDeserialize(using = ObjectDeserializer::class) - @JsonProperty("object") - val apObject: Object, - override val actor: String -) : Object( - type = add(type, "Accept") -), - HasActor { - override fun equals(other: Any?): Boolean { - if (this === other) return true - if (javaClass != other?.javaClass) return false - if (!super.equals(other)) return false - - other as Accept - - if (apObject != other.apObject) return false - if (actor != other.actor) return false - - return true - } - - override fun hashCode(): Int { - var result = super.hashCode() - result = 31 * result + apObject.hashCode() - result = 31 * result + actor.hashCode() - return result - } - - override fun toString(): String { - return "Accept(" + - "apObject=$apObject, " + - "actor='$actor'" + - ")" + - " ${super.toString()}" - } -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/domain/model/Announce.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/domain/model/Announce.kt deleted file mode 100644 index c07eab7c..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/domain/model/Announce.kt +++ /dev/null @@ -1,76 +0,0 @@ -/* - * Copyright (C) 2024 usbharu - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package dev.usbharu.hideout.activitypub.domain.model - -import com.fasterxml.jackson.annotation.JsonCreator -import com.fasterxml.jackson.annotation.JsonProperty -import dev.usbharu.hideout.activitypub.domain.model.objects.Object - -open class Announce @JsonCreator constructor( - type: List = emptyList(), - @JsonProperty("object") - val apObject: String, - override val actor: String, - override val id: String, - val published: String, - val to: List = emptyList(), - val cc: List = emptyList() -) : Object( - type = add(type, "Announce") -), - HasActor, - HasId { - override fun equals(other: Any?): Boolean { - if (this === other) return true - if (javaClass != other?.javaClass) return false - if (!super.equals(other)) return false - - other as Announce - - if (apObject != other.apObject) return false - if (actor != other.actor) return false - if (id != other.id) return false - if (published != other.published) return false - if (to != other.to) return false - if (cc != other.cc) return false - - return true - } - - override fun hashCode(): Int { - var result = super.hashCode() - result = 31 * result + apObject.hashCode() - result = 31 * result + actor.hashCode() - result = 31 * result + id.hashCode() - result = 31 * result + published.hashCode() - result = 31 * result + to.hashCode() - result = 31 * result + cc.hashCode() - return result - } - - override fun toString(): String { - return "Announce(" + - "apObject='$apObject', " + - "actor='$actor', " + - "id='$id', " + - "published='$published', " + - "to=$to, " + - "cc=$cc" + - ")" + - " ${super.toString()}" - } -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/domain/model/Create.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/domain/model/Create.kt deleted file mode 100644 index 53e12cd5..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/domain/model/Create.kt +++ /dev/null @@ -1,79 +0,0 @@ -/* - * Copyright (C) 2024 usbharu - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package dev.usbharu.hideout.activitypub.domain.model - -import com.fasterxml.jackson.annotation.JsonProperty -import com.fasterxml.jackson.databind.annotation.JsonDeserialize -import dev.usbharu.hideout.activitypub.domain.model.objects.Object -import dev.usbharu.hideout.activitypub.domain.model.objects.ObjectDeserializer - -open class Create( - type: List = emptyList(), - val name: String? = null, - @JsonDeserialize(using = ObjectDeserializer::class) - @JsonProperty("object") - val apObject: Object, - override val actor: String, - override val id: String, - val to: List = emptyList(), - val cc: List = emptyList() -) : Object( - type = add(type, "Create") -), - HasId, - HasActor { - - override fun equals(other: Any?): Boolean { - if (this === other) return true - if (javaClass != other?.javaClass) return false - if (!super.equals(other)) return false - - other as Create - - if (name != other.name) return false - if (apObject != other.apObject) return false - if (actor != other.actor) return false - if (id != other.id) return false - if (to != other.to) return false - if (cc != other.cc) return false - - return true - } - - override fun hashCode(): Int { - var result = super.hashCode() - result = 31 * result + (name?.hashCode() ?: 0) - result = 31 * result + apObject.hashCode() - result = 31 * result + actor.hashCode() - result = 31 * result + id.hashCode() - result = 31 * result + to.hashCode() - result = 31 * result + cc.hashCode() - return result - } - - override fun toString(): String { - return "Create(" + - "name=$name, " + - "apObject=$apObject, " + - "actor='$actor', " + - "id='$id', " + - "to=$to, " + - "cc=$cc" + - ")" + - " ${super.toString()}" - } -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/domain/model/Delete.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/domain/model/Delete.kt deleted file mode 100644 index 43ec1a51..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/domain/model/Delete.kt +++ /dev/null @@ -1,78 +0,0 @@ -/* - * Copyright (C) 2024 usbharu - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package dev.usbharu.hideout.activitypub.domain.model - -import com.fasterxml.jackson.annotation.JsonProperty -import com.fasterxml.jackson.databind.annotation.JsonDeserialize -import dev.usbharu.hideout.activitypub.domain.model.objects.Object -import dev.usbharu.hideout.activitypub.domain.model.objects.ObjectDeserializer - -open class Delete : Object, HasId, HasActor { - @JsonDeserialize(using = ObjectDeserializer::class) - @JsonProperty("object") - val apObject: Object - val published: String - override var actor: String = "" - override var id: String = "" - - constructor( - type: List = emptyList(), - actor: String, - id: String, - `object`: Object, - published: String - ) : super(add(type, "Delete")) { - this.apObject = `object` - this.published = published - this.actor = actor - this.id = id - } - - override fun equals(other: Any?): Boolean { - if (this === other) return true - if (javaClass != other?.javaClass) return false - if (!super.equals(other)) return false - - other as Delete - - if (apObject != other.apObject) return false - if (published != other.published) return false - if (actor != other.actor) return false - if (id != other.id) return false - - return true - } - - override fun hashCode(): Int { - var result = super.hashCode() - result = 31 * result + apObject.hashCode() - result = 31 * result + published.hashCode() - result = 31 * result + actor.hashCode() - result = 31 * result + id.hashCode() - return result - } - - override fun toString(): String { - return "Delete(" + - "apObject=$apObject, " + - "published='$published', " + - "actor='$actor', " + - "id='$id'" + - ")" + - " ${super.toString()}" - } -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/domain/model/Document.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/domain/model/Document.kt deleted file mode 100644 index 632fb7cb..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/domain/model/Document.kt +++ /dev/null @@ -1,57 +0,0 @@ -/* - * Copyright (C) 2024 usbharu - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package dev.usbharu.hideout.activitypub.domain.model - -import com.fasterxml.jackson.annotation.JsonSetter -import com.fasterxml.jackson.annotation.Nulls -import dev.usbharu.hideout.activitypub.domain.model.objects.Object - -open class Document( - type: List = emptyList(), - @JsonSetter(nulls = Nulls.AS_EMPTY) - override val name: String = "", - val mediaType: String, - val url: String -) : Object( - type = add(type, "Document") -), - HasName { - - override fun equals(other: Any?): Boolean { - if (this === other) return true - if (javaClass != other?.javaClass) return false - if (!super.equals(other)) return false - - other as Document - - if (mediaType != other.mediaType) return false - if (url != other.url) return false - if (name != other.name) return false - - return true - } - - override fun hashCode(): Int { - var result = super.hashCode() - result = 31 * result + mediaType.hashCode() - result = 31 * result + url.hashCode() - result = 31 * result + name.hashCode() - return result - } - - override fun toString(): String = "Document(mediaType=$mediaType, url=$url, name='$name') ${super.toString()}" -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/domain/model/Emoji.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/domain/model/Emoji.kt deleted file mode 100644 index f1f7ae7b..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/domain/model/Emoji.kt +++ /dev/null @@ -1,66 +0,0 @@ -/* - * Copyright (C) 2024 usbharu - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package dev.usbharu.hideout.activitypub.domain.model - -import dev.usbharu.hideout.activitypub.domain.model.objects.Object - -open class Emoji( - type: List, - override val name: String, - override val id: String, - val updated: String, - val icon: Image -) : Object( - type = add(type, "Emoji") -), - HasName, - HasId { - - override fun toString(): String { - return "Emoji(" + - "name='$name', " + - "id='$id', " + - "updated='$updated', " + - "icon=$icon" + - ")" + - " ${super.toString()}" - } - - override fun equals(other: Any?): Boolean { - if (this === other) return true - if (javaClass != other?.javaClass) return false - if (!super.equals(other)) return false - - other as Emoji - - if (name != other.name) return false - if (id != other.id) return false - if (updated != other.updated) return false - if (icon != other.icon) return false - - return true - } - - override fun hashCode(): Int { - var result = super.hashCode() - result = 31 * result + name.hashCode() - result = 31 * result + id.hashCode() - result = 31 * result + updated.hashCode() - result = 31 * result + icon.hashCode() - return result - } -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/domain/model/Follow.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/domain/model/Follow.kt deleted file mode 100644 index f404946a..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/domain/model/Follow.kt +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Copyright (C) 2024 usbharu - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package dev.usbharu.hideout.activitypub.domain.model - -import com.fasterxml.jackson.annotation.JsonProperty -import dev.usbharu.hideout.activitypub.domain.model.objects.Object - -open class Follow( - type: List = emptyList(), - @JsonProperty("object") val apObject: String, - override val actor: String, - val id: String? = null -) : Object( - type = add(type, "Follow") -), - HasActor { - override fun equals(other: Any?): Boolean { - if (this === other) return true - if (javaClass != other?.javaClass) return false - if (!super.equals(other)) return false - - other as Follow - - if (apObject != other.apObject) return false - if (actor != other.actor) return false - if (id != other.id) return false - - return true - } - - override fun hashCode(): Int { - var result = super.hashCode() - result = 31 * result + apObject.hashCode() - result = 31 * result + actor.hashCode() - result = 31 * result + (id?.hashCode() ?: 0) - return result - } - - override fun toString(): String { - return "Follow(" + - "apObject='$apObject', " + - "actor='$actor', " + - "id=$id" + - ")" + - " ${super.toString()}" - } -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/domain/model/HasActor.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/domain/model/HasActor.kt deleted file mode 100644 index d04a3a7c..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/domain/model/HasActor.kt +++ /dev/null @@ -1,21 +0,0 @@ -/* - * Copyright (C) 2024 usbharu - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package dev.usbharu.hideout.activitypub.domain.model - -interface HasActor { - val actor: String -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/domain/model/HasId.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/domain/model/HasId.kt deleted file mode 100644 index b4043b5c..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/domain/model/HasId.kt +++ /dev/null @@ -1,21 +0,0 @@ -/* - * Copyright (C) 2024 usbharu - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package dev.usbharu.hideout.activitypub.domain.model - -interface HasId { - val id: String -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/domain/model/Image.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/domain/model/Image.kt deleted file mode 100644 index a522574c..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/domain/model/Image.kt +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Copyright (C) 2024 usbharu - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package dev.usbharu.hideout.activitypub.domain.model - -import dev.usbharu.hideout.activitypub.domain.model.objects.Object - -open class Image( - type: List = emptyList(), - val mediaType: String? = null, - val url: String -) : Object( - add(type, "Image") -) { - override fun equals(other: Any?): Boolean { - if (this === other) return true - if (javaClass != other?.javaClass) return false - if (!super.equals(other)) return false - - other as Image - - if (mediaType != other.mediaType) return false - if (url != other.url) return false - - return true - } - - override fun hashCode(): Int { - var result = super.hashCode() - result = 31 * result + (mediaType?.hashCode() ?: 0) - result = 31 * result + url.hashCode() - return result - } - - override fun toString(): String { - return "Image(" + - "mediaType=$mediaType, " + - "url='$url'" + - ")" + - " ${super.toString()}" - } -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/domain/model/JsonLd.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/domain/model/JsonLd.kt deleted file mode 100644 index 88a8e48d..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/domain/model/JsonLd.kt +++ /dev/null @@ -1,101 +0,0 @@ -/* - * Copyright (C) 2024 usbharu - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package dev.usbharu.hideout.activitypub.domain.model - -import com.fasterxml.jackson.annotation.JsonAutoDetect -import com.fasterxml.jackson.annotation.JsonCreator -import com.fasterxml.jackson.annotation.JsonInclude -import com.fasterxml.jackson.annotation.JsonProperty -import com.fasterxml.jackson.core.JsonGenerator -import com.fasterxml.jackson.databind.JsonDeserializer -import com.fasterxml.jackson.databind.JsonNode -import com.fasterxml.jackson.databind.JsonSerializer -import com.fasterxml.jackson.databind.SerializerProvider -import com.fasterxml.jackson.databind.annotation.JsonDeserialize -import com.fasterxml.jackson.databind.annotation.JsonSerialize - -@JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY) -open class JsonLd { - @JsonProperty("@context") - @JsonDeserialize(contentUsing = StringOrObjectDeserializer::class) - @JsonSerialize(include = JsonSerialize.Inclusion.NON_EMPTY, using = ContextSerializer::class) - @JsonInclude(JsonInclude.Include.NON_EMPTY) - var context: List = emptyList() - set(value) { - field = value.filterNot { it.isEmpty() } - } - - @JsonCreator - constructor(context: List?) { - if (context != null) { - this.context = context.filterNotNull().filterNot { it.isEmpty() } - } else { - this.context = emptyList() - } - } - - protected constructor() - - override fun equals(other: Any?): Boolean { - if (this === other) return true - if (other !is JsonLd) return false - - return context == other.context - } - - override fun hashCode(): Int = context.hashCode() - - override fun toString(): String = "JsonLd(context=$context)" -} - -class ContextDeserializer : JsonDeserializer() { - - override fun deserialize( - p0: com.fasterxml.jackson.core.JsonParser?, - p1: com.fasterxml.jackson.databind.DeserializationContext? - ): String { - val readTree: JsonNode = p0?.codec?.readTree(p0) ?: return "" - if (readTree.isValueNode) { - return readTree.textValue() - } - return "" - } -} - -class ContextSerializer : JsonSerializer>() { - - @Deprecated("Deprecated in Java") - override fun isEmpty(value: List?): Boolean = value.isNullOrEmpty() - - override fun isEmpty(provider: SerializerProvider?, value: List?): Boolean = value.isNullOrEmpty() - - override fun serialize(value: List?, gen: JsonGenerator?, serializers: SerializerProvider) { - if (value.isNullOrEmpty()) { - serializers.defaultSerializeNull(gen) - return - } - if (value.size == 1) { - serializers.findValueSerializer(StringOrObject::class.java).serialize(value[0], gen, serializers) - } else { - gen?.writeStartArray() - value.forEach { - serializers.findValueSerializer(StringOrObject::class.java).serialize(it, gen, serializers) - } - gen?.writeEndArray() - } - } -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/domain/model/Key.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/domain/model/Key.kt deleted file mode 100644 index bb046d56..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/domain/model/Key.kt +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright (C) 2024 usbharu - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package dev.usbharu.hideout.activitypub.domain.model - -import dev.usbharu.hideout.activitypub.domain.model.objects.Object - -open class Key( - override val id: String, - val owner: String, - val publicKeyPem: String -) : Object( - type = add(list = emptyList(), type = "Key") -), - HasId { - - override fun equals(other: Any?): Boolean { - if (this === other) return true - if (javaClass != other?.javaClass) return false - if (!super.equals(other)) return false - - other as Key - - if (owner != other.owner) return false - if (publicKeyPem != other.publicKeyPem) return false - if (id != other.id) return false - - return true - } - - override fun hashCode(): Int { - var result = super.hashCode() - result = 31 * result + owner.hashCode() - result = 31 * result + publicKeyPem.hashCode() - result = 31 * result + id.hashCode() - return result - } - - override fun toString(): String = "Key(owner=$owner, publicKeyPem=$publicKeyPem, id='$id') ${super.toString()}" -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/domain/model/Like.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/domain/model/Like.kt deleted file mode 100644 index 6b46cb54..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/domain/model/Like.kt +++ /dev/null @@ -1,73 +0,0 @@ -/* - * Copyright (C) 2024 usbharu - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package dev.usbharu.hideout.activitypub.domain.model - -import com.fasterxml.jackson.annotation.JsonProperty -import com.fasterxml.jackson.databind.annotation.JsonDeserialize -import dev.usbharu.hideout.activitypub.domain.model.objects.Object -import dev.usbharu.hideout.activitypub.domain.model.objects.ObjectDeserializer - -open class Like( - type: List = emptyList(), - override val actor: String, - override val id: String, - @JsonProperty("object") val apObject: String, - val content: String, - @JsonDeserialize(contentUsing = ObjectDeserializer::class) val tag: List = emptyList() -) : Object( - type = add(type, "Like") -), - HasId, - HasActor { - - override fun equals(other: Any?): Boolean { - if (this === other) return true - if (javaClass != other?.javaClass) return false - if (!super.equals(other)) return false - - other as Like - - if (actor != other.actor) return false - if (id != other.id) return false - if (apObject != other.apObject) return false - if (content != other.content) return false - if (tag != other.tag) return false - - return true - } - - override fun hashCode(): Int { - var result = super.hashCode() - result = 31 * result + actor.hashCode() - result = 31 * result + id.hashCode() - result = 31 * result + apObject.hashCode() - result = 31 * result + content.hashCode() - result = 31 * result + tag.hashCode() - return result - } - - override fun toString(): String { - return "Like(" + - "actor='$actor', " + - "id='$id', " + - "apObject='$apObject', " + - "content='$content', " + - "tag=$tag" + - ")" + - " ${super.toString()}" - } -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/domain/model/Note.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/domain/model/Note.kt deleted file mode 100644 index b925c861..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/domain/model/Note.kt +++ /dev/null @@ -1,110 +0,0 @@ -/* - * Copyright (C) 2024 usbharu - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package dev.usbharu.hideout.activitypub.domain.model - -import com.fasterxml.jackson.annotation.JsonProperty -import com.fasterxml.jackson.databind.annotation.JsonDeserialize -import dev.usbharu.hideout.activitypub.domain.model.objects.Object -import dev.usbharu.hideout.activitypub.domain.model.objects.ObjectDeserializer - -open class Note -@Suppress("LongParameterList", "CyclomaticComplexMethod") -constructor( - type: List = emptyList(), - override val id: String, - val attributedTo: String, - val content: String, - val published: String, - val to: List = emptyList(), - val cc: List = emptyList(), - val sensitive: Boolean = false, - val inReplyTo: String? = null, - val attachment: List = emptyList(), - @JsonDeserialize(contentUsing = ObjectDeserializer::class) - val tag: List = emptyList(), - val quoteUri: String? = null, - val quoteUrl: String? = null, - @JsonProperty("_misskey_quote") - val misskeyQuote: String? = null -) : Object( - type = add(type, "Note") -), - HasId { - - @Suppress("CyclomaticComplexMethod", "CognitiveComplexMethod") - override fun equals(other: Any?): Boolean { - if (this === other) return true - if (javaClass != other?.javaClass) return false - if (!super.equals(other)) return false - - other as Note - - if (id != other.id) return false - if (attributedTo != other.attributedTo) return false - if (content != other.content) return false - if (published != other.published) return false - if (to != other.to) return false - if (cc != other.cc) return false - if (sensitive != other.sensitive) return false - if (inReplyTo != other.inReplyTo) return false - if (attachment != other.attachment) return false - if (tag != other.tag) return false - if (quoteUri != other.quoteUri) return false - if (quoteUrl != other.quoteUrl) return false - if (misskeyQuote != other.misskeyQuote) return false - - return true - } - - @Suppress("CyclomaticComplexMethod") - override fun hashCode(): Int { - var result = super.hashCode() - result = 31 * result + id.hashCode() - result = 31 * result + attributedTo.hashCode() - result = 31 * result + content.hashCode() - result = 31 * result + published.hashCode() - result = 31 * result + to.hashCode() - result = 31 * result + cc.hashCode() - result = 31 * result + sensitive.hashCode() - result = 31 * result + (inReplyTo?.hashCode() ?: 0) - result = 31 * result + attachment.hashCode() - result = 31 * result + tag.hashCode() - result = 31 * result + (quoteUri?.hashCode() ?: 0) - result = 31 * result + (quoteUrl?.hashCode() ?: 0) - result = 31 * result + (misskeyQuote?.hashCode() ?: 0) - return result - } - - override fun toString(): String { - return "Note(" + - "id='$id', " + - "attributedTo='$attributedTo', " + - "content='$content', " + - "published='$published', " + - "to=$to, " + - "cc=$cc, " + - "sensitive=$sensitive, " + - "inReplyTo=$inReplyTo, " + - "attachment=$attachment, " + - "tag=$tag, " + - "quoteUri=$quoteUri, " + - "quoteUrl=$quoteUrl, " + - "misskeyQuote=$misskeyQuote" + - ")" + - " ${super.toString()}" - } -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/domain/model/Person.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/domain/model/Person.kt deleted file mode 100644 index 3f4a7dbe..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/domain/model/Person.kt +++ /dev/null @@ -1,102 +0,0 @@ -/* - * Copyright (C) 2024 usbharu - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package dev.usbharu.hideout.activitypub.domain.model - -import dev.usbharu.hideout.activitypub.domain.model.objects.Object - -open class Person -@Suppress("LongParameterList") -constructor( - type: List = emptyList(), - val name: String?, - override val id: String, - var preferredUsername: String, - var summary: String?, - var inbox: String, - var outbox: String, - var url: String, - private var icon: Image?, - var publicKey: Key, - var endpoints: Map = emptyMap(), - var followers: String?, - var following: String?, - val manuallyApprovesFollowers: Boolean? = false -) : Object(add(type, "Person")), HasId { - - @Suppress("CyclomaticComplexMethod", "CognitiveComplexMethod") - override fun equals(other: Any?): Boolean { - if (this === other) return true - if (javaClass != other?.javaClass) return false - if (!super.equals(other)) return false - - other as Person - - if (name != other.name) return false - if (id != other.id) return false - if (preferredUsername != other.preferredUsername) return false - if (summary != other.summary) return false - if (inbox != other.inbox) return false - if (outbox != other.outbox) return false - if (url != other.url) return false - if (icon != other.icon) return false - if (publicKey != other.publicKey) return false - 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() ?: 0) - result = 31 * result + id.hashCode() - result = 31 * result + preferredUsername.hashCode() - result = 31 * result + (summary?.hashCode() ?: 0) - result = 31 * result + inbox.hashCode() - result = 31 * result + outbox.hashCode() - result = 31 * result + url.hashCode() - result = 31 * result + (icon?.hashCode() ?: 0) - result = 31 * result + publicKey.hashCode() - result = 31 * result + endpoints.hashCode() - result = 31 * result + (followers?.hashCode() ?: 0) - result = 31 * result + (following?.hashCode() ?: 0) - result = 31 * result + (manuallyApprovesFollowers?.hashCode() ?: 0) - 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/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/domain/model/Reject.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/domain/model/Reject.kt deleted file mode 100644 index 5216c2a1..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/domain/model/Reject.kt +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Copyright (C) 2024 usbharu - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package dev.usbharu.hideout.activitypub.domain.model - -import com.fasterxml.jackson.annotation.JsonProperty -import com.fasterxml.jackson.databind.annotation.JsonDeserialize -import dev.usbharu.hideout.activitypub.domain.model.objects.Object -import dev.usbharu.hideout.activitypub.domain.model.objects.ObjectDeserializer - -open class Reject( - override val actor: String, - override val id: String, - @JsonDeserialize(using = ObjectDeserializer::class) @JsonProperty("object") val apObject: Object -) : Object(listOf("Reject")), HasId, HasActor { - override fun equals(other: Any?): Boolean { - if (this === other) return true - if (javaClass != other?.javaClass) return false - if (!super.equals(other)) return false - - other as Reject - - if (actor != other.actor) return false - if (id != other.id) return false - if (apObject != other.apObject) return false - - return true - } - - override fun hashCode(): Int { - var result = super.hashCode() - result = 31 * result + actor.hashCode() - result = 31 * result + id.hashCode() - result = 31 * result + apObject.hashCode() - return result - } - - override fun toString(): String { - return "Reject(" + - "actor='$actor', " + - "id='$id', " + - "apObject=$apObject" + - ")" + - " ${super.toString()}" - } -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/domain/model/StringOrObject.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/domain/model/StringOrObject.kt deleted file mode 100644 index 3a9969db..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/domain/model/StringOrObject.kt +++ /dev/null @@ -1,76 +0,0 @@ -package dev.usbharu.hideout.activitypub.domain.model - -import com.fasterxml.jackson.annotation.JsonCreator -import com.fasterxml.jackson.core.JsonGenerator -import com.fasterxml.jackson.core.JsonParser -import com.fasterxml.jackson.core.type.TypeReference -import com.fasterxml.jackson.databind.* - -open class StringOrObject { - var contextString: String? = null - var contextObject: Map? = null - - @JsonCreator - protected constructor() - - constructor(string: String) : this() { - contextString = string - } - - constructor(contextObject: Map) : this() { - this.contextObject = contextObject - } - - fun isEmpty(): Boolean = contextString.isNullOrEmpty() and contextObject.isNullOrEmpty() - - override fun equals(other: Any?): Boolean { - if (this === other) return true - if (javaClass != other?.javaClass) return false - - other as StringOrObject - - if (contextString != other.contextString) return false - if (contextObject != other.contextObject) return false - - return true - } - - override fun hashCode(): Int { - var result = contextString?.hashCode() ?: 0 - result = 31 * result + (contextObject?.hashCode() ?: 0) - return result - } - - override fun toString(): String = "StringOrObject(contextString=$contextString, contextObject=$contextObject)" -} - -class StringOrObjectDeserializer : JsonDeserializer() { - override fun deserialize(p: JsonParser?, ctxt: DeserializationContext): StringOrObject { - val readTree: JsonNode = p?.codec?.readTree(p) ?: return StringOrObject("") - return if (readTree.isValueNode) { - StringOrObject(readTree.textValue()) - } else if (readTree.isObject) { - val map: Map = ctxt.readTreeAsValue( - readTree, - ctxt.typeFactory.constructType(object : TypeReference>() {}) - ) - StringOrObject(map) - } else { - StringOrObject("") - } - } -} - -class StringORObjectSerializer : JsonSerializer() { - override fun serialize(value: StringOrObject?, gen: JsonGenerator?, serializers: SerializerProvider) { - if (value == null) { - serializers.defaultSerializeNull(gen) - return - } - if (value.contextString != null) { - gen?.writeString(value.contextString) - } else { - serializers.defaultSerializeValue(value.contextObject, gen) - } - } -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/domain/model/Tombstone.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/domain/model/Tombstone.kt deleted file mode 100644 index 56ea7cde..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/domain/model/Tombstone.kt +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright (C) 2024 usbharu - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package dev.usbharu.hideout.activitypub.domain.model - -import dev.usbharu.hideout.activitypub.domain.model.objects.Object - -open class Tombstone(type: List = emptyList(), override val id: String) : - Object(add(type, "Tombstone")), - HasId { - override fun equals(other: Any?): Boolean { - if (this === other) return true - if (javaClass != other?.javaClass) return false - if (!super.equals(other)) return false - - other as Tombstone - - return id == other.id - } - - override fun hashCode(): Int { - var result = super.hashCode() - result = 31 * result + id.hashCode() - return result - } - - override fun toString(): String = "Tombstone(id='$id') ${super.toString()}" -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/domain/model/Undo.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/domain/model/Undo.kt deleted file mode 100644 index 038d4054..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/domain/model/Undo.kt +++ /dev/null @@ -1,66 +0,0 @@ -/* - * Copyright (C) 2024 usbharu - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package dev.usbharu.hideout.activitypub.domain.model - -import com.fasterxml.jackson.annotation.JsonProperty -import com.fasterxml.jackson.databind.annotation.JsonDeserialize -import dev.usbharu.hideout.activitypub.domain.model.objects.Object -import dev.usbharu.hideout.activitypub.domain.model.objects.ObjectDeserializer - -open class Undo( - type: List = emptyList(), - override val actor: String, - override val id: String, - @JsonDeserialize(using = ObjectDeserializer::class) - @JsonProperty("object") val apObject: Object, - val published: String? -) : Object(add(type, "Undo")), HasId, HasActor { - - override fun equals(other: Any?): Boolean { - if (this === other) return true - if (javaClass != other?.javaClass) return false - if (!super.equals(other)) return false - - other as Undo - - if (actor != other.actor) return false - if (id != other.id) return false - if (apObject != other.apObject) return false - if (published != other.published) return false - - return true - } - - override fun hashCode(): Int { - var result = super.hashCode() - result = 31 * result + actor.hashCode() - result = 31 * result + id.hashCode() - result = 31 * result + apObject.hashCode() - result = 31 * result + (published?.hashCode() ?: 0) - return result - } - - override fun toString(): String { - return "Undo(" + - "actor='$actor', " + - "id='$id', " + - "apObject=$apObject, " + - "published=$published" + - ")" + - " ${super.toString()}" - } -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/domain/model/nodeinfo/Nodeinfo.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/domain/model/nodeinfo/Nodeinfo.kt deleted file mode 100644 index 2e3cbf1f..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/domain/model/nodeinfo/Nodeinfo.kt +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Copyright (C) 2024 usbharu - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package dev.usbharu.hideout.activitypub.domain.model.nodeinfo - -data class Nodeinfo( - val links: List -) { - data class Links( - val rel: String, - val href: String - ) -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/domain/model/nodeinfo/Nodeinfo2_0.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/domain/model/nodeinfo/Nodeinfo2_0.kt deleted file mode 100644 index f65749ef..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/domain/model/nodeinfo/Nodeinfo2_0.kt +++ /dev/null @@ -1,67 +0,0 @@ -/* - * Copyright (C) 2024 usbharu - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -@file:Suppress("ClassName") - -package dev.usbharu.hideout.activitypub.domain.model.nodeinfo - -@Suppress("ClassNaming") -data class Nodeinfo2_0( - val version: String, - val software: Software, - val protocols: List, - val services: Services, - val openRegistrations: Boolean, - val usage: Usage, - val metadata: Metadata -) { - data class Software( - val name: String, - val version: String - ) - - data class Services( - val inbound: List, - val outbound: List - ) - - data class Usage( - val users: Users, - val localPosts: Int, - val localComments: Int - ) { - data class Users( - val total: Int, - val activeHalfYear: Int, - val activeMonth: Int - ) - } - - data class Metadata( - val nodeName: String, - val nodeDescription: String, - val maintainer: Maintainer, - val langs: List, - val tosUrl: String, - val repositoryUrl: String, - val feedbackUrl: String, - ) { - data class Maintainer( - val name: String, - val email: String - ) - } -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/domain/model/objects/Object.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/domain/model/objects/Object.kt deleted file mode 100644 index c40b37fb..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/domain/model/objects/Object.kt +++ /dev/null @@ -1,77 +0,0 @@ -/* - * Copyright (C) 2024 usbharu - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package dev.usbharu.hideout.activitypub.domain.model.objects - -import com.fasterxml.jackson.core.JsonGenerator -import com.fasterxml.jackson.databind.JsonSerializer -import com.fasterxml.jackson.databind.SerializerProvider -import com.fasterxml.jackson.databind.annotation.JsonSerialize -import dev.usbharu.hideout.activitypub.domain.model.JsonLd - -open class Object : JsonLd { - @JsonSerialize(using = TypeSerializer::class) - var type: List = emptyList() - set(value) { - field = value.filter { it.isNotBlank() } - } - - protected constructor() - constructor(type: List) : super() { - this.type = type.filter { it.isNotBlank() } - } - - override fun equals(other: Any?): Boolean { - if (this === other) return true - if (javaClass != other?.javaClass) return false - if (!super.equals(other)) return false - - other as Object - - return type == other.type - } - - override fun hashCode(): Int { - var result = super.hashCode() - result = 31 * result + type.hashCode() - return result - } - - override fun toString(): String = "Object(type=$type) ${super.toString()}" - - companion object { - @JvmStatic - protected fun add(list: List, type: String): List { - val toMutableList = list.toMutableList() - toMutableList.add(type) - return toMutableList.distinct() - } - } -} - -class TypeSerializer : JsonSerializer>() { - override fun serialize(value: List?, gen: JsonGenerator?, serializers: SerializerProvider?) { - if (value?.size == 1) { - gen?.writeString(value[0]) - } else { - gen?.writeStartArray() - value?.forEach { - gen?.writeString(it) - } - gen?.writeEndArray() - } - } -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/domain/model/objects/ObjectDeserializer.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/domain/model/objects/ObjectDeserializer.kt deleted file mode 100644 index 1a554745..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/domain/model/objects/ObjectDeserializer.kt +++ /dev/null @@ -1,110 +0,0 @@ -/* - * Copyright (C) 2024 usbharu - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package dev.usbharu.hideout.activitypub.domain.model.objects - -import com.fasterxml.jackson.core.JsonParser -import com.fasterxml.jackson.databind.DeserializationContext -import com.fasterxml.jackson.databind.JsonDeserializer -import com.fasterxml.jackson.databind.JsonNode -import dev.usbharu.hideout.activitypub.domain.model.* -import dev.usbharu.hideout.activitypub.service.common.ExtendedActivityVocabulary - -class ObjectDeserializer : JsonDeserializer() { - @Suppress("LongMethod", "CyclomaticComplexMethod") - override fun deserialize(p: JsonParser?, ctxt: DeserializationContext?): Object? { - requireNotNull(p) - val treeNode: JsonNode = requireNotNull(p.codec?.readTree(p)) - if (treeNode.isValueNode) { - return ObjectValue( - emptyList(), - treeNode.asText() - ) - } else if (treeNode.isObject) { - val type = treeNode["type"] - val activityType = if (type.isArray) { - type.firstNotNullOf { jsonNode: JsonNode -> - ExtendedActivityVocabulary.values().firstOrNull { it.name.equals(jsonNode.asText(), true) } - } - } else if (type.isValueNode) { - ExtendedActivityVocabulary.values().firstOrNull { it.name.equals(type.asText(), true) } - } else { - null - } - - return when (activityType) { - ExtendedActivityVocabulary.Follow -> p.codec.treeToValue(treeNode, Follow::class.java) - ExtendedActivityVocabulary.Note -> p.codec.treeToValue(treeNode, Note::class.java) - ExtendedActivityVocabulary.Object -> p.codec.treeToValue(treeNode, Object::class.java) - ExtendedActivityVocabulary.Link -> null - ExtendedActivityVocabulary.Activity -> null - ExtendedActivityVocabulary.IntransitiveActivity -> null - ExtendedActivityVocabulary.Collection -> null - ExtendedActivityVocabulary.OrderedCollection -> null - ExtendedActivityVocabulary.CollectionPage -> null - ExtendedActivityVocabulary.OrderedCollectionPage -> null - ExtendedActivityVocabulary.Accept -> p.codec.treeToValue(treeNode, Accept::class.java) - ExtendedActivityVocabulary.Add -> null - ExtendedActivityVocabulary.Announce -> p.codec.treeToValue(treeNode, Announce::class.java) - ExtendedActivityVocabulary.Arrive -> null - ExtendedActivityVocabulary.Block -> null - ExtendedActivityVocabulary.Create -> p.codec.treeToValue(treeNode, Create::class.java) - ExtendedActivityVocabulary.Delete -> p.codec.treeToValue(treeNode, Delete::class.java) - ExtendedActivityVocabulary.Dislike -> null - ExtendedActivityVocabulary.Flag -> null - ExtendedActivityVocabulary.Ignore -> null - ExtendedActivityVocabulary.Invite -> null - ExtendedActivityVocabulary.Join -> null - ExtendedActivityVocabulary.Leave -> null - ExtendedActivityVocabulary.Like -> p.codec.treeToValue(treeNode, Like::class.java) - ExtendedActivityVocabulary.Listen -> null - ExtendedActivityVocabulary.Move -> null - ExtendedActivityVocabulary.Offer -> null - ExtendedActivityVocabulary.Question -> null - ExtendedActivityVocabulary.Reject -> p.codec.treeToValue(treeNode, Reject::class.java) - ExtendedActivityVocabulary.Read -> null - ExtendedActivityVocabulary.Remove -> null - ExtendedActivityVocabulary.TentativeReject -> null - ExtendedActivityVocabulary.TentativeAccept -> null - ExtendedActivityVocabulary.Travel -> null - ExtendedActivityVocabulary.Undo -> p.codec.treeToValue(treeNode, Undo::class.java) - ExtendedActivityVocabulary.Update -> null - ExtendedActivityVocabulary.View -> null - ExtendedActivityVocabulary.Application -> null - ExtendedActivityVocabulary.Group -> null - ExtendedActivityVocabulary.Organization -> null - ExtendedActivityVocabulary.Person -> p.codec.treeToValue(treeNode, Person::class.java) - ExtendedActivityVocabulary.Service -> null - ExtendedActivityVocabulary.Article -> null - ExtendedActivityVocabulary.Audio -> null - ExtendedActivityVocabulary.Document -> p.codec.treeToValue(treeNode, Document::class.java) - ExtendedActivityVocabulary.Event -> null - ExtendedActivityVocabulary.Image -> p.codec.treeToValue(treeNode, Image::class.java) - ExtendedActivityVocabulary.Page -> null - ExtendedActivityVocabulary.Place -> null - ExtendedActivityVocabulary.Profile -> null - ExtendedActivityVocabulary.Relationship -> null - ExtendedActivityVocabulary.Tombstone -> p.codec.treeToValue(treeNode, Tombstone::class.java) - ExtendedActivityVocabulary.Video -> null - ExtendedActivityVocabulary.Mention -> null - ExtendedActivityVocabulary.Emoji -> p.codec.treeToValue(treeNode, Emoji::class.java) - null -> null - } - } else { - return null - } - } -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/domain/model/objects/ObjectValue.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/domain/model/objects/ObjectValue.kt deleted file mode 100644 index 6bb2c9d5..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/domain/model/objects/ObjectValue.kt +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright (C) 2024 usbharu - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package dev.usbharu.hideout.activitypub.domain.model.objects - -import com.fasterxml.jackson.annotation.JsonCreator - -@Suppress("VariableNaming") -open class ObjectValue @JsonCreator constructor(type: List, var `object`: String) : Object( - type -) { - - override fun equals(other: Any?): Boolean { - if (this === other) return true - if (javaClass != other?.javaClass) return false - if (!super.equals(other)) return false - - other as ObjectValue - - return `object` == other.`object` - } - - override fun hashCode(): Int { - var result = super.hashCode() - result = 31 * result + `object`.hashCode() - return result - } - - override fun toString(): String = "ObjectValue(`object`='$`object`') ${super.toString()}" -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/domain/model/webfinger/WebFinger.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/domain/model/webfinger/WebFinger.kt deleted file mode 100644 index 3760a991..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/domain/model/webfinger/WebFinger.kt +++ /dev/null @@ -1,21 +0,0 @@ -/* - * Copyright (C) 2024 usbharu - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package dev.usbharu.hideout.activitypub.domain.model.webfinger - -data class WebFinger(val subject: String, val links: List) { - data class Link(val rel: String, val type: String, val href: String) -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/infrastructure/exposedquery/ExposedAnnounceQueryService.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/infrastructure/exposedquery/ExposedAnnounceQueryService.kt deleted file mode 100644 index 7807131d..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/infrastructure/exposedquery/ExposedAnnounceQueryService.kt +++ /dev/null @@ -1,82 +0,0 @@ -/* - * Copyright (C) 2024 usbharu - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package dev.usbharu.hideout.activitypub.infrastructure.exposedquery - -import dev.usbharu.hideout.activitypub.domain.model.Announce -import dev.usbharu.hideout.activitypub.query.AnnounceQueryService -import dev.usbharu.hideout.activitypub.service.objects.note.APNoteServiceImpl -import dev.usbharu.hideout.application.infrastructure.exposed.ResultRowMapper -import dev.usbharu.hideout.core.domain.model.post.Post -import dev.usbharu.hideout.core.domain.model.post.PostRepository -import dev.usbharu.hideout.core.domain.model.post.Visibility -import dev.usbharu.hideout.core.infrastructure.exposedrepository.Actors -import dev.usbharu.hideout.core.infrastructure.exposedrepository.Posts -import org.jetbrains.exposed.sql.ResultRow -import org.jetbrains.exposed.sql.selectAll -import org.springframework.stereotype.Repository -import java.time.Instant - -@Repository -class ExposedAnnounceQueryService( - private val postRepository: PostRepository, - private val postResultRowMapper: ResultRowMapper -) : AnnounceQueryService { - override suspend fun findById(id: Long): Pair? { - return Posts - .leftJoin(Actors) - .selectAll().where { Posts.id eq id } - .singleOrNull() - ?.let { (it.toAnnounce() ?: return null) to (postResultRowMapper.map(it)) } - } - - override suspend fun findByApId(apId: String): Pair? { - return Posts - .leftJoin(Actors) - .selectAll().where { Posts.apId eq apId } - .singleOrNull() - ?.let { (it.toAnnounce() ?: return null) to (postResultRowMapper.map(it)) } - } - - private suspend fun ResultRow.toAnnounce(): Announce? { - val repostId = this[Posts.repostId] ?: return null - val repost = postRepository.findById(repostId)?.url ?: return null - - val (to, cc) = visibility( - Visibility.entries.first { visibility -> visibility.ordinal == this[Posts.visibility] }, - this[Actors.followers] - ) - - return Announce( - type = emptyList(), - id = this[Posts.apId], - apObject = repost, - actor = this[Actors.url], - published = Instant.ofEpochMilli(this[Posts.createdAt]).toString(), - to = to, - cc = cc - ) - } - - private fun visibility(visibility: Visibility, followers: String?): Pair, List> { - return when (visibility) { - Visibility.PUBLIC -> listOf(APNoteServiceImpl.public) to listOf(APNoteServiceImpl.public) - Visibility.UNLISTED -> listOfNotNull(followers) to listOf(APNoteServiceImpl.public) - Visibility.FOLLOWERS -> listOfNotNull(followers) to listOfNotNull(followers) - Visibility.DIRECT -> TODO() - } - } -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/infrastructure/exposedquery/NoteQueryServiceImpl.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/infrastructure/exposedquery/NoteQueryServiceImpl.kt deleted file mode 100644 index 85ee76ae..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/infrastructure/exposedquery/NoteQueryServiceImpl.kt +++ /dev/null @@ -1,129 +0,0 @@ -/* - * Copyright (C) 2024 usbharu - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package dev.usbharu.hideout.activitypub.infrastructure.exposedquery - -import dev.usbharu.hideout.activitypub.domain.model.Document -import dev.usbharu.hideout.activitypub.domain.model.Note -import dev.usbharu.hideout.activitypub.query.NoteQueryService -import dev.usbharu.hideout.activitypub.service.objects.note.APNoteServiceImpl.Companion.public -import dev.usbharu.hideout.application.infrastructure.exposed.QueryMapper -import dev.usbharu.hideout.core.domain.model.post.Post -import dev.usbharu.hideout.core.domain.model.post.PostRepository -import dev.usbharu.hideout.core.domain.model.post.Visibility -import dev.usbharu.hideout.core.infrastructure.exposedrepository.* -import org.jetbrains.exposed.sql.Query -import org.jetbrains.exposed.sql.ResultRow -import org.jetbrains.exposed.sql.selectAll -import org.slf4j.LoggerFactory -import org.springframework.stereotype.Repository -import java.time.Instant - -@Repository -class NoteQueryServiceImpl(private val postRepository: PostRepository, private val postQueryMapper: QueryMapper) : - NoteQueryService { - override suspend fun findById(id: Long): Pair? { - return Posts - .leftJoin(Actors) - .leftJoin(PostsMedia) - .leftJoin(Media) - .selectAll().where { Posts.id eq id } - .let { - (it.toNote() ?: return null) to ( - postQueryMapper.map(it) - .singleOrNull() ?: return null - ) - } - } - - override suspend fun findByApid(apId: String): Pair? { - return Posts - .leftJoin(Actors) - .leftJoin(PostsMedia) - .leftJoin(Media) - .selectAll().where { Posts.apId eq apId } - .let { - (it.toNote() ?: return null) to ( - postQueryMapper.map(it) - .singleOrNull() ?: return null - ) - } - } - - private suspend fun ResultRow.toNote(mediaList: List): Note { - val replyId = this[Posts.replyId] - val replyTo = if (replyId != null) { - val url = postRepository.findById(replyId)?.url - if (url == null) { - logger.warn("Failed to get replyId: $replyId") - } - url - } else { - null - } - - val repostId = this[Posts.repostId] - val repost = if (repostId != null) { - val url = postRepository.findById(repostId)?.url - if (url == null) { - logger.warn("Failed to get repostId: $repostId") - } - url - } else { - null - } - - val visibility1 = - visibility( - Visibility.values().first { visibility -> visibility.ordinal == this[Posts.visibility] }, - this[Actors.followers] - ) - return Note( - id = this[Posts.apId], - attributedTo = this[Actors.url], - content = this[Posts.text], - published = Instant.ofEpochMilli(this[Posts.createdAt]).toString(), - to = visibility1.first, - cc = visibility1.second, - inReplyTo = replyTo, - misskeyQuote = repost, - quoteUri = repost, - quoteUrl = repost, - sensitive = this[Posts.sensitive], - attachment = mediaList.map { Document(url = it.url, mediaType = "image/jpeg") } - ) - } - - private suspend fun Query.toNote(): Note? { - return this.groupBy { it[Posts.id] } - .map { it.value } - .map { it.first().toNote(it.mapNotNull { resultRow -> resultRow.toMediaOrNull() }) } - .singleOrNull() - } - - private fun visibility(visibility: Visibility, followers: String?): Pair, List> { - return when (visibility) { - Visibility.PUBLIC -> listOf(public) to listOf(public) - Visibility.UNLISTED -> listOfNotNull(followers) to listOf(public) - Visibility.FOLLOWERS -> listOfNotNull(followers) to listOfNotNull(followers) - Visibility.DIRECT -> TODO() - } - } - - companion object { - private val logger = LoggerFactory.getLogger(NoteQueryServiceImpl::class.java) - } -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/interfaces/api/actor/UserAPController.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/interfaces/api/actor/UserAPController.kt deleted file mode 100644 index a51a7683..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/interfaces/api/actor/UserAPController.kt +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Copyright (C) 2024 usbharu - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package dev.usbharu.hideout.activitypub.interfaces.api.actor - -import dev.usbharu.hideout.activitypub.domain.model.Person -import org.springframework.http.ResponseEntity -import org.springframework.web.bind.annotation.GetMapping -import org.springframework.web.bind.annotation.PathVariable -import org.springframework.web.bind.annotation.RestController - -@RestController -interface UserAPController { - @GetMapping("/users/{username}") - suspend fun userAp(@PathVariable("username") username: String): ResponseEntity -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/interfaces/api/actor/UserAPControllerImpl.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/interfaces/api/actor/UserAPControllerImpl.kt deleted file mode 100644 index 73b9b8a5..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/interfaces/api/actor/UserAPControllerImpl.kt +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright (C) 2024 usbharu - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package dev.usbharu.hideout.activitypub.interfaces.api.actor - -import dev.usbharu.hideout.activitypub.domain.Constant -import dev.usbharu.hideout.activitypub.domain.model.Person -import dev.usbharu.hideout.activitypub.service.objects.user.APUserService -import dev.usbharu.hideout.core.domain.exception.resource.UserNotFoundException -import org.springframework.http.HttpStatus -import org.springframework.http.ResponseEntity -import org.springframework.web.bind.annotation.RestController - -@RestController -class UserAPControllerImpl(private val apUserService: APUserService) : UserAPController { - override suspend fun userAp(username: String): ResponseEntity { - val person = try { - apUserService.getPersonByName(username) - } catch (_: UserNotFoundException) { - return ResponseEntity.notFound().build() - } - person.context += Constant.context - return ResponseEntity(person, HttpStatus.OK) - } -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/interfaces/api/hostmeta/HostMetaController.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/interfaces/api/hostmeta/HostMetaController.kt deleted file mode 100644 index f89b9260..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/interfaces/api/hostmeta/HostMetaController.kt +++ /dev/null @@ -1,52 +0,0 @@ -/* - * Copyright (C) 2024 usbharu - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package dev.usbharu.hideout.activitypub.interfaces.api.hostmeta - -import dev.usbharu.hideout.application.config.ApplicationConfig -import org.intellij.lang.annotations.Language -import org.springframework.http.HttpStatus -import org.springframework.http.ResponseEntity -import org.springframework.web.bind.annotation.GetMapping -import org.springframework.web.bind.annotation.RestController - -@RestController -class HostMetaController(private val applicationConfig: ApplicationConfig) { - - val xml = //language=XML - """ - - -""" - - @Language("JSON") - val json = """{ - "links": [ - { - "rel": "lrdd", - "type": "application/jrd+json", - "template": "${applicationConfig.url}/.well-known/webfinger?resource={uri}" - } - ] -}""" - - @GetMapping("/.well-known/host-meta", produces = ["application/xml"]) - fun hostmeta(): ResponseEntity = ResponseEntity(xml, HttpStatus.OK) - - @GetMapping("/.well-known/host-meta.json", produces = ["application/json"]) - fun hostmetJson(): ResponseEntity = ResponseEntity(json, HttpStatus.OK) -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/interfaces/api/inbox/InboxController.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/interfaces/api/inbox/InboxController.kt deleted file mode 100644 index 2cbbdbec..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/interfaces/api/inbox/InboxController.kt +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright (C) 2024 usbharu - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package dev.usbharu.hideout.activitypub.interfaces.api.inbox - -import jakarta.servlet.http.HttpServletRequest -import org.springframework.http.ResponseEntity -import org.springframework.web.bind.annotation.RequestMapping -import org.springframework.web.bind.annotation.RequestMethod -import org.springframework.web.bind.annotation.RestController - -@RestController -interface InboxController { - @RequestMapping( - "/inbox", - "/users/{username}/inbox", - produces = [ - "application/activity+json", - "application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\"" - ], - consumes = ["application/json", "application/*+json"], - method = [RequestMethod.POST] - ) - suspend fun inbox(httpServletRequest: HttpServletRequest): ResponseEntity -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/interfaces/api/inbox/InboxControllerImpl.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/interfaces/api/inbox/InboxControllerImpl.kt deleted file mode 100644 index 5045007b..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/interfaces/api/inbox/InboxControllerImpl.kt +++ /dev/null @@ -1,133 +0,0 @@ -/* - * Copyright (C) 2024 usbharu - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package dev.usbharu.hideout.activitypub.interfaces.api.inbox - -import dev.usbharu.hideout.activitypub.service.common.APService -import dev.usbharu.hideout.core.infrastructure.springframework.httpsignature.HttpSignatureHeaderChecker -import dev.usbharu.httpsignature.common.HttpHeaders -import dev.usbharu.httpsignature.common.HttpMethod -import dev.usbharu.httpsignature.common.HttpRequest -import jakarta.servlet.http.HttpServletRequest -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.slf4j.MDCContext -import kotlinx.coroutines.withContext -import org.slf4j.LoggerFactory -import org.springframework.http.HttpHeaders.WWW_AUTHENTICATE -import org.springframework.http.HttpStatus -import org.springframework.http.ResponseEntity -import org.springframework.web.bind.annotation.RestController -import java.net.URL - -@RestController -class InboxControllerImpl( - private val apService: APService, - private val httpSignatureHeaderChecker: HttpSignatureHeaderChecker, -) : InboxController { - @Suppress("TooGenericExceptionCaught") - override suspend fun inbox( - httpServletRequest: HttpServletRequest, - ): ResponseEntity { - val headersList = httpServletRequest.headerNames?.toList().orEmpty() - LOGGER.trace("Inbox Headers {}", headersList) - - val body = withContext(Dispatchers.IO + MDCContext()) { - httpServletRequest.inputStream.readAllBytes()!! - } - - val responseEntity = checkHeader(httpServletRequest, body) - - if (responseEntity != null) { - return responseEntity - } - - val parseActivity = try { - apService.parseActivity(body.decodeToString()) - } catch (e: Exception) { - LOGGER.warn("FAILED Parse Activity", e) - return ResponseEntity.accepted().build() - } - LOGGER.info("INBOX Processing Activity Type: {}", parseActivity) - try { - val url = httpServletRequest.requestURL.toString() - - val headers = - headersList.associateWith { header -> - httpServletRequest.getHeaders(header)?.toList().orEmpty() - } - - apService.processActivity( - body.decodeToString(), - parseActivity, - HttpRequest( - URL(url + httpServletRequest.queryString.orEmpty()), - HttpHeaders(headers), - HttpMethod.POST - ), - headers - ) - } catch (e: Exception) { - LOGGER.warn("FAILED Process Activity $parseActivity", e) - return ResponseEntity(HttpStatus.ACCEPTED) - } - LOGGER.info("SUCCESS Processing Activity Type: {}", parseActivity) - return ResponseEntity(HttpStatus.ACCEPTED) - } - - private fun checkHeader( - httpServletRequest: HttpServletRequest, - body: ByteArray, - ): ResponseEntity? { - try { - httpSignatureHeaderChecker.checkDate(httpServletRequest.getHeader("date")!!) - } catch (_: NullPointerException) { - return ResponseEntity.status(HttpStatus.BAD_REQUEST).body("Required date header") - } catch (_: IllegalArgumentException) { - return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body("Request is too old.") - } - try { - httpSignatureHeaderChecker.checkHost(httpServletRequest.getHeader("host")!!) - } catch (_: NullPointerException) { - return ResponseEntity.status(HttpStatus.BAD_REQUEST).body("Required host header") - } catch (_: IllegalArgumentException) { - return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body("Wrong host for request") - } - try { - httpSignatureHeaderChecker.checkDigest(body, httpServletRequest.getHeader("digest")!!) - } catch (_: NullPointerException) { - return ResponseEntity.status(HttpStatus.BAD_REQUEST) - .body("Required request body digest in digest header (sha256)") - } catch (_: IllegalArgumentException) { - return ResponseEntity - .status(HttpStatus.UNAUTHORIZED) - .body("Wrong digest for request") - } - - if (httpServletRequest.getHeader("signature").orEmpty().isBlank()) { - return ResponseEntity.status(HttpStatus.UNAUTHORIZED) - .header( - WWW_AUTHENTICATE, - "Signature realm=\"Example\",headers=\"(request-target) date host digest\"" - ) - .build() - } - return null - } - - companion object { - private val LOGGER = LoggerFactory.getLogger(InboxControllerImpl::class.java) - } -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/interfaces/api/nodeinfo/NodeinfoController.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/interfaces/api/nodeinfo/NodeinfoController.kt deleted file mode 100644 index 00cda72e..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/interfaces/api/nodeinfo/NodeinfoController.kt +++ /dev/null @@ -1,82 +0,0 @@ -/* - * Copyright (C) 2024 usbharu - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package dev.usbharu.hideout.activitypub.interfaces.api.nodeinfo - -import dev.usbharu.hideout.activitypub.domain.model.nodeinfo.Nodeinfo -import dev.usbharu.hideout.activitypub.domain.model.nodeinfo.Nodeinfo2_0 -import dev.usbharu.hideout.application.config.ApplicationConfig -import org.springframework.http.HttpStatus -import org.springframework.http.ResponseEntity -import org.springframework.web.bind.annotation.GetMapping -import org.springframework.web.bind.annotation.RestController - -@RestController -class NodeinfoController(private val applicationConfig: ApplicationConfig) { - @GetMapping("/.well-known/nodeinfo") - fun nodeinfo(): ResponseEntity { - return ResponseEntity( - Nodeinfo( - listOf( - Nodeinfo.Links( - "http://nodeinfo.diaspora.software/ns/schema/2.0", - "${applicationConfig.url}/nodeinfo/2.0" - ) - ) - ), - HttpStatus.OK - ) - } - - @GetMapping("/nodeinfo/2.0") - @Suppress("FunctionNaming") - fun nodeinfo2_0(): ResponseEntity { - return ResponseEntity( - Nodeinfo2_0( - version = "2.0", - software = Nodeinfo2_0.Software( - name = "hideout", - version = "0.0.1" - ), - protocols = listOf("activitypub"), - services = Nodeinfo2_0.Services( - inbound = emptyList(), - outbound = emptyList() - ), - openRegistrations = false, - usage = Nodeinfo2_0.Usage( - users = Nodeinfo2_0.Usage.Users( - total = 1, - activeHalfYear = 1, - activeMonth = 1 - ), - localPosts = 1, - localComments = 0 - ), - metadata = Nodeinfo2_0.Metadata( - nodeName = "hideout", - nodeDescription = "hideout test server", - maintainer = Nodeinfo2_0.Metadata.Maintainer("usbharu", "i@usbharu.dev"), - langs = emptyList(), - tosUrl = "", - repositoryUrl = "https://github.com/usbharu/Hideout", - feedbackUrl = "https://github.com/usbharu/Hideout/issues/new/choose", - ) - ), - HttpStatus.OK - ) - } -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/interfaces/api/note/NoteApController.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/interfaces/api/note/NoteApController.kt deleted file mode 100644 index 2150171a..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/interfaces/api/note/NoteApController.kt +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Copyright (C) 2024 usbharu - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package dev.usbharu.hideout.activitypub.interfaces.api.note - -import dev.usbharu.hideout.activitypub.domain.model.Note -import org.springframework.http.ResponseEntity -import org.springframework.web.bind.annotation.GetMapping -import org.springframework.web.bind.annotation.PathVariable - -interface NoteApController { - @GetMapping("/users/*/posts/{postId}") - suspend fun postsAp( - @PathVariable("postId") postId: Long - ): ResponseEntity -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/interfaces/api/note/NoteApControllerImpl.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/interfaces/api/note/NoteApControllerImpl.kt deleted file mode 100644 index 79e54dcb..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/interfaces/api/note/NoteApControllerImpl.kt +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright (C) 2024 usbharu - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package dev.usbharu.hideout.activitypub.interfaces.api.note - -import dev.usbharu.hideout.activitypub.domain.model.Note -import dev.usbharu.hideout.activitypub.service.objects.note.NoteApApiService -import dev.usbharu.hideout.core.infrastructure.springframework.httpsignature.HttpSignatureUser -import org.springframework.http.ResponseEntity -import org.springframework.security.core.context.SecurityContextHolder -import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken -import org.springframework.web.bind.annotation.PathVariable -import org.springframework.web.bind.annotation.RestController - -@RestController -class NoteApControllerImpl(private val noteApApiService: NoteApApiService) : NoteApController { - override suspend fun postsAp( - @PathVariable(value = "postId") postId: Long, - ): ResponseEntity { - val context = SecurityContextHolder.getContext() - val userId = - if (context.authentication is PreAuthenticatedAuthenticationToken && - context.authentication.details is HttpSignatureUser - ) { - (context.authentication.details as HttpSignatureUser).id - } else { - null - } - - val note = noteApApiService.getNote(postId, userId) - if (note != null) { - return ResponseEntity.ok(note) - } - return ResponseEntity.notFound().build() - } -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/interfaces/api/outbox/OutboxController.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/interfaces/api/outbox/OutboxController.kt deleted file mode 100644 index 0574dd34..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/interfaces/api/outbox/OutboxController.kt +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright (C) 2024 usbharu - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package dev.usbharu.hideout.activitypub.interfaces.api.outbox - -import org.springframework.http.ResponseEntity -import org.springframework.web.bind.annotation.RequestMapping -import org.springframework.web.bind.annotation.RequestMethod -import org.springframework.web.bind.annotation.RestController - -@RestController -interface OutboxController { - @RequestMapping("/outbox", "/users/{username}/outbox", method = [RequestMethod.POST, RequestMethod.GET]) - suspend fun outbox(): ResponseEntity -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/interfaces/api/outbox/OutboxControllerImpl.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/interfaces/api/outbox/OutboxControllerImpl.kt deleted file mode 100644 index b9ebbbc4..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/interfaces/api/outbox/OutboxControllerImpl.kt +++ /dev/null @@ -1,27 +0,0 @@ -/* - * Copyright (C) 2024 usbharu - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package dev.usbharu.hideout.activitypub.interfaces.api.outbox - -import org.springframework.http.HttpStatus -import org.springframework.http.ResponseEntity -import org.springframework.web.bind.annotation.RestController - -@RestController -class OutboxControllerImpl : OutboxController { - override suspend fun outbox(): ResponseEntity = - ResponseEntity(HttpStatus.NOT_IMPLEMENTED) -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/interfaces/api/webfinger/WebFingerController.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/interfaces/api/webfinger/WebFingerController.kt deleted file mode 100644 index a6d65710..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/interfaces/api/webfinger/WebFingerController.kt +++ /dev/null @@ -1,68 +0,0 @@ -/* - * Copyright (C) 2024 usbharu - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package dev.usbharu.hideout.activitypub.interfaces.api.webfinger - -import dev.usbharu.hideout.activitypub.domain.model.webfinger.WebFinger -import dev.usbharu.hideout.activitypub.service.webfinger.WebFingerApiService -import dev.usbharu.hideout.application.config.ApplicationConfig -import dev.usbharu.hideout.core.domain.exception.resource.UserNotFoundException -import dev.usbharu.hideout.util.AcctUtil -import kotlinx.coroutines.runBlocking -import org.slf4j.LoggerFactory -import org.springframework.http.HttpStatus -import org.springframework.http.ResponseEntity -import org.springframework.stereotype.Controller -import org.springframework.web.bind.annotation.GetMapping -import org.springframework.web.bind.annotation.RequestParam - -@Controller -class WebFingerController( - private val webFingerApiService: WebFingerApiService, - private val applicationConfig: ApplicationConfig -) { - @GetMapping("/.well-known/webfinger") - fun webfinger(@RequestParam("resource") resource: String): ResponseEntity = runBlocking { - logger.info("WEBFINGER Lookup webfinger resource: {}", resource) - val acct = try { - AcctUtil.parse(resource.replace("acct:", "")) - } catch (e: IllegalArgumentException) { - logger.warn("FAILED Parse acct.", e) - return@runBlocking ResponseEntity.badRequest().build() - } - val user = try { - webFingerApiService.findByNameAndDomain(acct.username, acct.domain ?: applicationConfig.url.host) - } catch (_: UserNotFoundException) { - return@runBlocking ResponseEntity.notFound().build() - } - val webFinger = WebFinger( - "acct:${user.name}@${user.domain}", - listOf( - WebFinger.Link( - "self", - "application/activity+json", - user.url - ) - ) - ) - logger.info("SUCCESS Lookup webfinger resource: {} acct: {}", resource, acct) - ResponseEntity(webFinger, HttpStatus.OK) - } - - companion object { - private val logger = LoggerFactory.getLogger(WebFingerController::class.java) - } -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/query/AnnounceQueryService.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/query/AnnounceQueryService.kt deleted file mode 100644 index 77bf6287..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/query/AnnounceQueryService.kt +++ /dev/null @@ -1,27 +0,0 @@ -/* - * Copyright (C) 2024 usbharu - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package dev.usbharu.hideout.activitypub.query - -import dev.usbharu.hideout.activitypub.domain.model.Announce -import dev.usbharu.hideout.core.domain.model.post.Post -import org.springframework.stereotype.Repository - -@Repository -interface AnnounceQueryService { - suspend fun findById(id: Long): Pair? - suspend fun findByApId(apId: String): Pair? -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/query/NoteQueryService.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/query/NoteQueryService.kt deleted file mode 100644 index 8d76227b..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/query/NoteQueryService.kt +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (C) 2024 usbharu - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package dev.usbharu.hideout.activitypub.query - -import dev.usbharu.hideout.activitypub.domain.model.Note -import dev.usbharu.hideout.core.domain.model.post.Post - -interface NoteQueryService { - suspend fun findById(id: Long): Pair? - suspend fun findByApid(apId: String): Pair? -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/service/activity/accept/ApAcceptProcessor.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/service/activity/accept/ApAcceptProcessor.kt deleted file mode 100644 index 7df08bb3..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/service/activity/accept/ApAcceptProcessor.kt +++ /dev/null @@ -1,62 +0,0 @@ -/* - * Copyright (C) 2024 usbharu - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package dev.usbharu.hideout.activitypub.service.activity.accept - -import dev.usbharu.hideout.activitypub.domain.exception.IllegalActivityPubObjectException -import dev.usbharu.hideout.activitypub.domain.model.Accept -import dev.usbharu.hideout.activitypub.domain.model.Follow -import dev.usbharu.hideout.activitypub.service.common.AbstractActivityPubProcessor -import dev.usbharu.hideout.activitypub.service.common.ActivityPubProcessContext -import dev.usbharu.hideout.activitypub.service.common.ActivityType -import dev.usbharu.hideout.application.external.Transaction -import dev.usbharu.hideout.core.domain.exception.resource.UserNotFoundException -import dev.usbharu.hideout.core.domain.model.actor.ActorRepository -import dev.usbharu.hideout.core.service.relationship.RelationshipService -import org.springframework.stereotype.Service - -@Service -class ApAcceptProcessor( - transaction: Transaction, - private val relationshipService: RelationshipService, - private val actorRepository: ActorRepository -) : - AbstractActivityPubProcessor(transaction) { - - override suspend fun internalProcess(activity: ActivityPubProcessContext) { - val value = activity.activity.apObject - - if (value.type.contains("Follow").not()) { - logger.warn("FAILED Activity type isn't Follow.") - throw IllegalActivityPubObjectException("Invalid type ${value.type}") - } - - val follow = value as Follow - - val userUrl = follow.apObject - val followerUrl = follow.actor - - val user = actorRepository.findByUrl(userUrl) ?: throw UserNotFoundException.withUrl(userUrl) - val follower = actorRepository.findByUrl(followerUrl) ?: throw UserNotFoundException.withUrl(followerUrl) - - relationshipService.acceptFollowRequest(user.id, follower.id) - logger.debug("SUCCESS Follow from ${user.url} to ${follower.url}.") - } - - override fun isSupported(activityType: ActivityType): Boolean = activityType == ActivityType.Accept - - override fun type(): Class = Accept::class.java -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/service/activity/accept/ApSendAcceptService.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/service/activity/accept/ApSendAcceptService.kt deleted file mode 100644 index ea1793e6..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/service/activity/accept/ApSendAcceptService.kt +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright (C) 2024 usbharu - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package dev.usbharu.hideout.activitypub.service.activity.accept - -import dev.usbharu.hideout.activitypub.domain.model.Accept -import dev.usbharu.hideout.activitypub.domain.model.Follow -import dev.usbharu.hideout.core.domain.model.actor.Actor -import dev.usbharu.hideout.core.external.job.DeliverAcceptTask -import dev.usbharu.owl.producer.api.OwlProducer -import org.springframework.stereotype.Service - -interface ApSendAcceptService { - suspend fun sendAcceptFollow(actor: Actor, target: Actor) -} - -@Service -class ApSendAcceptServiceImpl( - private val owlProducer: OwlProducer, -) : ApSendAcceptService { - override suspend fun sendAcceptFollow(actor: Actor, target: Actor) { - val deliverAcceptTask = DeliverAcceptTask( - Accept( - apObject = Follow( - apObject = actor.url, - actor = target.url - ), - actor = actor.url - ), - target.inbox, - actor.id - ) - - owlProducer.publishTask(deliverAcceptTask) - } -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/service/activity/announce/ApAnnounceProcessor.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/service/activity/announce/ApAnnounceProcessor.kt deleted file mode 100644 index 17ff0cac..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/service/activity/announce/ApAnnounceProcessor.kt +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright (C) 2024 usbharu - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package dev.usbharu.hideout.activitypub.service.activity.announce - -import dev.usbharu.hideout.activitypub.domain.model.Announce -import dev.usbharu.hideout.activitypub.service.common.AbstractActivityPubProcessor -import dev.usbharu.hideout.activitypub.service.common.ActivityPubProcessContext -import dev.usbharu.hideout.activitypub.service.common.ActivityType -import dev.usbharu.hideout.activitypub.service.objects.note.APNoteService -import dev.usbharu.hideout.application.external.Transaction -import org.springframework.stereotype.Service - -@Service -class ApAnnounceProcessor(transaction: Transaction, private val apNoteService: APNoteService) : - AbstractActivityPubProcessor(transaction) { - override suspend fun internalProcess(activity: ActivityPubProcessContext) { - apNoteService.fetchAnnounce(activity.activity) - } - - override fun isSupported(activityType: ActivityType): Boolean = ActivityType.Announce == activityType - - override fun type(): Class = Announce::class.java -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/service/activity/create/ApSendCreateService.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/service/activity/create/ApSendCreateService.kt deleted file mode 100644 index 2b8921ca..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/service/activity/create/ApSendCreateService.kt +++ /dev/null @@ -1,23 +0,0 @@ -/* - * Copyright (C) 2024 usbharu - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package dev.usbharu.hideout.activitypub.service.activity.create - -import dev.usbharu.hideout.core.domain.model.post.Post - -interface ApSendCreateService { - suspend fun createNote(post: Post) -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/service/activity/create/ApSendCreateServiceImpl.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/service/activity/create/ApSendCreateServiceImpl.kt deleted file mode 100644 index 0fb4a1e8..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/service/activity/create/ApSendCreateServiceImpl.kt +++ /dev/null @@ -1,66 +0,0 @@ -/* - * Copyright (C) 2024 usbharu - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package dev.usbharu.hideout.activitypub.service.activity.create - -import dev.usbharu.hideout.activitypub.domain.model.Create -import dev.usbharu.hideout.activitypub.query.NoteQueryService -import dev.usbharu.hideout.application.config.ApplicationConfig -import dev.usbharu.hideout.core.domain.exception.resource.PostNotFoundException -import dev.usbharu.hideout.core.domain.exception.resource.UserNotFoundException -import dev.usbharu.hideout.core.domain.model.actor.ActorRepository -import dev.usbharu.hideout.core.domain.model.post.Post -import dev.usbharu.hideout.core.external.job.DeliverCreateTask -import dev.usbharu.hideout.core.query.FollowerQueryService -import dev.usbharu.owl.producer.api.OwlProducer -import org.slf4j.LoggerFactory -import org.springframework.stereotype.Service - -@Service -class ApSendCreateServiceImpl( - private val followerQueryService: FollowerQueryService, - private val noteQueryService: NoteQueryService, - private val applicationConfig: ApplicationConfig, - private val actorRepository: ActorRepository, - private val owlProducer: OwlProducer, -) : ApSendCreateService { - override suspend fun createNote(post: Post) { - logger.info("CREATE Create Local Note ${post.url}") - logger.debug("START Create Local Note ${post.url}") - logger.trace("{}", post) - val followers = followerQueryService.findFollowersById(post.actorId) - - logger.debug("DELIVER Deliver Note Create ${followers.size} accounts.") - - val userEntity = actorRepository.findById(post.actorId) ?: throw UserNotFoundException.withId(post.actorId) - val note = noteQueryService.findById(post.id)?.first ?: throw PostNotFoundException.withId(post.id) - val create = Create( - name = "Create Note", - apObject = note, - actor = note.attributedTo, - id = "${applicationConfig.url}/create/note/${post.id}" - ) - followers.forEach { followerEntity -> - owlProducer.publishTask(DeliverCreateTask(create, userEntity.url, followerEntity.inbox)) - } - - logger.debug("SUCCESS Create Local Note ${post.url}") - } - - companion object { - private val logger = LoggerFactory.getLogger(ApSendCreateServiceImpl::class.java) - } -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/service/activity/create/CreateActivityProcessor.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/service/activity/create/CreateActivityProcessor.kt deleted file mode 100644 index 9be6fa65..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/service/activity/create/CreateActivityProcessor.kt +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright (C) 2024 usbharu - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package dev.usbharu.hideout.activitypub.service.activity.create - -import dev.usbharu.hideout.activitypub.domain.model.Create -import dev.usbharu.hideout.activitypub.domain.model.Note -import dev.usbharu.hideout.activitypub.service.common.AbstractActivityPubProcessor -import dev.usbharu.hideout.activitypub.service.common.ActivityPubProcessContext -import dev.usbharu.hideout.activitypub.service.common.ActivityType -import dev.usbharu.hideout.activitypub.service.objects.note.APNoteService -import dev.usbharu.hideout.application.external.Transaction -import org.springframework.stereotype.Service - -@Service -class CreateActivityProcessor(transaction: Transaction, private val apNoteService: APNoteService) : - AbstractActivityPubProcessor(transaction) { - override suspend fun internalProcess(activity: ActivityPubProcessContext) { - apNoteService.fetchNote(activity.activity.apObject as Note) - } - - override fun isSupported(activityType: ActivityType): Boolean = activityType == ActivityType.Create - - override fun type(): Class = Create::class.java -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/service/activity/delete/APDeleteProcessor.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/service/activity/delete/APDeleteProcessor.kt deleted file mode 100644 index a0d789c5..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/service/activity/delete/APDeleteProcessor.kt +++ /dev/null @@ -1,66 +0,0 @@ -/* - * Copyright (C) 2024 usbharu - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package dev.usbharu.hideout.activitypub.service.activity.delete - -import dev.usbharu.hideout.activitypub.domain.exception.IllegalActivityPubObjectException -import dev.usbharu.hideout.activitypub.domain.model.Delete -import dev.usbharu.hideout.activitypub.domain.model.HasId -import dev.usbharu.hideout.activitypub.domain.model.objects.ObjectValue -import dev.usbharu.hideout.activitypub.service.common.AbstractActivityPubProcessor -import dev.usbharu.hideout.activitypub.service.common.ActivityPubProcessContext -import dev.usbharu.hideout.activitypub.service.common.ActivityType -import dev.usbharu.hideout.application.external.Transaction -import dev.usbharu.hideout.core.domain.model.actor.ActorRepository -import dev.usbharu.hideout.core.domain.model.post.PostRepository -import dev.usbharu.hideout.core.service.post.PostService -import dev.usbharu.hideout.core.service.user.UserService -import org.springframework.stereotype.Service - -@Service -class APDeleteProcessor( - transaction: Transaction, - private val userService: UserService, - private val postService: PostService, - private val actorRepository: ActorRepository, - private val postRepository: PostRepository -) : - AbstractActivityPubProcessor(transaction) { - override suspend fun internalProcess(activity: ActivityPubProcessContext) { - val value = activity.activity.apObject - val deleteId = if (value is HasId) { - value.id - } else if (value is ObjectValue) { - value.`object` - } else { - throw IllegalActivityPubObjectException("object hasn't id or object") - } - - val actor = actorRepository.findByUrl(deleteId) - actor?.let { userService.deleteRemoteActor(it.id) } - - val post = postRepository.findByApId(deleteId) - if (post == null) { - logger.warn("FAILED Delete id: {} is not found.", deleteId) - return - } - postService.deleteRemote(post) - } - - override fun isSupported(activityType: ActivityType): Boolean = activityType == ActivityType.Delete - - override fun type(): Class = Delete::class.java -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/service/activity/delete/APSendDeleteService.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/service/activity/delete/APSendDeleteService.kt deleted file mode 100644 index 4570808d..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/service/activity/delete/APSendDeleteService.kt +++ /dev/null @@ -1,86 +0,0 @@ -/* - * Copyright (C) 2024 usbharu - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package dev.usbharu.hideout.activitypub.service.activity.delete - -import dev.usbharu.hideout.activitypub.domain.model.Delete -import dev.usbharu.hideout.activitypub.domain.model.Tombstone -import dev.usbharu.hideout.activitypub.domain.model.objects.ObjectValue -import dev.usbharu.hideout.application.config.ApplicationConfig -import dev.usbharu.hideout.core.domain.exception.resource.UserNotFoundException -import dev.usbharu.hideout.core.domain.model.actor.Actor -import dev.usbharu.hideout.core.domain.model.actor.ActorRepository -import dev.usbharu.hideout.core.domain.model.post.Post -import dev.usbharu.hideout.core.external.job.DeliverDeleteTask -import dev.usbharu.hideout.core.query.FollowerQueryService -import dev.usbharu.owl.producer.api.OwlProducer -import org.springframework.stereotype.Service -import java.time.Instant - -interface APSendDeleteService { - suspend fun sendDeleteNote(deletedPost: Post) - suspend fun sendDeleteActor(deletedActor: Actor) -} - -@Service -class APSendDeleteServiceImpl( - private val followerQueryService: FollowerQueryService, - private val applicationConfig: ApplicationConfig, - private val actorRepository: ActorRepository, - private val owlProducer: OwlProducer, -) : APSendDeleteService { - override suspend fun sendDeleteNote(deletedPost: Post) { - val actor = - actorRepository.findById(deletedPost.actorId) ?: throw UserNotFoundException.withId(deletedPost.actorId) - val followersById = followerQueryService.findFollowersById(deletedPost.actorId) - - val delete = Delete( - actor = actor.url, - id = "${applicationConfig.url}/delete/note/${deletedPost.id}", - published = Instant.now().toString(), - `object` = Tombstone(id = deletedPost.apId) - ) - - followersById.forEach { - val jobProps = DeliverDeleteTask( - delete, - it.inbox, - actor.id - ) - - owlProducer.publishTask(jobProps) - } - } - - override suspend fun sendDeleteActor(deletedActor: Actor) { - val followers = followerQueryService.findFollowersById(deletedActor.id) - - val delete = Delete( - actor = deletedActor.url, - `object` = ObjectValue(emptyList(), `object` = deletedActor.url), - id = "${applicationConfig.url}/delete/actor/${deletedActor.id}", - published = Instant.now().toString() - ) - - followers.forEach { - DeliverDeleteTask( - delete = delete, - it.inbox, - deletedActor.id - ) - } - } -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/service/activity/follow/APFollowProcessor.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/service/activity/follow/APFollowProcessor.kt deleted file mode 100644 index 3cc8cc68..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/service/activity/follow/APFollowProcessor.kt +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright (C) 2024 usbharu - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package dev.usbharu.hideout.activitypub.service.activity.follow - -import dev.usbharu.hideout.activitypub.domain.model.Follow -import dev.usbharu.hideout.activitypub.service.common.AbstractActivityPubProcessor -import dev.usbharu.hideout.activitypub.service.common.ActivityPubProcessContext -import dev.usbharu.hideout.activitypub.service.common.ActivityType -import dev.usbharu.hideout.application.external.Transaction -import dev.usbharu.hideout.core.external.job.ReceiveFollowTask -import dev.usbharu.owl.producer.api.OwlProducer -import org.springframework.stereotype.Service - -@Service -class APFollowProcessor( - transaction: Transaction, - private val owlProducer: OwlProducer, -) : - AbstractActivityPubProcessor(transaction) { - override suspend fun internalProcess(activity: ActivityPubProcessContext) { - logger.info("FOLLOW from: {} to {}", activity.activity.actor, activity.activity.apObject) - - // inboxをジョブキューに乗せているので既に不要だが、フォロー承認制アカウントを実装する際に必要なので残す - val jobProps = ReceiveFollowTask( - activity.activity.actor, - activity.activity, - activity.activity.apObject - ) - owlProducer.publishTask(jobProps) - } - - override fun isSupported(activityType: ActivityType): Boolean = activityType == ActivityType.Follow - - override fun type(): Class = Follow::class.java -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/service/activity/follow/APReceiveFollowService.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/service/activity/follow/APReceiveFollowService.kt deleted file mode 100644 index 9d69e7be..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/service/activity/follow/APReceiveFollowService.kt +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright (C) 2024 usbharu - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package dev.usbharu.hideout.activitypub.service.activity.follow - -import dev.usbharu.hideout.activitypub.domain.model.Follow -import dev.usbharu.hideout.core.external.job.ReceiveFollowTask -import dev.usbharu.owl.producer.api.OwlProducer -import org.slf4j.LoggerFactory -import org.springframework.stereotype.Service - -interface APReceiveFollowService { - suspend fun receiveFollow(follow: Follow) -} - -@Service -class APReceiveFollowServiceImpl( - private val owlProducer: OwlProducer, -) : APReceiveFollowService { - override suspend fun receiveFollow(follow: Follow) { - logger.info("FOLLOW from: {} to: {}", follow.actor, follow.apObject) - owlProducer.publishTask(ReceiveFollowTask(follow.actor, follow, follow.apObject)) - return - } - - companion object { - private val logger = LoggerFactory.getLogger(APReceiveFollowServiceImpl::class.java) - } -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/service/activity/follow/APSendFollowService.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/service/activity/follow/APSendFollowService.kt deleted file mode 100644 index 80ac8c7f..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/service/activity/follow/APSendFollowService.kt +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright (C) 2024 usbharu - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package dev.usbharu.hideout.activitypub.service.activity.follow - -import dev.usbharu.hideout.activitypub.domain.model.Follow -import dev.usbharu.hideout.activitypub.service.common.APRequestService -import dev.usbharu.hideout.application.config.ApplicationConfig -import dev.usbharu.hideout.core.service.follow.SendFollowDto -import org.springframework.stereotype.Service - -interface APSendFollowService { - suspend fun sendFollow(sendFollowDto: SendFollowDto) -} - -@Service -class APSendFollowServiceImpl( - private val apRequestService: APRequestService, - private val applicationConfig: ApplicationConfig, -) : APSendFollowService { - override suspend fun sendFollow(sendFollowDto: SendFollowDto) { - val follow = Follow( - apObject = sendFollowDto.followTargetActorId.url, - actor = sendFollowDto.actorId.url, - id = "${applicationConfig.url}/follow/${sendFollowDto.actorId.id}/${sendFollowDto.followTargetActorId.id}" - ) - - apRequestService.apPost(sendFollowDto.followTargetActorId.inbox, follow, sendFollowDto.actorId) - } -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/service/activity/like/APLikeProcessor.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/service/activity/like/APLikeProcessor.kt deleted file mode 100644 index b018b6d4..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/service/activity/like/APLikeProcessor.kt +++ /dev/null @@ -1,77 +0,0 @@ -/* - * Copyright (C) 2024 usbharu - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package dev.usbharu.hideout.activitypub.service.activity.like - -import dev.usbharu.hideout.activitypub.domain.exception.FailedToGetActivityPubResourceException -import dev.usbharu.hideout.activitypub.domain.model.Emoji -import dev.usbharu.hideout.activitypub.domain.model.Like -import dev.usbharu.hideout.activitypub.service.common.AbstractActivityPubProcessor -import dev.usbharu.hideout.activitypub.service.common.ActivityPubProcessContext -import dev.usbharu.hideout.activitypub.service.common.ActivityType -import dev.usbharu.hideout.activitypub.service.objects.emoji.EmojiService -import dev.usbharu.hideout.activitypub.service.objects.note.APNoteService -import dev.usbharu.hideout.activitypub.service.objects.user.APUserService -import dev.usbharu.hideout.application.external.Transaction -import dev.usbharu.hideout.core.domain.model.emoji.UnicodeEmoji -import dev.usbharu.hideout.core.service.reaction.ReactionService -import org.springframework.stereotype.Service - -@Service -class APLikeProcessor( - transaction: Transaction, - private val apUserService: APUserService, - private val apNoteService: APNoteService, - private val reactionService: ReactionService, - private val emojiService: EmojiService -) : - AbstractActivityPubProcessor(transaction) { - override suspend fun internalProcess(activity: ActivityPubProcessContext) { - val actor = activity.activity.actor - val content = activity.activity.content - - val target = activity.activity.apObject - - val personWithEntity = apUserService.fetchPersonWithEntity(actor) - - try { - val post = apNoteService.fetchNoteWithEntity(target).second - - val emoji = if (content.startsWith(":")) { - val tag = activity.activity.tag - (tag.firstOrNull { it is Emoji } as? Emoji)?.let { emojiService.fetchEmoji(it).second } - } else { - UnicodeEmoji(content) - } - - reactionService.receiveReaction( - emoji ?: UnicodeEmoji("❤"), - personWithEntity.second.id, - post.id - ) - - logger.debug("SUCCESS Add Like($content) from ${personWithEntity.second.url} to ${post.url}") - } catch (e: FailedToGetActivityPubResourceException) { - logger.debug("FAILED failed to get {}", target) - logger.trace("", e) - return - } - } - - override fun isSupported(activityType: ActivityType): Boolean = activityType == ActivityType.Like - - override fun type(): Class = Like::class.java -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/service/activity/like/APReactionService.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/service/activity/like/APReactionService.kt deleted file mode 100644 index fac9ccff..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/service/activity/like/APReactionService.kt +++ /dev/null @@ -1,93 +0,0 @@ -/* - * Copyright (C) 2024 usbharu - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package dev.usbharu.hideout.activitypub.service.activity.like - -import dev.usbharu.hideout.activitypub.domain.model.Like -import dev.usbharu.hideout.activitypub.domain.model.Undo -import dev.usbharu.hideout.application.config.ApplicationConfig -import dev.usbharu.hideout.core.domain.exception.resource.PostNotFoundException -import dev.usbharu.hideout.core.domain.exception.resource.UserNotFoundException -import dev.usbharu.hideout.core.domain.model.actor.ActorRepository -import dev.usbharu.hideout.core.domain.model.post.PostRepository -import dev.usbharu.hideout.core.domain.model.reaction.Reaction -import dev.usbharu.hideout.core.external.job.DeliverReactionTask -import dev.usbharu.hideout.core.external.job.DeliverUndoTask -import dev.usbharu.hideout.core.query.FollowerQueryService -import dev.usbharu.owl.producer.api.OwlProducer -import org.springframework.stereotype.Service -import java.time.Instant - -interface APReactionService { - suspend fun reaction(like: Reaction) - suspend fun removeReaction(like: Reaction) -} - -@Service -class APReactionServiceImpl( - private val followerQueryService: FollowerQueryService, - private val actorRepository: ActorRepository, - private val postRepository: PostRepository, - private val applicationConfig: ApplicationConfig, - private val owlProducer: OwlProducer, -) : APReactionService { - override suspend fun reaction(like: Reaction) { - val followers = followerQueryService.findFollowersById(like.actorId) - val user = actorRepository.findById(like.actorId) ?: throw UserNotFoundException.withId(like.actorId) - val post = - postRepository.findById(like.postId) ?: throw PostNotFoundException.withId(like.postId) - followers.forEach { follower -> - owlProducer.publishTask( - DeliverReactionTask( - actor = user.url, - like = Like( - actor = user.url, - id = "${applicationConfig.url}/like/note/${post.id}", - content = "❤", - apObject = post.url - ), - inbox = follower.inbox - ) - ) - } - } - - override suspend fun removeReaction(like: Reaction) { - val followers = followerQueryService.findFollowersById(like.actorId) - val user = actorRepository.findById(like.actorId) ?: throw UserNotFoundException.withId(like.actorId) - val post = - postRepository.findById(like.postId) ?: throw PostNotFoundException.withId(like.postId) - followers.forEach { follower -> - owlProducer.publishTask( - DeliverUndoTask( - signer = user.id, - inbox = follower.inbox, - undo = Undo( - actor = user.url, - id = "${applicationConfig.url}/undo/like/${post.id}", - apObject = Like( - actor = user.url, - id = "${applicationConfig.url}/like/note/${post.id}", - content = "❤", - apObject = post.url - ), - published = Instant.now().toString(), - ) - ) - ) - } - } -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/service/activity/reject/ApRejectProcessor.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/service/activity/reject/ApRejectProcessor.kt deleted file mode 100644 index 9a3d5bb0..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/service/activity/reject/ApRejectProcessor.kt +++ /dev/null @@ -1,68 +0,0 @@ -/* - * Copyright (C) 2024 usbharu - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package dev.usbharu.hideout.activitypub.service.activity.reject - -import dev.usbharu.hideout.activitypub.domain.model.Follow -import dev.usbharu.hideout.activitypub.domain.model.Reject -import dev.usbharu.hideout.activitypub.service.common.AbstractActivityPubProcessor -import dev.usbharu.hideout.activitypub.service.common.ActivityPubProcessContext -import dev.usbharu.hideout.activitypub.service.common.ActivityType -import dev.usbharu.hideout.application.external.Transaction -import dev.usbharu.hideout.core.domain.exception.resource.UserNotFoundException -import dev.usbharu.hideout.core.domain.model.actor.ActorRepository -import dev.usbharu.hideout.core.service.relationship.RelationshipService -import org.springframework.stereotype.Service - -@Service -class ApRejectProcessor( - private val relationshipService: RelationshipService, - transaction: Transaction, - private val actorRepository: ActorRepository -) : - AbstractActivityPubProcessor(transaction) { - override suspend fun internalProcess(activity: ActivityPubProcessContext) { - val activityType = activity.activity.apObject.type.firstOrNull { it == "Follow" } - - if (activityType == null) { - logger.warn("FAILED Process Reject Activity type: {}", activity.activity.apObject.type) - return - } - when (activityType) { - "Follow" -> { - val user = actorRepository.findByUrl(activity.activity.actor) ?: throw UserNotFoundException.withUrl( - activity.activity.actor - ) - - activity.activity.apObject as Follow - - val actor = activity.activity.apObject.actor - - val target = actorRepository.findByUrl(actor) ?: throw UserNotFoundException.withUrl(actor) - - logger.debug("REJECT Follow user {} target {}", user.url, target.url) - - relationshipService.rejectFollowRequest(user.id, target.id) - } - - else -> {} - } - } - - override fun isSupported(activityType: ActivityType): Boolean = activityType == ActivityType.Reject - - override fun type(): Class = Reject::class.java -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/service/activity/reject/ApSendRejectService.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/service/activity/reject/ApSendRejectService.kt deleted file mode 100644 index e925aef0..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/service/activity/reject/ApSendRejectService.kt +++ /dev/null @@ -1,23 +0,0 @@ -/* - * Copyright (C) 2024 usbharu - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package dev.usbharu.hideout.activitypub.service.activity.reject - -import dev.usbharu.hideout.core.domain.model.actor.Actor - -interface ApSendRejectService { - suspend fun sendRejectFollow(actor: Actor, target: Actor) -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/service/activity/reject/ApSendRejectServiceImpl.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/service/activity/reject/ApSendRejectServiceImpl.kt deleted file mode 100644 index eef8dc2f..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/service/activity/reject/ApSendRejectServiceImpl.kt +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright (C) 2024 usbharu - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package dev.usbharu.hideout.activitypub.service.activity.reject - -import dev.usbharu.hideout.activitypub.domain.model.Follow -import dev.usbharu.hideout.activitypub.domain.model.Reject -import dev.usbharu.hideout.application.config.ApplicationConfig -import dev.usbharu.hideout.core.domain.model.actor.Actor -import dev.usbharu.hideout.core.external.job.DeliverRejectTask -import dev.usbharu.owl.producer.api.OwlProducer -import org.springframework.stereotype.Service - -@Service -class ApSendRejectServiceImpl( - private val applicationConfig: ApplicationConfig, - private val owlProducer: OwlProducer, -) : ApSendRejectService { - override suspend fun sendRejectFollow(actor: Actor, target: Actor) { - val deliverRejectTask = DeliverRejectTask( - Reject( - actor.url, - "${applicationConfig.url}/reject/${actor.id}/${target.id}", - Follow(apObject = actor.url, actor = target.url) - ), - target.inbox, - actor.id - ) - - owlProducer.publishTask(deliverRejectTask) - } -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/service/activity/undo/APSendUndoService.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/service/activity/undo/APSendUndoService.kt deleted file mode 100644 index 80d4828b..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/service/activity/undo/APSendUndoService.kt +++ /dev/null @@ -1,23 +0,0 @@ -/* - * Copyright (C) 2024 usbharu - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package dev.usbharu.hideout.activitypub.service.activity.undo - -import dev.usbharu.hideout.core.domain.model.actor.Actor - -interface APSendUndoService { - suspend fun sendUndoFollow(actor: Actor, target: Actor) -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/service/activity/undo/APSendUndoServiceImpl.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/service/activity/undo/APSendUndoServiceImpl.kt deleted file mode 100644 index 37326c0d..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/service/activity/undo/APSendUndoServiceImpl.kt +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Copyright (C) 2024 usbharu - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package dev.usbharu.hideout.activitypub.service.activity.undo - -import dev.usbharu.hideout.activitypub.domain.model.Follow -import dev.usbharu.hideout.activitypub.domain.model.Undo -import dev.usbharu.hideout.application.config.ApplicationConfig -import dev.usbharu.hideout.core.domain.model.actor.Actor -import dev.usbharu.hideout.core.external.job.DeliverUndoTask -import dev.usbharu.owl.producer.api.OwlProducer -import org.springframework.stereotype.Service -import java.time.Instant - -@Service -class APSendUndoServiceImpl( - private val applicationConfig: ApplicationConfig, - private val owlProducer: OwlProducer, -) : APSendUndoService { - override suspend fun sendUndoFollow(actor: Actor, target: Actor) { - val deliverUndoTask = DeliverUndoTask( - Undo( - actor = actor.url, - id = "${applicationConfig.url}/undo/follow/${actor.id}/${target.url}", - apObject = Follow( - apObject = actor.url, - actor = target.url - ), - published = Instant.now().toString() - ), - target.inbox, - actor.id - ) - - owlProducer.publishTask(deliverUndoTask) - } -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/service/activity/undo/APUndoProcessor.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/service/activity/undo/APUndoProcessor.kt deleted file mode 100644 index e788929e..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/service/activity/undo/APUndoProcessor.kt +++ /dev/null @@ -1,145 +0,0 @@ -/* - * Copyright (C) 2024 usbharu - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package dev.usbharu.hideout.activitypub.service.activity.undo - -import dev.usbharu.hideout.activitypub.domain.model.* -import dev.usbharu.hideout.activitypub.domain.model.objects.ObjectValue -import dev.usbharu.hideout.activitypub.service.common.AbstractActivityPubProcessor -import dev.usbharu.hideout.activitypub.service.common.ActivityPubProcessContext -import dev.usbharu.hideout.activitypub.service.common.ActivityType -import dev.usbharu.hideout.activitypub.service.objects.user.APUserService -import dev.usbharu.hideout.application.external.Transaction -import dev.usbharu.hideout.core.domain.exception.resource.PostNotFoundException -import dev.usbharu.hideout.core.domain.exception.resource.UserNotFoundException -import dev.usbharu.hideout.core.domain.exception.resource.local.LocalUserNotFoundException -import dev.usbharu.hideout.core.domain.model.actor.ActorRepository -import dev.usbharu.hideout.core.domain.model.post.PostRepository -import dev.usbharu.hideout.core.service.post.PostService -import dev.usbharu.hideout.core.service.reaction.ReactionService -import dev.usbharu.hideout.core.service.relationship.RelationshipService -import dev.usbharu.hideout.core.service.user.UserService -import org.springframework.stereotype.Service - -@Service -class APUndoProcessor( - transaction: Transaction, - private val apUserService: APUserService, - private val relationshipService: RelationshipService, - private val reactionService: ReactionService, - private val actorRepository: ActorRepository, - private val postRepository: PostRepository, - private val postService: PostService, - private val userService: UserService, -) : AbstractActivityPubProcessor(transaction) { - override suspend fun internalProcess(activity: ActivityPubProcessContext) { - val undo = activity.activity - - val type = undo.apObject.type.firstOrNull { - it == "Block" || it == "Follow" || it == "Like" || it == "Announce" || it == "Accept" - } ?: return - - when (type) { - "Follow" -> { - follow(undo) - return - } - - "Accept" -> { - accept(undo) - return - } - - "Like" -> { - like(undo) - return - } - - "Announce" -> { - announce(undo) - return - } - - "Delete" -> { - delete(undo) - return - } - - else -> {} - } - TODO() - } - - private suspend fun accept(undo: Undo) { - val accept = undo.apObject as Accept - - val acceptObject = if (accept.apObject is ObjectValue) { - accept.apObject.`object` - } else if (accept.apObject is Follow) { - accept.apObject.apObject - } else { - logger.warn("FAILED Unsupported type. Undo Accept {}", accept.apObject.type) - return - } - - val accepter = apUserService.fetchPersonWithEntity(undo.actor, acceptObject).second - val target = actorRepository.findByUrl(acceptObject) ?: throw UserNotFoundException.withUrl(acceptObject) - - relationshipService.rejectFollowRequest(accepter.id, target.id) - return - } - - private suspend fun like(undo: Undo) { - val like = undo.apObject as Like - - val post = postRepository.findByUrl(like.apObject) ?: throw PostNotFoundException.withUrl(like.apObject) - - val signer = actorRepository.findById(post.actorId) ?: throw LocalUserNotFoundException.withId(post.actorId) - val actor = apUserService.fetchPersonWithEntity(like.actor, signer.url).second - - reactionService.receiveRemoveReaction(actor.id, post.id) - return - } - - private suspend fun follow(undo: Undo) { - val follow = undo.apObject as Follow - - val follower = apUserService.fetchPersonWithEntity(undo.actor, follow.apObject).second - val target = actorRepository.findByUrl(follow.apObject) ?: throw UserNotFoundException.withUrl(follow.apObject) - - relationshipService.unfollow(follower.id, target.id) - return - } - - private suspend fun announce(undo: Undo) { - val announce = undo.apObject as Announce - - val findByApId = postRepository.findByApId(announce.id) ?: return - postService.deleteRemote(findByApId) - } - - private suspend fun delete(undo: Undo) { - val announce = undo.apObject as Delete - - val actor = actorRepository.findByUrl(announce.actor) ?: throw UserNotFoundException.withUrl(announce.actor) - - userService.restorationRemoteActor(actor.id) - } - - override fun isSupported(activityType: ActivityType): Boolean = activityType == ActivityType.Undo - - override fun type(): Class = Undo::class.java -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/service/common/APRequestService.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/service/common/APRequestService.kt deleted file mode 100644 index 2507e3cb..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/service/common/APRequestService.kt +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright (C) 2024 usbharu - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package dev.usbharu.hideout.activitypub.service.common - -import dev.usbharu.hideout.activitypub.domain.model.objects.Object -import dev.usbharu.hideout.core.domain.model.actor.Actor - -interface APRequestService { - suspend fun apGet(url: String, signer: Actor? = null, responseClass: Class): R - suspend fun apPost( - url: String, - body: T? = null, - signer: Actor? = null, - responseClass: Class - ): R - - suspend fun apPost(url: String, body: T? = null, signer: Actor? = null): String -} - -suspend inline fun APRequestService.apGet(url: String, signer: Actor? = null): R = - apGet(url, signer, R::class.java) - -suspend inline fun APRequestService.apPost( - url: String, - body: T? = null, - signer: Actor? = null -): R = apPost(url, body, signer, R::class.java) diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/service/common/APRequestServiceImpl.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/service/common/APRequestServiceImpl.kt deleted file mode 100644 index 4b463532..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/service/common/APRequestServiceImpl.kt +++ /dev/null @@ -1,251 +0,0 @@ -/* - * Copyright (C) 2024 usbharu - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package dev.usbharu.hideout.activitypub.service.common - -import com.fasterxml.jackson.databind.ObjectMapper -import dev.usbharu.hideout.activitypub.domain.Constant -import dev.usbharu.hideout.activitypub.domain.model.StringOrObject -import dev.usbharu.hideout.activitypub.domain.model.objects.Object -import dev.usbharu.hideout.core.domain.model.actor.Actor -import dev.usbharu.hideout.util.Base64Util -import dev.usbharu.hideout.util.HttpUtil.Activity -import dev.usbharu.hideout.util.RsaUtil -import dev.usbharu.httpsignature.common.HttpHeaders -import dev.usbharu.httpsignature.common.HttpMethod -import dev.usbharu.httpsignature.common.HttpRequest -import dev.usbharu.httpsignature.common.PrivateKey -import dev.usbharu.httpsignature.sign.HttpSignatureSigner -import io.ktor.client.* -import io.ktor.client.request.* -import io.ktor.client.statement.* -import io.ktor.http.* -import io.ktor.util.* -import org.slf4j.LoggerFactory -import org.springframework.beans.factory.annotation.Qualifier -import org.springframework.stereotype.Service -import java.net.URL -import java.security.MessageDigest -import java.time.ZoneId -import java.time.ZonedDateTime -import java.time.format.DateTimeFormatter - -@Service -class APRequestServiceImpl( - private val httpClient: HttpClient, - @Qualifier("activitypub") private val objectMapper: ObjectMapper, - private val httpSignatureSigner: HttpSignatureSigner, - @Qualifier("http") private val dateTimeFormatter: DateTimeFormatter, -) : APRequestService { - - override suspend fun apGet(url: String, signer: Actor?, responseClass: Class): R { - logger.debug("START ActivityPub Request GET url: {}, signer: {}", url, signer?.url) - val date = dateTimeFormatter.format(ZonedDateTime.now(ZoneId.of("GMT"))) - val u = URL(url) - val httpResponse = if (signer?.privateKey == null) { - apGetNotSign(url, date) - } else { - apGetSign(date, u, signer, url) - } - - val bodyAsText = httpResponse.bodyAsText() - val readValue = objectMapper.readValue(bodyAsText, responseClass) - logger.debug( - "SUCCESS ActivityPub Request GET status: {} url: {}", - httpResponse.status, - httpResponse.request.url - ) - logBody(bodyAsText, url) - return readValue - } - - private suspend fun apGetSign( - date: String, - u: URL, - signer: Actor, - url: String, - ): HttpResponse { - val headers = headers { - append("Accept", Activity) - append("Date", date) - append("Host", u.host) - } - - val sign = httpSignatureSigner.sign( - httpRequest = HttpRequest( - url = u, - headers = HttpHeaders(headers.toMap()), - HttpMethod.GET - ), - privateKey = PrivateKey( - keyId = "${signer.url}#pubkey", - privateKey = RsaUtil.decodeRsaPrivateKeyPem(signer.privateKey!!), - ), - signHeaders = listOf("(request-target)", "date", "host", "accept") - ) - - val httpResponse = httpClient.get(url) { - headers { - headers { - appendAll(headers) - append("Signature", sign.signatureHeader) -// remove("Host") - } - } - contentType(Activity) - } - return httpResponse - } - - private suspend fun apGetNotSign(url: String, date: String?) = httpClient.get(url) { - header("Accept", Activity) - header("Date", date) - } - - override suspend fun apPost( - url: String, - body: T?, - signer: Actor?, - responseClass: Class, - ): R { - val bodyAsText = apPost(url, body, signer) - return objectMapper.readValue(bodyAsText, responseClass) - } - - override suspend fun apPost(url: String, body: T?, signer: Actor?): String { - logger.debug("START ActivityPub Request POST url: {}, signer: {}", url, signer?.url) - val requestBody = addContextIfNotNull(body) - - logger.trace( - """ - | - |***** BEGIN HTTP Request Trace url: {} ***** - | - |$requestBody - | - |***** END HTTP Request Trace url: {} ***** - | - """.trimMargin(), - url, - url - ) - - val sha256 = MessageDigest.getInstance("SHA-256") - - val digest = Base64Util.encode(sha256.digest(requestBody.orEmpty().toByteArray())) - - val date = dateTimeFormatter.format(ZonedDateTime.now(ZoneId.of("GMT"))) - val u = URL(url) - val httpResponse = if (signer?.privateKey == null) { - apPostNotSign(url, date, digest, requestBody) - } else { - apPostSign(date, u, digest, signer, requestBody) - } - - val bodyAsText = httpResponse.bodyAsText() - logger.debug( - "SUCCESS ActivityPub Request POST status: {} url: {}", - httpResponse.status, - httpResponse.request.url - ) - logBody(bodyAsText, url) - return bodyAsText - } - - private suspend fun apPostNotSign( - url: String, - date: String?, - digest: String, - requestBody: String?, - ) = httpClient.post(url) { - accept(Activity) - header("Date", date) - header("Digest", "sha-256=$digest") - if (requestBody != null) { - setBody(requestBody) - contentType(Activity) - } - } - - private suspend fun apPostSign( - date: String, - u: URL, - digest: String, - signer: Actor, - requestBody: String?, - ): HttpResponse { - val headers = headers { - append("Accept", Activity) - append("Date", date) - append("Host", u.host) - append("Digest", "SHA-256=$digest") - } - - val sign = httpSignatureSigner.sign( - httpRequest = HttpRequest( - u, - HttpHeaders(headers.toMap()), - HttpMethod.POST - ), - privateKey = PrivateKey( - keyId = signer.keyId, - privateKey = RsaUtil.decodeRsaPrivateKeyPem(signer.privateKey!!) - ), - signHeaders = listOf("(request-target)", "date", "host", "digest") - ) - - val httpResponse = httpClient.post(u) { - headers { - appendAll(headers) - append("Signature", sign.signatureHeader) -// remove("Host") - } - setBody(requestBody) - contentType(Activity) - } - return httpResponse - } - - private fun addContextIfNotNull(body: T?) = if (body != null) { - val context = mutableListOf() - context.addAll(Constant.context) - context.addAll(body.context) - body.context = context - objectMapper.writeValueAsString(body) - } else { - null - } - - private fun logBody(bodyAsText: String, url: String) { - logger.trace( - """ - | - |***** BEGIN HTTP Response Trace url: {} ***** - | - |$bodyAsText - | - |***** END HTTP Response TRACE url: {} ***** - | - """.trimMargin(), - url, - url - ) - } - - companion object { - private val logger = LoggerFactory.getLogger(APRequestServiceImpl::class.java) - } -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/service/common/APResourceResolveService.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/service/common/APResourceResolveService.kt deleted file mode 100644 index bd42e197..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/service/common/APResourceResolveService.kt +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright (C) 2024 usbharu - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package dev.usbharu.hideout.activitypub.service.common - -import dev.usbharu.hideout.activitypub.domain.model.objects.Object -import dev.usbharu.hideout.core.domain.model.actor.Actor - -interface APResourceResolveService { - suspend fun resolve(url: String, clazz: Class, singer: Actor?): T - suspend fun resolve(url: String, clazz: Class, singerId: Long?): T -} - -suspend inline fun APResourceResolveService.resolve(url: String, singer: Actor?): T = - resolve(url, T::class.java, singer) - -suspend inline fun APResourceResolveService.resolve(url: String, singerId: Long?): T = - resolve(url, T::class.java, singerId) diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/service/common/APResourceResolveServiceImpl.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/service/common/APResourceResolveServiceImpl.kt deleted file mode 100644 index f40589b1..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/service/common/APResourceResolveServiceImpl.kt +++ /dev/null @@ -1,104 +0,0 @@ -/* - * Copyright (C) 2024 usbharu - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package dev.usbharu.hideout.activitypub.service.common - -import dev.usbharu.hideout.activitypub.domain.model.objects.Object -import dev.usbharu.hideout.core.domain.model.actor.Actor -import dev.usbharu.hideout.core.domain.model.actor.ActorRepository -import dev.usbharu.hideout.core.service.resource.CacheManager -import dev.usbharu.hideout.core.service.resource.ResolveResponse -import org.springframework.stereotype.Service -import java.io.InputStream - -@Service -class APResourceResolveServiceImpl( - private val apRequestService: APRequestService, - private val actorRepository: ActorRepository, - private val cacheManager: CacheManager -) : - APResourceResolveService { - - override suspend fun resolve(url: String, clazz: Class, singerId: Long?): T = - internalResolve(url, singerId, clazz) - - override suspend fun resolve(url: String, clazz: Class, singer: Actor?): T = - internalResolve(url, singer, clazz) - - private suspend fun internalResolve(url: String, singerId: Long?, clazz: Class): T { - val key = genCacheKey(url, singerId) - - cacheManager.putCache(key) { - runResolve(url, singerId?.let { actorRepository.findById(it) }, clazz) - } - return (cacheManager.getOrWait(key) as APResolveResponse).objects - } - - private suspend fun internalResolve(url: String, singer: Actor?, clazz: Class): T { - val key = genCacheKey(url, singer?.id) - cacheManager.putCache(key) { - runResolve(url, singer, clazz) - } - return (cacheManager.getOrWait(key) as APResolveResponse).objects - } - - private suspend fun runResolve(url: String, singer: Actor?, clazz: Class): ResolveResponse = - APResolveResponse(apRequestService.apGet(url, singer, clazz)) - - private fun genCacheKey(url: String, singerId: Long?): String { - if (singerId != null) { - return "$url-$singerId" - } - return url - } - - private class APResolveResponse(val objects: T) : ResolveResponse { - override suspend fun body(): InputStream { - TODO("Not yet implemented") - } - - override suspend fun bodyAsText(): String { - TODO("Not yet implemented") - } - - override suspend fun bodyAsBytes(): ByteArray { - TODO("Not yet implemented") - } - - override suspend fun header(): Map> { - TODO("Not yet implemented") - } - - override suspend fun status(): Int { - TODO("Not yet implemented") - } - - override suspend fun statusMessage(): String { - TODO("Not yet implemented") - } - - override fun equals(other: Any?): Boolean { - if (this === other) return true - if (javaClass != other?.javaClass) return false - - other as APResolveResponse<*> - - return objects == other.objects - } - - override fun hashCode(): Int = objects.hashCode() - } -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/service/common/APService.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/service/common/APService.kt deleted file mode 100644 index 64aa581b..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/service/common/APService.kt +++ /dev/null @@ -1,103 +0,0 @@ -/* - * Copyright (C) 2024 usbharu - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package dev.usbharu.hideout.activitypub.service.common - -import com.fasterxml.jackson.databind.JsonNode -import com.fasterxml.jackson.databind.ObjectMapper -import dev.usbharu.hideout.activitypub.domain.exception.JsonParseException -import dev.usbharu.hideout.core.external.job.InboxTask -import dev.usbharu.httpsignature.common.HttpRequest -import dev.usbharu.owl.producer.api.OwlProducer -import org.slf4j.Logger -import org.slf4j.LoggerFactory -import org.springframework.beans.factory.annotation.Qualifier -import org.springframework.stereotype.Service - -interface APService { - fun parseActivity(json: String): ActivityType - - suspend fun processActivity( - json: String, - type: ActivityType, - httpRequest: HttpRequest, - map: Map> - ) -} - -@Service -class APServiceImpl( - @Qualifier("activitypub") private val objectMapper: ObjectMapper, - private val owlProducer: OwlProducer, -) : APService { - - val logger: Logger = LoggerFactory.getLogger(APServiceImpl::class.java) - override fun parseActivity(json: String): ActivityType { - val readTree = try { - objectMapper.readTree(json) - } catch (e: com.fasterxml.jackson.core.JsonParseException) { - throw JsonParseException("Failed to parse json", e) - } - logger.trace( - """ - | - |***** Trace Begin Activity ***** - | - |{} - | - |***** Trace End Activity ***** - | - """.trimMargin(), - readTree.toPrettyString() - ) - if (readTree.isObject.not()) { - throw JsonParseException("Json is not object.") - } - val type = readTree["type"] ?: throw JsonParseException("Type is null") - if (type.isArray) { - try { - return type.firstNotNullOf { jsonNode: JsonNode -> - ActivityType.entries.firstOrNull { it.name.equals(jsonNode.asText(), true) } - } - } catch (e: NoSuchElementException) { - throw IllegalArgumentException("No valid TYPE", e) - } - } - try { - return ActivityType.entries.first { it.name.equals(type.asText(), true) } - } catch (e: NoSuchElementException) { - throw IllegalArgumentException("No valid TYPE", e) - } - } - - override suspend fun processActivity( - json: String, - type: ActivityType, - httpRequest: HttpRequest, - map: Map> - ) { - logger.debug("process activity: {}", type) - owlProducer.publishTask( - InboxTask( - json, - type, - httpRequest, - map - ) - ) - return - } -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/service/common/AbstractActivityPubProcessor.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/service/common/AbstractActivityPubProcessor.kt deleted file mode 100644 index f38d7cc1..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/service/common/AbstractActivityPubProcessor.kt +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Copyright (C) 2024 usbharu - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package dev.usbharu.hideout.activitypub.service.common - -import dev.usbharu.hideout.activitypub.domain.exception.ActivityPubProcessException -import dev.usbharu.hideout.activitypub.domain.exception.FailedProcessException -import dev.usbharu.hideout.activitypub.domain.exception.HttpSignatureUnauthorizedException -import dev.usbharu.hideout.activitypub.domain.model.objects.Object -import dev.usbharu.hideout.application.external.Transaction -import dev.usbharu.hideout.core.domain.exception.resource.ResourceAccessException -import org.slf4j.Logger -import org.slf4j.LoggerFactory -import java.sql.SQLException - -abstract class AbstractActivityPubProcessor( - private val transaction: Transaction, - private val allowUnauthorized: Boolean = false -) : ActivityPubProcessor { - protected val logger: Logger = LoggerFactory.getLogger(this::class.java) - - override suspend fun process(activity: ActivityPubProcessContext) { - if (activity.isAuthorized.not() && allowUnauthorized.not()) { - throw HttpSignatureUnauthorizedException() - } - logger.info("START ActivityPub process. {}", this.type()) - try { - transaction.transaction { - try { - internalProcess(activity) - } catch (e: ResourceAccessException) { - throw SQLException(e) - } - } - } catch (e: ActivityPubProcessException) { - logger.warn("FAILED ActivityPub process", e) - throw FailedProcessException("Failed process", e) - } - logger.info("SUCCESS ActivityPub process. {}", this.type()) - } - - abstract suspend fun internalProcess(activity: ActivityPubProcessContext) -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/service/common/ActivityPubProcessContext.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/service/common/ActivityPubProcessContext.kt deleted file mode 100644 index 50d07478..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/service/common/ActivityPubProcessContext.kt +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright (C) 2024 usbharu - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package dev.usbharu.hideout.activitypub.service.common - -import com.fasterxml.jackson.databind.JsonNode -import dev.usbharu.hideout.activitypub.domain.model.objects.Object -import dev.usbharu.httpsignature.common.HttpRequest -import dev.usbharu.httpsignature.verify.Signature - -data class ActivityPubProcessContext( - val activity: T, - val jsonNode: JsonNode, - val httpRequest: HttpRequest, - val signature: Signature?, - val isAuthorized: Boolean -) diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/service/common/ActivityPubProcessor.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/service/common/ActivityPubProcessor.kt deleted file mode 100644 index d558e798..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/service/common/ActivityPubProcessor.kt +++ /dev/null @@ -1,27 +0,0 @@ -/* - * Copyright (C) 2024 usbharu - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package dev.usbharu.hideout.activitypub.service.common - -import dev.usbharu.hideout.activitypub.domain.model.objects.Object - -interface ActivityPubProcessor { - suspend fun process(activity: ActivityPubProcessContext) - - fun isSupported(activityType: ActivityType): Boolean - - fun type(): Class -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/service/common/ActivityType.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/service/common/ActivityType.kt deleted file mode 100644 index acd1fcb6..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/service/common/ActivityType.kt +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright (C) 2024 usbharu - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package dev.usbharu.hideout.activitypub.service.common - -enum class ActivityType { - Accept, - Add, - Announce, - Arrive, - Block, - Create, - Delete, - Dislike, - Flag, - Follow, - Ignore, - Invite, - Join, - Leave, - Like, - Listen, - Move, - Offer, - Question, - Reject, - Read, - Remove, - TentativeReject, - TentativeAccept, - Travel, - Undo, - Update, - View, - Other -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/service/common/ActivityVocabulary.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/service/common/ActivityVocabulary.kt deleted file mode 100644 index 4d42dfff..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/service/common/ActivityVocabulary.kt +++ /dev/null @@ -1,74 +0,0 @@ -/* - * Copyright (C) 2024 usbharu - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package dev.usbharu.hideout.activitypub.service.common - -enum class ActivityVocabulary { - Object, - Link, - Activity, - IntransitiveActivity, - Collection, - OrderedCollection, - CollectionPage, - OrderedCollectionPage, - Accept, - Add, - Announce, - Arrive, - Block, - Create, - Delete, - Dislike, - Flag, - Follow, - Ignore, - Invite, - Join, - Leave, - Like, - Listen, - Move, - Offer, - Question, - Reject, - Read, - Remove, - TentativeReject, - TentativeAccept, - Travel, - Undo, - Update, - View, - Application, - Group, - Organization, - Person, - Service, - Article, - Audio, - Document, - Event, - Image, - Note, - Page, - Place, - Profile, - Relationship, - Tombstone, - Video, - Mention, -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/service/common/ExtendedActivityVocabulary.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/service/common/ExtendedActivityVocabulary.kt deleted file mode 100644 index b8150064..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/service/common/ExtendedActivityVocabulary.kt +++ /dev/null @@ -1,75 +0,0 @@ -/* - * Copyright (C) 2024 usbharu - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package dev.usbharu.hideout.activitypub.service.common - -enum class ExtendedActivityVocabulary { - Object, - Link, - Activity, - IntransitiveActivity, - Collection, - OrderedCollection, - CollectionPage, - OrderedCollectionPage, - Accept, - Add, - Announce, - Arrive, - Block, - Create, - Delete, - Dislike, - Flag, - Follow, - Ignore, - Invite, - Join, - Leave, - Like, - Listen, - Move, - Offer, - Question, - Reject, - Read, - Remove, - TentativeReject, - TentativeAccept, - Travel, - Undo, - Update, - View, - Application, - Group, - Organization, - Person, - Service, - Article, - Audio, - Document, - Event, - Image, - Note, - Page, - Place, - Profile, - Relationship, - Tombstone, - Video, - Mention, - Emoji -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/service/common/ExtendedVocabulary.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/service/common/ExtendedVocabulary.kt deleted file mode 100644 index ef1c06c6..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/service/common/ExtendedVocabulary.kt +++ /dev/null @@ -1,21 +0,0 @@ -/* - * Copyright (C) 2024 usbharu - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package dev.usbharu.hideout.activitypub.service.common - -enum class ExtendedVocabulary { - Emoji -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/service/objects/emoji/EmojiService.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/service/objects/emoji/EmojiService.kt deleted file mode 100644 index 7687dcef..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/service/objects/emoji/EmojiService.kt +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Copyright (C) 2024 usbharu - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package dev.usbharu.hideout.activitypub.service.objects.emoji - -import dev.usbharu.hideout.activitypub.domain.model.Emoji -import dev.usbharu.hideout.core.domain.model.emoji.CustomEmoji - -interface EmojiService { - suspend fun fetchEmoji(url: String): Pair - suspend fun fetchEmoji(emoji: Emoji): Pair - suspend fun findByEmojiName(emojiName: String): CustomEmoji? -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/service/objects/emoji/EmojiServiceImpl.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/service/objects/emoji/EmojiServiceImpl.kt deleted file mode 100644 index 61ba3904..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/service/objects/emoji/EmojiServiceImpl.kt +++ /dev/null @@ -1,95 +0,0 @@ -/* - * Copyright (C) 2024 usbharu - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package dev.usbharu.hideout.activitypub.service.objects.emoji - -import dev.usbharu.hideout.activitypub.domain.model.Emoji -import dev.usbharu.hideout.activitypub.service.common.APResourceResolveServiceImpl -import dev.usbharu.hideout.activitypub.service.common.resolve -import dev.usbharu.hideout.application.config.ApplicationConfig -import dev.usbharu.hideout.core.domain.model.emoji.CustomEmoji -import dev.usbharu.hideout.core.domain.model.emoji.CustomEmojiRepository -import dev.usbharu.hideout.core.service.instance.InstanceService -import dev.usbharu.hideout.core.service.media.MediaService -import dev.usbharu.hideout.core.service.media.RemoteMedia -import org.springframework.stereotype.Service -import java.net.URL -import java.time.Instant - -@Service -class EmojiServiceImpl( - private val customEmojiRepository: CustomEmojiRepository, - private val instanceService: InstanceService, - private val mediaService: MediaService, - private val apResourceResolveServiceImpl: APResourceResolveServiceImpl, - private val applicationConfig: ApplicationConfig -) : EmojiService { - override suspend fun fetchEmoji(url: String): Pair { - val emoji = apResourceResolveServiceImpl.resolve(url, null as Long?) - return fetchEmoji(emoji) - } - - override suspend fun fetchEmoji(emoji: Emoji): Pair = emoji to save(emoji) - - private suspend fun save(emoji: Emoji): CustomEmoji { - val domain = URL(emoji.id).host - val name = emoji.name.trim(':') - val customEmoji = customEmojiRepository.findByNameAndDomain(name, domain) - - if (customEmoji != null) { - return customEmoji - } - - val instance = instanceService.fetchInstance(emoji.id) - - val media = mediaService.uploadRemoteMedia( - RemoteMedia( - emoji.name, - emoji.icon.url, - emoji.icon.mediaType.orEmpty(), - null - ) - ) - - val customEmoji1 = CustomEmoji( - id = customEmojiRepository.generateId(), - name = name, - domain = domain, - instanceId = instance.id, - url = media.url, - category = null, - createdAt = Instant.now() - ) - - return customEmojiRepository.save(customEmoji1) - } - - override suspend fun findByEmojiName(emojiName: String): CustomEmoji? { - val split = emojiName.trim(':').split("@") - - return when (split.size) { - 1 -> { - customEmojiRepository.findByNameAndDomain(split.first(), applicationConfig.url.host) - } - - 2 -> { - customEmojiRepository.findByNameAndDomain(split.first(), split[1]) - } - - else -> throw IllegalArgumentException("Unknown Emoji Format. $emojiName") - } - } -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/service/objects/note/APNoteService.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/service/objects/note/APNoteService.kt deleted file mode 100644 index ea9358a3..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/service/objects/note/APNoteService.kt +++ /dev/null @@ -1,275 +0,0 @@ -/* - * Copyright (C) 2024 usbharu - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package dev.usbharu.hideout.activitypub.service.objects.note - -import dev.usbharu.hideout.activitypub.domain.exception.FailedToGetActivityPubResourceException -import dev.usbharu.hideout.activitypub.domain.model.Announce -import dev.usbharu.hideout.activitypub.domain.model.Emoji -import dev.usbharu.hideout.activitypub.domain.model.Note -import dev.usbharu.hideout.activitypub.domain.model.Person -import dev.usbharu.hideout.activitypub.query.AnnounceQueryService -import dev.usbharu.hideout.activitypub.query.NoteQueryService -import dev.usbharu.hideout.activitypub.service.common.APResourceResolveService -import dev.usbharu.hideout.activitypub.service.common.resolve -import dev.usbharu.hideout.activitypub.service.objects.emoji.EmojiService -import dev.usbharu.hideout.activitypub.service.objects.user.APUserService -import dev.usbharu.hideout.core.domain.model.actor.Actor -import dev.usbharu.hideout.core.domain.model.post.Post -import dev.usbharu.hideout.core.domain.model.post.PostRepository -import dev.usbharu.hideout.core.domain.model.post.Visibility -import dev.usbharu.hideout.core.service.media.MediaService -import dev.usbharu.hideout.core.service.media.RemoteMedia -import dev.usbharu.hideout.core.service.post.PostService -import io.ktor.client.plugins.* -import org.slf4j.LoggerFactory -import org.springframework.stereotype.Service -import java.time.Instant - -interface APNoteService { - suspend fun fetchNote(url: String, targetActor: String? = null): Note = fetchNoteWithEntity(url, targetActor).first - suspend fun fetchNote(note: Note, targetActor: String? = null): Note - suspend fun fetchNoteWithEntity(url: String, targetActor: String? = null): Pair - - suspend fun fetchAnnounce(url: String, signerId: Long? = null): Pair - suspend fun fetchAnnounce(announce: Announce, signerId: Long? = null): Pair -} - -@Service -@Suppress("LongParameterList") -class APNoteServiceImpl( - private val postRepository: PostRepository, - private val apUserService: APUserService, - private val postService: PostService, - private val apResourceResolveService: APResourceResolveService, - private val postBuilder: Post.PostBuilder, - private val noteQueryService: NoteQueryService, - private val mediaService: MediaService, - private val emojiService: EmojiService, - private val announceQueryService: AnnounceQueryService - -) : APNoteService { - - private val logger = LoggerFactory.getLogger(APNoteServiceImpl::class.java) - - override suspend fun fetchNoteWithEntity(url: String, targetActor: String?): Pair { - logger.debug("START Fetch Note url: {}", url) - - val post = noteQueryService.findByApid(url) - - if (post != null) { - logger.debug("SUCCESS Found in local url: {}", url) - return post - } - - logger.info("AP GET url: {}", url) - val note = try { - apResourceResolveService.resolve(url, null as Long?) - } catch (e: ClientRequestException) { - logger.warn( - "FAILED Failed to retrieve ActivityPub resource. HTTP Status Code: {} url: {}", - e.response.status, - url - ) - throw FailedToGetActivityPubResourceException("Could not retrieve $url.", e) - } - val savedNote = saveIfMissing(note, targetActor) - logger.debug("SUCCESS Fetch Note url: {}", url) - return savedNote - } - - override suspend fun fetchAnnounce(url: String, signerId: Long?): Pair { - logger.debug("START Fetch Announce url: {}", url) - - val post: Pair? = announceQueryService.findByApId(url) - - if (post != null) { - logger.debug("SUCCESS Found in local url: {}", url) - return post - } - - logger.info("AP GET url: {}", url) - - val announce = try { - apResourceResolveService.resolve(url, signerId) - } catch (e: ClientRequestException) { - logger.warn( - "FAILED Failed to retrieve ActivityPub resource. HTTP Status Code: {} url: {}", - e.response.status, - url - ) - throw FailedToGetActivityPubResourceException("Could not retrieve $url.", e) - } - - return fetchAnnounce(announce, signerId) - } - - override suspend fun fetchAnnounce(announce: Announce, signerId: Long?): Pair { - val findByApId = announceQueryService.findByApId(announce.id) - - if (findByApId != null) { - return findByApId - } - - val (_, actor) = apUserService.fetchPersonWithEntity(announce.actor, null) - - val (_, post) = fetchNoteWithEntity(announce.apObject, null) - - val visibility = if (announce.to.contains(public)) { - Visibility.PUBLIC - } else if (announce.to.contains(actor.followers) && announce.cc.contains(public)) { - Visibility.UNLISTED - } else if (announce.to.contains(actor.followers)) { - Visibility.FOLLOWERS - } else { - Visibility.DIRECT - } - - val createRemote = postService.createRemote( - postBuilder.pureRepostOf( - id = postRepository.generateId(), - actorId = actor.id, - visibility = visibility, - createdAt = Instant.parse(announce.published), - url = announce.id, - repost = post, - apId = announce.id - ) - ) - return announce to createRemote - } - - private suspend fun saveIfMissing( - note: Note, - targetActor: String? - ): Pair = noteQueryService.findByApid(note.id) ?: saveNote(note, targetActor) - - private suspend fun saveNote(note: Note, targetActor: String?): Pair { - val person = apUserService.fetchPersonWithEntity( - note.attributedTo, - targetActor - ) - - val post = postRepository.findByApId(note.id) - if (post != null) { - return note to post - } - - logger.debug("VISIBILITY url: {} to: {} cc: {}", note.id, note.to, note.cc) - val visibility = visibility(note, person) - logger.debug("VISIBILITY is {} url: {}", visibility.name, note.id) - - val reply = note.inReplyTo?.let { - fetchNote(it, targetActor) - postRepository.findByUrl(it) - } - - val quote = (note.misskeyQuote ?: note.quoteUri ?: note.quoteUrl)?.let { - fetchNote(it, targetActor) - postRepository.findByUrl(it) - } - - val emojis = buildEmojis(note) - - val mediaList = note.attachment.map { - mediaService.uploadRemoteMedia( - RemoteMedia( - it.name, - it.url, - it.mediaType, - description = it.name - ) - ) - }.map { it.id } - - val createPost = - post(quote, person, note, visibility, reply, mediaList, emojis) - - val createRemote = postService.createRemote(createPost) - return note to createRemote - } - - private suspend fun post( - quote: Post?, - person: Pair, - note: Note, - visibility: Visibility, - reply: Post?, - mediaList: List, - emojis: List - ) = if (quote != null) { - postBuilder.quoteRepostOf( - id = postRepository.generateId(), - actorId = person.second.id, - content = note.content, - createdAt = Instant.parse(note.published), - visibility = visibility, - url = note.id, - replyId = reply?.id, - sensitive = note.sensitive, - apId = note.id, - mediaIds = mediaList, - emojiIds = emojis, - repost = quote - ) - } else { - postBuilder.of( - id = postRepository.generateId(), - actorId = person.second.id, - content = note.content, - createdAt = Instant.parse(note.published).toEpochMilli(), - visibility = visibility, - url = note.id, - replyId = reply?.id, - sensitive = note.sensitive, - apId = note.id, - mediaIds = mediaList, - emojiIds = emojis - ) - } - - private suspend fun buildEmojis(note: Note) = note.tag - .filterIsInstance() - .map { - emojiService.fetchEmoji(it).second - } - .map { - it.id - } - - private fun visibility( - note: Note, - person: Pair - ): Visibility { - val visibility = if (note.to.contains(public)) { - Visibility.PUBLIC - } else if (note.to.contains(person.second.followers) && note.cc.contains(public)) { - Visibility.UNLISTED - } else if (note.to.contains(person.second.followers)) { - Visibility.FOLLOWERS - } else { - Visibility.DIRECT - } - return visibility - } - - override suspend fun fetchNote(note: Note, targetActor: String?): Note = - saveIfMissing(note, targetActor).first - - companion object { - const val public: String = "https://www.w3.org/ns/activitystreams#Public" - } -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/service/objects/note/NoteApApiService.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/service/objects/note/NoteApApiService.kt deleted file mode 100644 index 97b1355d..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/service/objects/note/NoteApApiService.kt +++ /dev/null @@ -1,23 +0,0 @@ -/* - * Copyright (C) 2024 usbharu - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package dev.usbharu.hideout.activitypub.service.objects.note - -import dev.usbharu.hideout.activitypub.domain.model.Note - -interface NoteApApiService { - suspend fun getNote(postId: Long, userId: Long?): Note? -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/service/objects/note/NoteApApiServiceImpl.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/service/objects/note/NoteApApiServiceImpl.kt deleted file mode 100644 index df9ba955..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/service/objects/note/NoteApApiServiceImpl.kt +++ /dev/null @@ -1,72 +0,0 @@ -/* - * Copyright (C) 2024 usbharu - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package dev.usbharu.hideout.activitypub.service.objects.note - -import dev.usbharu.hideout.activitypub.domain.model.Note -import dev.usbharu.hideout.activitypub.query.NoteQueryService -import dev.usbharu.hideout.application.external.Transaction -import dev.usbharu.hideout.core.domain.model.post.Post -import dev.usbharu.hideout.core.domain.model.post.Visibility -import dev.usbharu.hideout.core.query.FollowerQueryService -import org.slf4j.LoggerFactory -import org.springframework.stereotype.Service - -@Service -class NoteApApiServiceImpl( - private val noteQueryService: NoteQueryService, - private val followerQueryService: FollowerQueryService, - private val transaction: Transaction -) : NoteApApiService { - override suspend fun getNote(postId: Long, userId: Long?): Note? = transaction.transaction { - val findById = noteQueryService.findById(postId) - - if (findById == null) { - logger.warn("Note not found. $postId $userId") - return@transaction null - } - - when (findById.second.visibility) { - Visibility.PUBLIC, Visibility.UNLISTED -> { - return@transaction findById.first - } - - Visibility.FOLLOWERS -> { - return@transaction getFollowersNote(userId, findById) - } - - Visibility.DIRECT -> return@transaction null - } - } - - private suspend fun getFollowersNote( - userId: Long?, - findById: Pair - ): Note? { - if (userId == null) { - return null - } - - if (followerQueryService.alreadyFollow(findById.second.actorId, userId)) { - return findById.first - } - return null - } - - companion object { - private val logger = LoggerFactory.getLogger(NoteApApiServiceImpl::class.java) - } -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/service/objects/user/APUserService.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/service/objects/user/APUserService.kt deleted file mode 100644 index 336a4de5..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/service/objects/user/APUserService.kt +++ /dev/null @@ -1,166 +0,0 @@ -/* - * Copyright (C) 2024 usbharu - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package dev.usbharu.hideout.activitypub.service.objects.user - -import dev.usbharu.hideout.activitypub.domain.model.Image -import dev.usbharu.hideout.activitypub.domain.model.Key -import dev.usbharu.hideout.activitypub.domain.model.Person -import dev.usbharu.hideout.activitypub.service.common.APResourceResolveService -import dev.usbharu.hideout.activitypub.service.common.resolve -import dev.usbharu.hideout.application.config.ApplicationConfig -import dev.usbharu.hideout.application.external.Transaction -import dev.usbharu.hideout.core.domain.exception.resource.UserNotFoundException -import dev.usbharu.hideout.core.domain.model.actor.Actor -import dev.usbharu.hideout.core.domain.model.actor.ActorRepository -import dev.usbharu.hideout.core.service.user.RemoteUserCreateDto -import dev.usbharu.hideout.core.service.user.UserService -import org.springframework.stereotype.Service - -interface APUserService { - suspend fun getPersonByName(name: String): Person - - /** - * Fetch person - * - * @param url - * @param targetActor 署名するユーザー - * @return - */ - suspend fun fetchPerson(url: String, targetActor: String? = null, idOverride: Long? = null): Person - - suspend fun fetchPersonWithEntity( - url: String, - targetActor: String? = null, - idOverride: Long? = null, - ): Pair -} - -@Service -class APUserServiceImpl( - private val userService: UserService, - private val transaction: Transaction, - private val applicationConfig: ApplicationConfig, - private val apResourceResolveService: APResourceResolveService, - private val actorRepository: ActorRepository, -) : - APUserService { - - override suspend fun getPersonByName(name: String): Person { - val userEntity = transaction.transaction { - actorRepository.findByNameAndDomain(name, applicationConfig.url.host) - ?: throw UserNotFoundException.withNameAndDomain(name, applicationConfig.url.host) - } - // TODO: JOINで書き直し - val userUrl = "${applicationConfig.url}/users/$name" - return Person( - type = emptyList(), - name = userEntity.name, - id = userUrl, - preferredUsername = name, - summary = userEntity.description, - inbox = "$userUrl/inbox", - outbox = "$userUrl/outbox", - url = userUrl, - icon = Image( - type = emptyList(), - mediaType = "image/jpeg", - url = "$userUrl/icon.jpg" - ), - publicKey = Key( - id = userEntity.keyId, - owner = userUrl, - publicKeyPem = userEntity.publicKey - ), - endpoints = mapOf("sharedInbox" to "${applicationConfig.url}/inbox"), - followers = userEntity.followers, - following = userEntity.following, - manuallyApprovesFollowers = userEntity.locked - ) - } - - override suspend fun fetchPerson(url: String, targetActor: String?, idOverride: Long?): Person = - fetchPersonWithEntity(url, targetActor, idOverride).first - - override suspend fun fetchPersonWithEntity( - url: String, - targetActor: String?, - idOverride: Long?, - ): Pair { - val userEntity = actorRepository.findByUrl(url) - - if (userEntity != null && idOverride == null) { - return entityToPerson(userEntity, userEntity.url) to userEntity - } - - val person = apResourceResolveService.resolve(url, null as Long?) - - val id = person.id - - val actor = actorRepository.findByUrlWithLock(id) - - if (actor != null && idOverride == null) { - return person to actor - } - - return person to userService.createRemoteUser( - RemoteUserCreateDto( - name = person.preferredUsername, - domain = id.substringAfter("://").substringBefore("/"), - screenName = person.name ?: person.preferredUsername, - description = person.summary.orEmpty(), - inbox = person.inbox, - outbox = person.outbox, - url = id, - publicKey = person.publicKey.publicKeyPem, - keyId = person.publicKey.id, - following = person.following, - followers = person.followers, - sharedInbox = person.endpoints["sharedInbox"], - locked = person.manuallyApprovesFollowers - ), - idOverride - ) - } - - private fun entityToPerson( - actorEntity: Actor, - id: String, - ) = Person( - type = emptyList(), - name = actorEntity.name, - id = id, - preferredUsername = actorEntity.name, - summary = actorEntity.description, - inbox = "$id/inbox", - outbox = "$id/outbox", - url = id, - icon = Image( - type = emptyList(), - mediaType = "image/jpeg", - url = "$id/icon.jpg" - ), - publicKey = Key( - id = actorEntity.keyId, - owner = id, - publicKeyPem = actorEntity.publicKey - ), - endpoints = mapOf("sharedInbox" to "${applicationConfig.url}/inbox"), - followers = actorEntity.followers, - following = actorEntity.following, - manuallyApprovesFollowers = actorEntity.locked - ) -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/service/webfinger/WebFingerApiService.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/service/webfinger/WebFingerApiService.kt deleted file mode 100644 index 32a09a76..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/service/webfinger/WebFingerApiService.kt +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright (C) 2024 usbharu - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package dev.usbharu.hideout.activitypub.service.webfinger - -import dev.usbharu.hideout.application.external.Transaction -import dev.usbharu.hideout.core.domain.exception.resource.UserNotFoundException -import dev.usbharu.hideout.core.domain.model.actor.Actor -import dev.usbharu.hideout.core.domain.model.actor.ActorRepository -import org.springframework.stereotype.Service - -@Service -interface WebFingerApiService { - suspend fun findByNameAndDomain(name: String, domain: String): Actor -} - -@Service -class WebFingerApiServiceImpl( - private val transaction: Transaction, - private val actorRepository: ActorRepository -) : - WebFingerApiService { - override suspend fun findByNameAndDomain(name: String, domain: String): Actor { - return transaction.transaction { - actorRepository.findByNameAndDomain(name, domain) ?: throw UserNotFoundException.withNameAndDomain( - name, - domain - ) - } - } -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/application/config/SecurityConfig.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/application/config/SecurityConfig.kt index e34fc87c..d51ae754 100644 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/application/config/SecurityConfig.kt +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/application/config/SecurityConfig.kt @@ -26,7 +26,6 @@ import com.nimbusds.jose.proc.SecurityContext import dev.usbharu.hideout.activitypub.domain.model.StringORObjectSerializer import dev.usbharu.hideout.activitypub.domain.model.StringOrObject import dev.usbharu.hideout.application.external.Transaction -import dev.usbharu.hideout.core.domain.model.actor.ActorRepository import dev.usbharu.hideout.core.infrastructure.springframework.httpsignature.HttpSignatureFilter import dev.usbharu.hideout.core.infrastructure.springframework.httpsignature.HttpSignatureHeaderChecker import dev.usbharu.hideout.core.infrastructure.springframework.httpsignature.HttpSignatureUserDetailsService diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/event/relationship/RelationshipEvent.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/event/relationship/RelationshipEvent.kt new file mode 100644 index 00000000..4854475d --- /dev/null +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/event/relationship/RelationshipEvent.kt @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2024 usbharu + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package dev.usbharu.hideout.core.domain.event.relationship + +import dev.usbharu.hideout.core.domain.model.relationship.Relationship2 +import dev.usbharu.hideout.core.domain.shared.domainevent.DomainEvent +import dev.usbharu.hideout.core.domain.shared.domainevent.DomainEventBody + +class RelationshipEventFactory(private val relationship2: Relationship2) { + fun createEvent(relationshipEvent: RelationshipEvent): DomainEvent { + return DomainEvent.create(relationshipEvent.eventName, RelationshipEventBody(relationship2)) + } +} + +class RelationshipEventBody(relationship: Relationship2) : DomainEventBody(mapOf("relationship" to relationship)) + +enum class RelationshipEvent(val eventName: String) { + follow("RelationshipFollow"), + unfollow("RelationshipUnfollow"), + block("RelationshipBlock"), + unblock("RelationshipUnblock"), + mute("RelationshipMute"), + unmute("RelationshipUnmute"), + followRequest("RelationshipFollowRequest"), + unfollowRequest("RelationshipUnfollowRequest"), +} \ No newline at end of file diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/actor/Acct.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/actor/Acct.kt deleted file mode 100644 index c8861b42..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/actor/Acct.kt +++ /dev/null @@ -1,19 +0,0 @@ -/* - * Copyright (C) 2024 usbharu - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package dev.usbharu.hideout.core.domain.model.actor - -data class Acct(val username: String, val domain: String? = null, val isRemote: Boolean = domain == null) diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/actor/Actor.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/actor/Actor.kt deleted file mode 100644 index 7e747110..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/actor/Actor.kt +++ /dev/null @@ -1,269 +0,0 @@ -/* - * Copyright (C) 2024 usbharu - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package dev.usbharu.hideout.core.domain.model.actor - -import dev.usbharu.hideout.application.config.ApplicationConfig -import dev.usbharu.hideout.application.config.CharacterLimit -import jakarta.validation.Validator -import jakarta.validation.constraints.* -import org.hibernate.validator.constraints.URL -import org.slf4j.LoggerFactory -import org.springframework.stereotype.Component -import java.time.Instant -import kotlin.math.max - -@Deprecated("Actor2を使う") -data class Actor private constructor( - @get:NotNull - @get:Positive - val id: Long, - @get:Pattern(regexp = "^[a-zA-Z0-9_-]{1,300}\$") - @get:Size(min = 1) - val name: String, - val domain: String, - val screenName: String, - val description: String, - @get:URL - val inbox: String, - @get:URL - val outbox: String, - @get:URL - val url: String, - @get:NotBlank - val publicKey: String, - val privateKey: String? = null, - @get:PastOrPresent - val createdAt: Instant, - @get:NotBlank - val keyId: String, - val followers: String? = null, - val following: String? = null, - @get:PositiveOrZero - val instance: Long, - val locked: Boolean, - val followersCount: Int = 0, - val followingCount: Int = 0, - val postsCount: Int = 0, - val lastPostDate: Instant? = null, - val emojis: List = emptyList(), -) { - - @Component - class UserBuilder( - private val characterLimit: CharacterLimit, - private val applicationConfig: ApplicationConfig, - private val validator: Validator, - ) { - - private val logger = LoggerFactory.getLogger(UserBuilder::class.java) - - @Suppress("LongParameterList", "FunctionMinLength", "LongMethod") - fun of( - id: Long, - name: String, - domain: String, - screenName: String, - description: String, - inbox: String, - outbox: String, - url: String, - publicKey: String, - privateKey: String? = null, - createdAt: Instant, - keyId: String, - following: String? = null, - followers: String? = null, - instance: Long, - locked: Boolean, - followersCount: Int = 0, - followingCount: Int = 0, - postsCount: Int = 0, - lastPostDate: Instant? = null, - emojis: List = emptyList(), - ): Actor { - if (id == 0L) { - return Actor( - id = id, - name = name, - domain = domain, - screenName = screenName, - description = description, - inbox = inbox, - outbox = outbox, - url = url, - publicKey = publicKey, - privateKey = privateKey, - createdAt = createdAt, - keyId = keyId, - followers = followers, - following = following, - instance = instance, - locked = locked, - followersCount = followersCount, - followingCount = followingCount, - postsCount = postsCount, - lastPostDate = lastPostDate, - emojis = emojis - ) - } - - // idは0未満ではいけない - require(id >= 0) { "id must be greater than or equal to 0." } - - // nameは空文字以外を含める必要がある - require(name.isNotBlank()) { "name must contain non-blank characters." } - - // nameは指定された長さ以下である必要がある - val limitedName = if (name.length >= characterLimit.account.id) { - logger.warn("name must not exceed ${characterLimit.account.id} characters.") - name.substring(0, characterLimit.account.id) - } else { - name - } - - // domainは空文字以外を含める必要がある - require(domain.isNotBlank()) { "domain must contain non-blank characters." } - - // domainは指定された長さ以下である必要がある - require(domain.length <= characterLimit.general.domain) { - "domain must not exceed ${characterLimit.general.domain} characters." - } - - // screenNameは空文字以外を含める必要がある - require(screenName.isNotBlank()) { "screenName must contain non-blank characters." } - - // screenNameは指定された長さ以下である必要がある - val limitedScreenName = if (screenName.length >= characterLimit.account.name) { - logger.warn("screenName must not exceed ${characterLimit.account.name} characters.") - screenName.substring(0, characterLimit.account.name) - } else { - screenName - } - - // descriptionは指定された長さ以下である必要がある - val limitedDescription = if (description.length >= characterLimit.account.description) { - logger.warn("description must not exceed ${characterLimit.account.description} characters.") - description.substring(0, characterLimit.account.description) - } else { - description - } - - // ローカルユーザーはpasswordとprivateKeyをnullにしてはいけない - if (domain == applicationConfig.url.host) { - requireNotNull(privateKey) { "password and privateKey must not be null for local users." } - } - - // urlは空文字以外を含める必要がある - require(url.isNotBlank()) { "url must contain non-blank characters." } - - // urlは指定された長さ以下である必要がある - require(url.length <= characterLimit.general.url) { - "url must not exceed ${characterLimit.general.url} characters." - } - - // inboxは空文字以外を含める必要がある - require(inbox.isNotBlank()) { "inbox must contain non-blank characters." } - - // inboxは指定された長さ以下である必要がある - require(inbox.length <= characterLimit.general.url) { - "inbox must not exceed ${characterLimit.general.url} characters." - } - - // outboxは空文字以外を含める必要がある - require(outbox.isNotBlank()) { "outbox must contain non-blank characters." } - - // outboxは指定された長さ以下である必要がある - require(outbox.length <= characterLimit.general.url) { - "outbox must not exceed ${characterLimit.general.url} characters." - } - - require(keyId.isNotBlank()) { - "keyId must contain non-blank characters." - } - - val actor = Actor( - id = id, - name = limitedName, - domain = domain, - screenName = limitedScreenName, - description = limitedDescription, - inbox = inbox, - outbox = outbox, - url = url, - publicKey = publicKey, - privateKey = privateKey, - createdAt = createdAt, - keyId = keyId, - followers = followers, - following = following, - instance = instance, - locked = locked, - followersCount = max(0, followersCount), - followingCount = max(0, followingCount), - postsCount = max(0, postsCount), - lastPostDate = lastPostDate, - emojis = emojis - ) - - val validate = validator.validate(actor) - - for (constraintViolation in validate) { - throw IllegalArgumentException("${constraintViolation.propertyPath} : ${constraintViolation.message}") - } - return actor - } - } - - fun incrementFollowing(): Actor = this.copy(followingCount = this.followingCount + 1) - - fun decrementFollowing(): Actor = this.copy(followingCount = this.followingCount - 1) - - fun incrementFollowers(): Actor = this.copy(followersCount = this.followersCount + 1) - - fun decrementFollowers(): Actor = this.copy(followersCount = this.followersCount - 1) - - fun incrementPostsCount(): Actor = this.copy(postsCount = this.postsCount + 1) - - fun decrementPostsCount(): Actor = this.copy(postsCount = this.postsCount - 1) - - fun withLastPostAt(lastPostDate: Instant): Actor = this.copy(lastPostDate = lastPostDate) - override fun toString(): String { - return "Actor(" + - "id=$id, " + - "name='$name', " + - "domain='$domain', " + - "screenName='$screenName', " + - "description='$description', " + - "inbox='$inbox', " + - "outbox='$outbox', " + - "url='$url', " + - "publicKey='$publicKey', " + - "privateKey=$privateKey, " + - "createdAt=$createdAt, " + - "keyId='$keyId', " + - "followers=$followers, " + - "following=$following, " + - "instance=$instance, " + - "locked=$locked, " + - "followersCount=$followersCount, " + - "followingCount=$followingCount, " + - "postsCount=$postsCount, " + - "lastPostDate=$lastPostDate, " + - "emojis=$emojis" + - ")" - } -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/actor/ActorRepository.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/actor/ActorRepository.kt deleted file mode 100644 index 0123961c..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/actor/ActorRepository.kt +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright (C) 2024 usbharu - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package dev.usbharu.hideout.core.domain.model.actor - -import org.springframework.stereotype.Repository - -@Repository -@Suppress("TooManyFunctions") -interface ActorRepository { - suspend fun save(actor: Actor): Actor - - suspend fun findById(id: Long): Actor? - - suspend fun findByIdWithLock(id: Long): Actor? - - suspend fun findAll(limit: Int, offset: Long): List - - suspend fun findByName(name: String): List - - suspend fun findByNameAndDomain(name: String, domain: String): Actor? - - suspend fun findByNameAndDomainWithLock(name: String, domain: String): Actor? - - suspend fun findByUrl(url: String): Actor? - - suspend fun findByUrlWithLock(url: String): Actor? - - suspend fun findByIds(ids: List): List - - suspend fun findByKeyId(keyId: String): Actor? - - suspend fun delete(id: Long) - - suspend fun nextId(): Long -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/emoji/EmojiId.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/emoji/EmojiId.kt index f5d537c2..4e7e16d6 100644 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/emoji/EmojiId.kt +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/emoji/EmojiId.kt @@ -17,4 +17,4 @@ package dev.usbharu.hideout.core.domain.model.emoji @JvmInline -value class EmojiId(private val emojiId: Long) \ No newline at end of file +value class EmojiId(val emojiId: Long) \ No newline at end of file diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/notification/ExposedNotificationRepository.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/notification/ExposedNotificationRepository.kt deleted file mode 100644 index 56e8f33d..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/notification/ExposedNotificationRepository.kt +++ /dev/null @@ -1,102 +0,0 @@ -/* - * Copyright (C) 2024 usbharu - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package dev.usbharu.hideout.core.domain.model.notification - -import dev.usbharu.hideout.application.service.id.IdGenerateService -import dev.usbharu.hideout.core.infrastructure.exposedrepository.AbstractRepository -import dev.usbharu.hideout.core.infrastructure.exposedrepository.Actors -import dev.usbharu.hideout.core.infrastructure.exposedrepository.Posts -import dev.usbharu.hideout.core.infrastructure.exposedrepository.Reactions -import org.jetbrains.exposed.sql.* -import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq -import org.jetbrains.exposed.sql.javatime.timestamp -import org.slf4j.Logger -import org.slf4j.LoggerFactory -import org.springframework.stereotype.Repository - -@Repository -class ExposedNotificationRepository(private val idGenerateService: IdGenerateService) : NotificationRepository, - AbstractRepository() { - override val logger: Logger - get() = Companion.logger - - override suspend fun generateId(): Long = idGenerateService.generateId() - - override suspend fun save(notification: Notification): Notification = query { - val singleOrNull = - Notifications.selectAll().where { Notifications.id eq notification.id }.forUpdate().singleOrNull() - if (singleOrNull == null) { - Notifications.insert { - it[id] = notification.id - it[type] = notification.type - it[userId] = notification.userId - it[sourceActorId] = notification.sourceActorId - it[postId] = notification.postId - it[text] = notification.text - it[reactionId] = notification.reactionId - it[createdAt] = notification.createdAt - } - } else { - Notifications.update({ Notifications.id eq notification.id }) { - it[type] = notification.type - it[userId] = notification.userId - it[sourceActorId] = notification.sourceActorId - it[postId] = notification.postId - it[text] = notification.text - it[reactionId] = notification.reactionId - it[createdAt] = notification.createdAt - } - } - notification - } - - override suspend fun findById(id: Long): Notification? = query { - Notifications.selectAll().where { Notifications.id eq id }.singleOrNull()?.toNotifications() - } - - override suspend fun deleteById(id: Long) { - Notifications.deleteWhere { Notifications.id eq id } - } - - companion object { - private val logger = LoggerFactory.getLogger(ExposedNotificationRepository::class.java) - } -} - -fun ResultRow.toNotifications() = Notification( - id = this[Notifications.id], - type = this[Notifications.type], - userId = this[Notifications.userId], - sourceActorId = this[Notifications.sourceActorId], - postId = this[Notifications.postId], - text = this[Notifications.text], - reactionId = this[Notifications.reactionId], - createdAt = this[Notifications.createdAt], -) - -object Notifications : Table("notifications") { - val id = long("id") - val type = varchar("type", 100) - val userId = long("user_id").references(Actors.id) - val sourceActorId = long("source_actor_id").references(Actors.id).nullable() - val postId = long("post_id").references(Posts.id).nullable() - val text = varchar("text", 3000).nullable() - val reactionId = long("reaction_id").references(Reactions.id).nullable() - val createdAt = timestamp("created_at") - - override val primaryKey: PrimaryKey = PrimaryKey(id) -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/post/Post.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/post/Post.kt deleted file mode 100644 index 67dc170d..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/post/Post.kt +++ /dev/null @@ -1,220 +0,0 @@ -/* - * Copyright (C) 2024 usbharu - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package dev.usbharu.hideout.core.domain.model.post - -import dev.usbharu.hideout.application.config.CharacterLimit -import dev.usbharu.hideout.core.service.post.PostContentFormatter -import jakarta.validation.Validator -import jakarta.validation.constraints.Positive -import org.hibernate.validator.constraints.URL -import org.springframework.stereotype.Component -import java.time.Instant - -@Deprecated("Post2を使う") -data class Post private constructor( - @get:Positive - val id: Long, - @get:Positive - val actorId: Long, - val overview: String? = null, - val content: String, - val text: String, - @get:Positive - val createdAt: Long, - val visibility: Visibility, - @get:URL - val url: String, - val repostId: Long? = null, - val replyId: Long? = null, - val sensitive: Boolean = false, - @get:URL - val apId: String = url, - val mediaIds: List = emptyList(), - val deleted: Boolean = false, - val emojiIds: List = emptyList(), -) { - - @Component - class PostBuilder( - private val characterLimit: CharacterLimit, - private val postContentFormatter: PostContentFormatter, - private val validator: Validator, - ) { - @Suppress("FunctionMinLength", "LongParameterList") - fun of( - id: Long, - actorId: Long, - overview: String? = null, - content: String, - createdAt: Long, - visibility: Visibility, - url: String, - repostId: Long? = null, - replyId: Long? = null, - sensitive: Boolean = false, - apId: String = url, - mediaIds: List = emptyList(), - emojiIds: List = emptyList(), - deleted: Boolean = false, - ): Post { - require(id >= 0) { "id must be greater than or equal to 0." } - - require(actorId >= 0) { "actorId must be greater than or equal to 0." } - - val limitedOverview = if ((overview?.length ?: 0) >= characterLimit.post.overview) { - overview?.substring(0, characterLimit.post.overview) - } else { - overview - } - - val limitedText = if (content.length >= characterLimit.post.text) { - content.substring(0, characterLimit.post.text) - } else { - content - } - - val (html, content1) = postContentFormatter.format(limitedText) - - require(url.isNotBlank()) { "url must contain non-blank characters" } - require(url.length <= characterLimit.general.url) { - "url must not exceed ${characterLimit.general.url} characters." - } - - require((repostId ?: 0) >= 0) { "repostId must be greater then or equal to 0." } - require((replyId ?: 0) >= 0) { "replyId must be greater then or equal to 0." } - - val post = Post( - id = id, - actorId = actorId, - overview = limitedOverview, - content = html, - text = content1, - createdAt = createdAt, - visibility = visibility, - url = url, - repostId = repostId, - replyId = replyId, - sensitive = sensitive, - apId = apId, - mediaIds = mediaIds, - deleted = deleted, - emojiIds = emojiIds - ) - - val validate = validator.validate(post) - - for (constraintViolation in validate) { - throw IllegalArgumentException("${constraintViolation.propertyPath} : ${constraintViolation.message}") - } - - if (post.deleted) { - return post.delete() - } - - return post - } - - @Suppress("LongParameterList") - fun pureRepostOf( - id: Long, - actorId: Long, - visibility: Visibility, - createdAt: Instant, - url: String, - repost: Post, - apId: String, - ): Post { - // リポストの公開範囲は元のポストより広くてはいけない - val fixedVisibility = if (visibility.ordinal <= repost.visibility.ordinal) { - repost.visibility - } else { - visibility - } - - val post = of( - id = id, - actorId = actorId, - overview = null, - content = "", - createdAt = createdAt.toEpochMilli(), - visibility = fixedVisibility, - url = url, - repostId = repost.id, - replyId = null, - sensitive = false, - apId = apId, - mediaIds = emptyList(), - deleted = false, - emojiIds = emptyList() - ) - return post - } - - @Suppress("LongParameterList") - fun quoteRepostOf( - id: Long, - actorId: Long, - overview: String? = null, - content: String, - createdAt: Instant, - visibility: Visibility, - url: String, - repost: Post, - replyId: Long? = null, - sensitive: Boolean = false, - apId: String = url, - mediaIds: List = emptyList(), - emojiIds: List = emptyList(), - ): Post { - // リポストの公開範囲は元のポストより広くてはいけない - val fixedVisibility = if (visibility.ordinal <= repost.visibility.ordinal) { - repost.visibility - } else { - visibility - } - - val post = of( - id = id, - actorId = actorId, - overview = overview, - content = content, - createdAt = createdAt.toEpochMilli(), - visibility = fixedVisibility, - url = url, - repostId = repost.id, - replyId = replyId, - sensitive = sensitive, - apId = apId, - mediaIds = mediaIds, - deleted = false, - emojiIds = emojiIds - ) - return post - } - } - - fun isPureRepost(): Boolean = - this.text.isEmpty() && - this.content.isEmpty() && - this.overview == null && - this.replyId == null && - this.repostId != null - - fun delete(): Post = copy(deleted = true) - - fun restore(): Post = copy(deleted = false) -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/post/Post2Repository.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/post/Post2Repository.kt index f2bffd9a..26bdb9f0 100644 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/post/Post2Repository.kt +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/post/Post2Repository.kt @@ -23,5 +23,5 @@ interface Post2Repository { suspend fun saveAll(posts: List): List suspend fun findById(id: PostId): Post2? suspend fun findByActorId(id: ActorId): List - suspend fun deleteById(id: PostId) + suspend fun delete(post: Post2) } \ No newline at end of file diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/post/PostContent.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/post/PostContent.kt index 2ed4df88..ee9e4e08 100644 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/post/PostContent.kt +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/post/PostContent.kt @@ -22,6 +22,8 @@ class PostContent private constructor(val text: String, val content: String, val companion object { val empty = PostContent("", "", emptyList()) + val contentLength = 5000 + val textLength = 3000 } abstract class PostContentFactory { diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/post/PostOverview.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/post/PostOverview.kt index 995329b1..1c2419bb 100644 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/post/PostOverview.kt +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/post/PostOverview.kt @@ -17,4 +17,8 @@ package dev.usbharu.hideout.core.domain.model.post @JvmInline -value class PostOverview(val overview: String) +value class PostOverview(val overview: String) { + companion object { + val length = 100 + } +} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/post/PostRepository.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/post/PostRepository.kt deleted file mode 100644 index 561911f9..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/post/PostRepository.kt +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright (C) 2024 usbharu - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package dev.usbharu.hideout.core.domain.model.post - -import org.springframework.stereotype.Repository - -@Suppress("LongParameterList", "TooManyFunctions") -@Repository -interface PostRepository { - suspend fun generateId(): Long - suspend fun save(post: Post): Post - suspend fun saveAll(posts: List) - suspend fun delete(id: Long) - suspend fun findById(id: Long): Post? - suspend fun findByUrl(url: String): Post? - - suspend fun findByApId(apId: String): Post? - suspend fun existByApIdWithLock(apId: String): Boolean - suspend fun findByActorId(actorId: Long): List - suspend fun findByActorIdAndDeleted(actorId: Long, deleted: Boolean): List - - suspend fun countByActorId(actorId: Long): Int -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/relationship/Relationship2.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/relationship/Relationship2.kt new file mode 100644 index 00000000..5b735964 --- /dev/null +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/relationship/Relationship2.kt @@ -0,0 +1,106 @@ +/* + * Copyright (C) 2024 usbharu + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package dev.usbharu.hideout.core.domain.model.relationship + +import dev.usbharu.hideout.core.domain.event.relationship.RelationshipEvent +import dev.usbharu.hideout.core.domain.event.relationship.RelationshipEventFactory +import dev.usbharu.hideout.core.domain.model.actor.ActorId +import dev.usbharu.hideout.core.domain.shared.domainevent.DomainEventStorable + +class Relationship2( + val actorId: ActorId, + val targetActorId: ActorId, + following: Boolean, + blocking: Boolean, + muting: Boolean, + followRequesting: Boolean, + mutingFollowRequest: Boolean, +) : DomainEventStorable() { + + var following: Boolean = following + private set + var blocking: Boolean = blocking + private set + + var muting: Boolean = muting + private set + var followRequesting: Boolean = followRequesting + private set + var mutingFollowRequest: Boolean = mutingFollowRequest + private set + + + fun follow() { + require(blocking.not()) + following = true + addDomainEvent(RelationshipEventFactory(this).createEvent(RelationshipEvent.follow)) + } + + fun unfollow() { + following = false + addDomainEvent(RelationshipEventFactory(this).createEvent(RelationshipEvent.unfollow)) + } + + fun block() { + require(following.not()) + blocking = true + addDomainEvent(RelationshipEventFactory(this).createEvent(RelationshipEvent.block)) + } + + fun unblock() { + blocking = false + addDomainEvent(RelationshipEventFactory(this).createEvent(RelationshipEvent.unblock)) + } + + fun mute() { + muting = true + addDomainEvent(RelationshipEventFactory(this).createEvent(RelationshipEvent.mute)) + } + + fun unmute() { + muting = false + addDomainEvent(RelationshipEventFactory(this).createEvent(RelationshipEvent.unmute)) + } + + fun muteFollowRequest() { + mutingFollowRequest = true + } + + fun unmuteFollowRequest() { + mutingFollowRequest = false + } + + fun followRequest() { + require(blocking.not()) + followRequesting = true + addDomainEvent(RelationshipEventFactory(this).createEvent(RelationshipEvent.followRequest)) + } + + fun unfollowRequest() { + followRequesting = false + addDomainEvent(RelationshipEventFactory(this).createEvent(RelationshipEvent.unfollowRequest)) + } + + fun acceptFollowRequest() { + follow() + followRequesting = false + } + + fun rejectFollowRequest() { + followRequesting = false + } +} \ No newline at end of file diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/domain/model/HasName.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/relationship/Relationship2Repository.kt similarity index 73% rename from hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/domain/model/HasName.kt rename to hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/relationship/Relationship2Repository.kt index 14abb53e..c04e6106 100644 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/activitypub/domain/model/HasName.kt +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/relationship/Relationship2Repository.kt @@ -14,8 +14,9 @@ * limitations under the License. */ -package dev.usbharu.hideout.activitypub.domain.model +package dev.usbharu.hideout.core.domain.model.relationship -interface HasName { - val name: String -} +interface Relationship2Repository { + suspend fun save(relationship2: Relationship2): Relationship2 + suspend fun delete(relationship2: Relationship2) +} \ No newline at end of file diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/relationship/RelationshipRepository.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/relationship/RelationshipRepository.kt deleted file mode 100644 index 4780d30d..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/relationship/RelationshipRepository.kt +++ /dev/null @@ -1,72 +0,0 @@ -/* - * Copyright (C) 2024 usbharu - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package dev.usbharu.hideout.core.domain.model.relationship - -import dev.usbharu.hideout.application.infrastructure.exposed.Page -import dev.usbharu.hideout.application.infrastructure.exposed.PaginationList - -/** - * [Relationship]の永続化 - * - */ -interface RelationshipRepository { - /** - * 永続化します - * - * @param relationship 永続化する[Relationship] - * @return 永続化された[Relationship] - */ - suspend fun save(relationship: Relationship): Relationship - - /** - * 永続化されたものを削除します - * - * @param relationship 削除する[Relationship] - */ - suspend fun delete(relationship: Relationship) - - /** - * userIdとtargetUserIdで[Relationship]を取得します - * - * @param actorId 取得するユーザーID - * @param targetActorId 対象ユーザーID - * @return 取得された[Relationship] 存在しない場合nullが返ります - */ - suspend fun findByUserIdAndTargetUserId(actorId: Long, targetActorId: Long): Relationship? - - suspend fun deleteByActorIdOrTargetActorId(actorId: Long, targetActorId: Long) - - suspend fun findByTargetIdAndFollowing(targetId: Long, following: Boolean): List - - suspend fun countByTargetIdAndFollowing(targetId: Long, following: Boolean): Int - - suspend fun countByUserIdAndFollowing(userId: Long, following: Boolean): Int - - @Suppress("FunctionMaxLength") - suspend fun findByTargetIdAndFollowRequestAndIgnoreFollowRequest( - targetId: Long, - followRequest: Boolean, - ignoreFollowRequest: Boolean, - page: Page.PageByMaxId, - ): PaginationList - - suspend fun findByActorIdAndMuting( - actorId: Long, - muting: Boolean, - page: Page.PageByMaxId, - ): PaginationList -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/relationship/RelationshipRepositoryImpl.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/relationship/RelationshipRepositoryImpl.kt index 481c3dd6..5e6b7886 100644 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/relationship/RelationshipRepositoryImpl.kt +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/relationship/RelationshipRepositoryImpl.kt @@ -20,7 +20,6 @@ import dev.usbharu.hideout.application.infrastructure.exposed.Page import dev.usbharu.hideout.application.infrastructure.exposed.PaginationList import dev.usbharu.hideout.application.infrastructure.exposed.withPagination import dev.usbharu.hideout.core.infrastructure.exposedrepository.AbstractRepository -import dev.usbharu.hideout.core.infrastructure.exposedrepository.Actors import org.jetbrains.exposed.dao.id.LongIdTable import org.jetbrains.exposed.sql.* import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/shared/domainevent/DomainEventStorable.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/shared/domainevent/DomainEventStorable.kt deleted file mode 100644 index a0da8d06..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/shared/domainevent/DomainEventStorable.kt +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Copyright (C) 2024 usbharu - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package dev.usbharu.hideout.core.domain.model.shared.domainevent - -abstract class DomainEventStorable { - private val domainEvents: MutableList = mutableListOf() - - protected fun addDomainEvent(domainEvent: DomainEvent) { - domainEvents.add(domainEvent) - } - - fun clearDomainEvents() = domainEvents.clear() - - fun getDomainEvents(): List = domainEvents.toList() -} \ No newline at end of file diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposed/PostQueryMapper.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposed/PostQueryMapper.kt deleted file mode 100644 index c9598fb1..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposed/PostQueryMapper.kt +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright (C) 2024 usbharu - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package dev.usbharu.hideout.core.infrastructure.exposed - -import dev.usbharu.hideout.application.infrastructure.exposed.QueryMapper -import dev.usbharu.hideout.application.infrastructure.exposed.ResultRowMapper -import dev.usbharu.hideout.core.domain.model.post.Post -import dev.usbharu.hideout.core.infrastructure.exposedrepository.Posts -import dev.usbharu.hideout.core.infrastructure.exposedrepository.PostsEmojis -import dev.usbharu.hideout.core.infrastructure.exposedrepository.PostsMedia -import org.jetbrains.exposed.sql.Query -import org.springframework.stereotype.Component - -@Component -class PostQueryMapper(private val postResultRowMapper: ResultRowMapper) : QueryMapper { - override fun map(query: Query): List { - return query.groupBy { it[Posts.id] } - .map { it.value } - .map { - it.first().let(postResultRowMapper::map) - .copy( - mediaIds = it.mapNotNull { resultRow -> resultRow.getOrNull(PostsMedia.mediaId) }, - emojiIds = it.mapNotNull { resultRow -> resultRow.getOrNull(PostsEmojis.emojiId) } - ) - } - } -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposed/PostResultRowMapper.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposed/PostResultRowMapper.kt deleted file mode 100644 index b18690e3..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposed/PostResultRowMapper.kt +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright (C) 2024 usbharu - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package dev.usbharu.hideout.core.infrastructure.exposed - -import dev.usbharu.hideout.application.infrastructure.exposed.ResultRowMapper -import dev.usbharu.hideout.core.domain.model.post.Post -import dev.usbharu.hideout.core.domain.model.post.Visibility -import dev.usbharu.hideout.core.infrastructure.exposedrepository.Posts -import org.jetbrains.exposed.sql.ResultRow -import org.springframework.stereotype.Component - -@Component -class PostResultRowMapper(private val postBuilder: Post.PostBuilder) : ResultRowMapper { - override fun map(resultRow: ResultRow): Post { - return postBuilder.of( - id = resultRow[Posts.id], - actorId = resultRow[Posts.actorId], - overview = resultRow[Posts.overview], - content = resultRow[Posts.text], - createdAt = resultRow[Posts.createdAt], - visibility = Visibility.values().first { visibility -> visibility.ordinal == resultRow[Posts.visibility] }, - url = resultRow[Posts.url], - repostId = resultRow[Posts.repostId], - replyId = resultRow[Posts.replyId], - sensitive = resultRow[Posts.sensitive], - apId = resultRow[Posts.apId], - deleted = resultRow[Posts.deleted], - ) - } -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposed/UserQueryMapper.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposed/UserQueryMapper.kt deleted file mode 100644 index 2c976d4c..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposed/UserQueryMapper.kt +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright (C) 2024 usbharu - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package dev.usbharu.hideout.core.infrastructure.exposed - -import dev.usbharu.hideout.application.infrastructure.exposed.QueryMapper -import dev.usbharu.hideout.application.infrastructure.exposed.ResultRowMapper -import dev.usbharu.hideout.core.domain.model.actor.Actor -import org.jetbrains.exposed.sql.Query -import org.springframework.stereotype.Component - -@Component -class UserQueryMapper(private val actorResultRowMapper: ResultRowMapper) : QueryMapper { - override fun map(query: Query): List = query.map(actorResultRowMapper::map) -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposed/UserResultRowMapper.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposed/UserResultRowMapper.kt deleted file mode 100644 index 2f6a02d2..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposed/UserResultRowMapper.kt +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright (C) 2024 usbharu - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package dev.usbharu.hideout.core.infrastructure.exposed - -import dev.usbharu.hideout.application.infrastructure.exposed.ResultRowMapper -import dev.usbharu.hideout.core.domain.model.actor.Actor -import dev.usbharu.hideout.core.infrastructure.exposedrepository.Actors -import org.jetbrains.exposed.sql.ResultRow -import org.springframework.stereotype.Component -import java.time.Instant - -@Component -class UserResultRowMapper(private val actorBuilder: Actor.UserBuilder) : ResultRowMapper { - override fun map(resultRow: ResultRow): Actor { - return actorBuilder.of( - id = resultRow[Actors.id], - name = resultRow[Actors.name], - domain = resultRow[Actors.domain], - screenName = resultRow[Actors.screenName], - description = resultRow[Actors.description], - inbox = resultRow[Actors.inbox], - outbox = resultRow[Actors.outbox], - url = resultRow[Actors.url], - publicKey = resultRow[Actors.publicKey], - privateKey = resultRow[Actors.privateKey], - createdAt = Instant.ofEpochMilli((resultRow[Actors.createdAt])), - keyId = resultRow[Actors.keyId], - followers = resultRow[Actors.followers], - following = resultRow[Actors.following], - instance = resultRow[Actors.instance], - locked = resultRow[Actors.locked], - followingCount = resultRow[Actors.followingCount], - followersCount = resultRow[Actors.followersCount], - postsCount = resultRow[Actors.postsCount], - lastPostDate = resultRow[Actors.lastPostAt], - emojis = resultRow[Actors.emojis].split(",").filter { it.isNotEmpty() }.map { it.toLong() } - ) - } -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposedquery/FollowerQueryServiceImpl.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposedquery/FollowerQueryServiceImpl.kt deleted file mode 100644 index 770ad559..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposedquery/FollowerQueryServiceImpl.kt +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright (C) 2024 usbharu - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package dev.usbharu.hideout.core.infrastructure.exposedquery - -import dev.usbharu.hideout.core.domain.model.actor.Actor -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 org.springframework.stereotype.Repository - -@Repository -class FollowerQueryServiceImpl( - private val relationshipRepository: RelationshipRepository, - private val actorRepository: ActorRepository -) : FollowerQueryService { - override suspend fun findFollowersById(id: Long): List { - return actorRepository.findByIds( - relationshipRepository.findByTargetIdAndFollowing(id, true).map { it.actorId } - ) - } - - override suspend fun alreadyFollow(actorId: Long, followerId: Long): Boolean = - relationshipRepository.findByUserIdAndTargetUserId(followerId, actorId)?.following ?: false -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposedrepository/ActorRepositoryImpl.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposedrepository/ActorRepositoryImpl.kt deleted file mode 100644 index 08bc5de4..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposedrepository/ActorRepositoryImpl.kt +++ /dev/null @@ -1,182 +0,0 @@ -/* - * Copyright (C) 2024 usbharu - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package dev.usbharu.hideout.core.infrastructure.exposedrepository - -import dev.usbharu.hideout.application.infrastructure.exposed.QueryMapper -import dev.usbharu.hideout.application.infrastructure.exposed.ResultRowMapper -import dev.usbharu.hideout.application.service.id.IdGenerateService -import dev.usbharu.hideout.core.domain.model.actor.Actor -import dev.usbharu.hideout.core.domain.model.actor.ActorRepository -import org.jetbrains.exposed.sql.* -import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq -import org.jetbrains.exposed.sql.javatime.timestamp -import org.slf4j.Logger -import org.slf4j.LoggerFactory -import org.springframework.stereotype.Repository - -@Repository -class ActorRepositoryImpl( - private val idGenerateService: IdGenerateService, - private val actorResultRowMapper: ResultRowMapper, - private val actorQueryMapper: QueryMapper -) : ActorRepository, AbstractRepository() { - override val logger: Logger - get() = Companion.logger - - override suspend fun save(actor: Actor): Actor = query { - val singleOrNull = Actors.selectAll().where { Actors.id eq actor.id }.forUpdate().empty() - if (singleOrNull) { - Actors.insert { - it[id] = actor.id - it[name] = actor.name - it[domain] = actor.domain - it[screenName] = actor.screenName - it[description] = actor.description - it[inbox] = actor.inbox - it[outbox] = actor.outbox - it[url] = actor.url - it[createdAt] = actor.createdAt.toEpochMilli() - it[publicKey] = actor.publicKey - it[privateKey] = actor.privateKey - it[keyId] = actor.keyId - it[following] = actor.following - it[followers] = actor.followers - it[instance] = actor.instance - it[locked] = actor.locked - it[followersCount] = actor.followersCount - it[followingCount] = actor.followingCount - it[postsCount] = actor.postsCount - it[lastPostAt] = actor.lastPostDate - it[emojis] = actor.emojis.joinToString(",") - } - } else { - Actors.update({ Actors.id eq actor.id }) { - it[name] = actor.name - it[domain] = actor.domain - it[screenName] = actor.screenName - it[description] = actor.description - it[inbox] = actor.inbox - it[outbox] = actor.outbox - it[url] = actor.url - it[createdAt] = actor.createdAt.toEpochMilli() - it[publicKey] = actor.publicKey - it[privateKey] = actor.privateKey - it[keyId] = actor.keyId - it[following] = actor.following - it[followers] = actor.followers - it[instance] = actor.instance - it[locked] = actor.locked - it[followersCount] = actor.followersCount - it[followingCount] = actor.followingCount - it[postsCount] = actor.postsCount - it[lastPostAt] = actor.lastPostDate - it[emojis] = actor.emojis.joinToString(",") - } - } - return@query actor - } - - override suspend fun findById(id: Long): Actor? = query { - return@query Actors.selectAll().where { Actors.id eq id }.singleOrNull()?.let(actorResultRowMapper::map) - } - - override suspend fun findByIdWithLock(id: Long): Actor? = query { - return@query Actors.selectAll().where { Actors.id eq id }.forUpdate().singleOrNull() - ?.let(actorResultRowMapper::map) - } - - override suspend fun findAll(limit: Int, offset: Long): List = query { - return@query Actors.selectAll().limit(limit, offset).let(actorQueryMapper::map) - } - - override suspend fun findByName(name: String): List = query { - return@query Actors.selectAll().where { Actors.name eq name }.let(actorQueryMapper::map) - } - - override suspend fun findByNameAndDomain(name: String, domain: String): Actor? = query { - return@query Actors.selectAll().where { Actors.name eq name and (Actors.domain eq domain) }.singleOrNull() - ?.let(actorResultRowMapper::map) - } - - override suspend fun findByNameAndDomainWithLock(name: String, domain: String): Actor? = query { - return@query Actors.selectAll().where { Actors.name eq name and (Actors.domain eq domain) }.forUpdate() - .singleOrNull() - ?.let(actorResultRowMapper::map) - } - - override suspend fun findByUrl(url: String): Actor? = query { - return@query Actors.selectAll().where { Actors.url eq url }.singleOrNull()?.let(actorResultRowMapper::map) - } - - override suspend fun findByUrlWithLock(url: String): Actor? = query { - return@query Actors.selectAll().where { Actors.url eq url }.forUpdate().singleOrNull() - ?.let(actorResultRowMapper::map) - } - - override suspend fun findByIds(ids: List): List = query { - return@query Actors.selectAll().where { Actors.id inList ids }.let(actorQueryMapper::map) - } - - override suspend fun findByKeyId(keyId: String): Actor? = query { - return@query Actors.selectAll().where { Actors.keyId eq keyId }.singleOrNull()?.let(actorResultRowMapper::map) - } - - override suspend fun delete(id: Long): Unit = query { - Actors.deleteWhere { Actors.id.eq(id) } - } - - override suspend fun nextId(): Long = idGenerateService.generateId() - - companion object { - private val logger = LoggerFactory.getLogger(ActorRepositoryImpl::class.java) - } -} - -object Actors : Table("actors") { - val id: Column = long("id") - val name: Column = varchar("name", length = 300) - val domain: Column = varchar("domain", length = 1000) - val screenName: Column = varchar("screen_name", length = 300) - val description: Column = varchar( - "description", - length = 10000 - ) - val inbox: Column = varchar("inbox", length = 1000).uniqueIndex() - val outbox: Column = varchar("outbox", length = 1000).uniqueIndex() - val url: Column = varchar("url", length = 1000).uniqueIndex() - val publicKey: Column = varchar("public_key", length = 10000) - val privateKey: Column = varchar( - "private_key", - length = 10000 - ).nullable() - val createdAt: Column = long("created_at") - val keyId = varchar("key_id", length = 1000) - val following = varchar("following", length = 1000).nullable() - val followers = varchar("followers", length = 1000).nullable() - val instance = long("instance").references(Instance.id) - val locked = bool("locked") - val followingCount = integer("following_count") - val followersCount = integer("followers_count") - val postsCount = integer("posts_count") - val lastPostAt = timestamp("last_post_at").nullable() - val emojis = varchar("emojis", 3000) - override val primaryKey: PrimaryKey = PrimaryKey(id) - - init { - uniqueIndex(name, domain) - } -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposedrepository/ExposedPost2Repository.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposedrepository/ExposedPost2Repository.kt new file mode 100644 index 00000000..9abb2bbe --- /dev/null +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposedrepository/ExposedPost2Repository.kt @@ -0,0 +1,175 @@ +/* + * Copyright (C) 2024 usbharu + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package dev.usbharu.hideout.core.infrastructure.exposedrepository + +import dev.usbharu.hideout.core.domain.model.actor.ActorId +import dev.usbharu.hideout.core.domain.model.post.* +import dev.usbharu.hideout.core.domain.shared.domainevent.DomainEventPublisher +import dev.usbharu.hideout.core.domain.shared.repository.DomainEventPublishableRepository +import dev.usbharu.hideout.core.infrastructure.exposedrepository.Posts2.actorId +import dev.usbharu.hideout.core.infrastructure.exposedrepository.Posts2.apId +import dev.usbharu.hideout.core.infrastructure.exposedrepository.Posts2.content +import dev.usbharu.hideout.core.infrastructure.exposedrepository.Posts2.createdAt +import dev.usbharu.hideout.core.infrastructure.exposedrepository.Posts2.deleted +import dev.usbharu.hideout.core.infrastructure.exposedrepository.Posts2.hide +import dev.usbharu.hideout.core.infrastructure.exposedrepository.Posts2.id +import dev.usbharu.hideout.core.infrastructure.exposedrepository.Posts2.moveTo +import dev.usbharu.hideout.core.infrastructure.exposedrepository.Posts2.overview +import dev.usbharu.hideout.core.infrastructure.exposedrepository.Posts2.replyId +import dev.usbharu.hideout.core.infrastructure.exposedrepository.Posts2.repostId +import dev.usbharu.hideout.core.infrastructure.exposedrepository.Posts2.sensitive +import dev.usbharu.hideout.core.infrastructure.exposedrepository.Posts2.text +import dev.usbharu.hideout.core.infrastructure.exposedrepository.Posts2.url +import dev.usbharu.hideout.core.infrastructure.exposedrepository.Posts2.visibility +import org.jetbrains.exposed.sql.* +import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq +import org.jetbrains.exposed.sql.javatime.timestamp +import org.slf4j.Logger +import org.slf4j.LoggerFactory +import org.springframework.stereotype.Repository + +@Repository +class ExposedPost2Repository(override val domainEventPublisher: DomainEventPublisher) : Post2Repository, + AbstractRepository(), DomainEventPublishableRepository { + override suspend fun save(post: Post2): Post2 { + query { + Posts2.upsert { + it[id] = post.id.id + it[actorId] = post.actorId.id + it[overview] = post.overview?.overview + it[content] = post.content.content + it[text] = post.content.text + it[createdAt] = post.createdAt + it[visibility] = post.visibility.name + it[url] = post.url.toString() + it[repostId] = post.repostId?.id + it[replyId] = post.replyId?.id + it[sensitive] = post.sensitive + it[apId] = post.apId.toString() + it[deleted] = post.deleted + it[hide] = post.hide + it[moveTo] = post.moveTo?.id + } + PostsMedia.deleteWhere { + postId eq post.id.id + } + PostsEmojis.deleteWhere { + postId eq post.id.id + } + PostsMedia.batchInsert(post.mediaIds) { + this[PostsMedia.postId] = post.id.id + this[PostsMedia.mediaId] = it.id + } + PostsEmojis.batchInsert(post.emojiIds) { + this[PostsEmojis.postId] = post.id.id + this[PostsEmojis.emojiId] = it.emojiId + } + } + update(post) + return post + } + + override suspend fun saveAll(posts: List): List { + query { + Posts2.batchUpsert(posts, id) { + this[id] = it.id.id + this[actorId] = it.actorId.id + this[overview] = it.overview?.overview + this[content] = it.content.content + this[text] = it.content.text + this[createdAt] = it.createdAt + this[visibility] = it.visibility.name + this[url] = it.url.toString() + this[repostId] = it.repostId?.id + this[replyId] = it.replyId?.id + this[sensitive] = it.sensitive + this[apId] = it.apId.toString() + this[deleted] = it.deleted + this[hide] = it.hide + this[moveTo] = it.moveTo?.id + } + val mediaIds = posts.flatMap { post -> post.mediaIds.map { post.id.id to it.id } } + PostsMedia.batchUpsert(mediaIds, PostsMedia.postId) { + this[PostsMedia.postId] = it.first + this[PostsMedia.mediaId] = it.second + } + val emojiIds = posts.flatMap { post -> post.emojiIds.map { post.id.id to it.emojiId } } + PostsEmojis.batchUpsert(emojiIds, PostsEmojis.postId) { + this[PostsEmojis.postId] = it.first + this[PostsEmojis.emojiId] = it.second + } + } + posts.forEach { + update(it) + } + return posts + } + + override suspend fun findById(id: PostId): Post2? { + TODO("Not yet implemented") + } + + override suspend fun findByActorId(id: ActorId): List { + TODO("Not yet implemented") + } + + override suspend fun delete(post: Post2) { + query { + Posts2.deleteWhere { + id eq post.id.id + } + } + update(post) + } + + override val logger: Logger = Companion.logger + + companion object { + private val logger = LoggerFactory.getLogger(ExposedPost2Repository::class.java) + } +} + +object Posts2 : Table("posts") { + val id = long("id") + val actorId = long("actor_id").references(Actors2.id) + val overview = varchar("overview", PostOverview.length).nullable() + val content = varchar("content", PostContent.contentLength) + val text = varchar("text", PostContent.textLength) + val createdAt = timestamp("created_at") + val visibility = varchar("visibility", 100) + val url = varchar("url", 1000) + val repostId = long("repost_id").references(id).nullable() + val replyId = long("reply_id").references(id).nullable() + val sensitive = bool("sensitive") + val apId = varchar("ap_id", 1000) + val deleted = bool("deleted") + val hide = bool("hide") + val moveTo = long("move_to").references(id).nullable() + +} + +object PostsMedia : Table("posts_media") { + val postId = long("post_id").references(id, ReferenceOption.CASCADE, ReferenceOption.CASCADE) + val mediaId = long("media_id").references(Media.id, ReferenceOption.CASCADE, ReferenceOption.CASCADE) + override val primaryKey = PrimaryKey(postId, mediaId) +} + +object PostsEmojis : Table("posts_emojis") { + val postId = long("post_id").references(id) + val emojiId = long("emoji_id").references(CustomEmojis.id) + override val primaryKey: PrimaryKey = PrimaryKey(postId, emojiId) +} \ No newline at end of file diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposedrepository/PostRepositoryImpl.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposedrepository/PostRepositoryImpl.kt deleted file mode 100644 index 96180244..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposedrepository/PostRepositoryImpl.kt +++ /dev/null @@ -1,232 +0,0 @@ -/* - * Copyright (C) 2024 usbharu - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package dev.usbharu.hideout.core.infrastructure.exposedrepository - -import dev.usbharu.hideout.application.infrastructure.exposed.QueryMapper -import dev.usbharu.hideout.application.service.id.IdGenerateService -import dev.usbharu.hideout.core.domain.model.post.Post -import dev.usbharu.hideout.core.domain.model.post.PostRepository -import dev.usbharu.hideout.core.infrastructure.exposedrepository.Posts.actorId -import dev.usbharu.hideout.core.infrastructure.exposedrepository.Posts.apId -import dev.usbharu.hideout.core.infrastructure.exposedrepository.Posts.content -import dev.usbharu.hideout.core.infrastructure.exposedrepository.Posts.createdAt -import dev.usbharu.hideout.core.infrastructure.exposedrepository.Posts.deleted -import dev.usbharu.hideout.core.infrastructure.exposedrepository.Posts.id -import dev.usbharu.hideout.core.infrastructure.exposedrepository.Posts.overview -import dev.usbharu.hideout.core.infrastructure.exposedrepository.Posts.replyId -import dev.usbharu.hideout.core.infrastructure.exposedrepository.Posts.repostId -import dev.usbharu.hideout.core.infrastructure.exposedrepository.Posts.sensitive -import dev.usbharu.hideout.core.infrastructure.exposedrepository.Posts.text -import dev.usbharu.hideout.core.infrastructure.exposedrepository.Posts.url -import dev.usbharu.hideout.core.infrastructure.exposedrepository.Posts.visibility -import org.jetbrains.exposed.sql.* -import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq -import org.slf4j.Logger -import org.slf4j.LoggerFactory -import org.springframework.stereotype.Repository - -@Repository -class PostRepositoryImpl( - private val idGenerateService: IdGenerateService, - private val postQueryMapper: QueryMapper, -) : PostRepository, AbstractRepository() { - override val logger: Logger - get() = Companion.logger - - override suspend fun generateId(): Long = idGenerateService.generateId() - - override suspend fun save(post: Post): Post = query { - val singleOrNull = Posts.selectAll().where { id eq post.id }.forUpdate().singleOrNull() - if (singleOrNull == null) { - Posts.insert { - it[id] = post.id - it[actorId] = post.actorId - it[overview] = post.overview - it[content] = post.content - it[text] = post.text - it[createdAt] = post.createdAt - it[visibility] = post.visibility.ordinal - it[url] = post.url - it[repostId] = post.repostId - it[replyId] = post.replyId - it[sensitive] = post.sensitive - it[apId] = post.apId - it[deleted] = post.deleted - } - PostsMedia.batchInsert(post.mediaIds) { - this[PostsMedia.postId] = post.id - this[PostsMedia.mediaId] = it - } - PostsEmojis.batchInsert(post.emojiIds) { - this[PostsEmojis.postId] = post.id - this[PostsEmojis.emojiId] = it - } - } else { - PostsMedia.deleteWhere { - postId eq post.id - } - PostsEmojis.deleteWhere { - postId eq post.id - } - PostsMedia.batchInsert(post.mediaIds) { - this[PostsMedia.postId] = post.id - this[PostsMedia.mediaId] = it - } - PostsEmojis.batchInsert(post.emojiIds) { - this[PostsEmojis.postId] = post.id - this[PostsEmojis.emojiId] = it - } - Posts.update({ id eq post.id }) { - it[actorId] = post.actorId - it[overview] = post.overview - it[content] = post.content - it[text] = post.text - it[createdAt] = post.createdAt - it[visibility] = post.visibility.ordinal - it[url] = post.url - it[repostId] = post.repostId - it[replyId] = post.replyId - it[sensitive] = post.sensitive - it[apId] = post.apId - it[deleted] = post.deleted - } - } - return@query post - } - - override suspend fun saveAll(posts: List) { - Posts.batchUpsert( - posts, - id, - ) { - this[id] = it.id - this[actorId] = it.actorId - this[overview] = it.overview - this[content] = it.content - this[text] = it.text - this[createdAt] = it.createdAt - this[visibility] = it.visibility.ordinal - this[url] = it.url - this[repostId] = it.repostId - this[replyId] = it.replyId - this[sensitive] = it.sensitive - this[apId] = it.apId - this[deleted] = it.deleted - } - val mediaIds = posts.flatMap { post -> post.mediaIds.map { post.id to it } } - PostsMedia.batchUpsert( - mediaIds, - PostsMedia.postId - ) { - this[PostsMedia.postId] = it.first - this[PostsMedia.mediaId] = it.second - } - - val emojiIds = posts.flatMap { post -> post.emojiIds.map { post.id to it } } - PostsEmojis.batchUpsert(emojiIds, PostsEmojis.postId) { - this[PostsEmojis.postId] = it.first - this[PostsEmojis.emojiId] = it.second - } - } - - override suspend fun findById(id: Long): Post? = query { - return@query Posts - .leftJoin(PostsMedia) - .leftJoin(PostsEmojis) - .selectAll().where { Posts.id eq id } - .let(postQueryMapper::map) - .singleOrNull() - } - - override suspend fun findByUrl(url: String): Post? = query { - return@query Posts - .leftJoin(PostsMedia) - .leftJoin(PostsEmojis) - .selectAll().where { Posts.url eq url } - .let(postQueryMapper::map) - .singleOrNull() - } - - override suspend fun findByApId(apId: String): Post? = query { - return@query Posts - .leftJoin(PostsMedia) - .leftJoin(PostsEmojis) - .selectAll().where { Posts.apId eq apId } - .let(postQueryMapper::map) - .singleOrNull() - } - - override suspend fun existByApIdWithLock(apId: String): Boolean = query { - return@query Posts.selectAll().where { Posts.apId eq apId }.forUpdate().empty().not() - } - - override suspend fun findByActorId(actorId: Long): List = query { - return@query Posts - .leftJoin(PostsMedia) - .leftJoin(PostsEmojis) - .selectAll().where { Posts.actorId eq actorId }.let(postQueryMapper::map) - } - - override suspend fun findByActorIdAndDeleted(actorId: Long, deleted: Boolean): List { - TODO("Not yet implemented") - } - - override suspend fun countByActorId(actorId: Long): Int = query { - return@query Posts - .selectAll() - .where { Posts.actorId eq actorId } - .count() - .toInt() - } - - override suspend fun delete(id: Long): Unit = query { - Posts.deleteWhere { Posts.id eq id } - } - - companion object { - private val logger = LoggerFactory.getLogger(PostRepositoryImpl::class.java) - } -} - -object Posts : Table() { - val id: Column = long("id") - val actorId: Column = long("actor_id").references(Actors.id) - val overview: Column = varchar("overview", 100).nullable() - val content = varchar("content", 5000) - val text: Column = varchar("text", 3000) - val createdAt: Column = long("created_at") - val visibility: Column = integer("visibility").default(0) - val url: Column = varchar("url", 500) - val repostId: Column = long("repost_id").references(id).nullable() - val replyId: Column = long("reply_id").references(id).nullable() - val sensitive: Column = bool("sensitive").default(false) - val apId: Column = varchar("ap_id", 100).uniqueIndex() - val deleted = bool("deleted").default(false) - override val primaryKey: PrimaryKey = PrimaryKey(id) -} - -object PostsMedia : Table("posts_media") { - val postId = long("post_id").references(id, ReferenceOption.CASCADE, ReferenceOption.CASCADE) - val mediaId = long("media_id").references(Media.id, ReferenceOption.CASCADE, ReferenceOption.CASCADE) - override val primaryKey = PrimaryKey(postId, mediaId) -} - -object PostsEmojis : Table("posts_emojis") { - val postId = long("post_id").references(id) - val emojiId = long("emoji_id").references(CustomEmojis.id) - override val primaryKey: PrimaryKey = PrimaryKey(postId, emojiId) -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/springframework/httpsignature/HttpSignatureUserDetailsService.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/springframework/httpsignature/HttpSignatureUserDetailsService.kt index 6ec16ddd..578da4ad 100644 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/springframework/httpsignature/HttpSignatureUserDetailsService.kt +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/springframework/httpsignature/HttpSignatureUserDetailsService.kt @@ -18,7 +18,6 @@ package dev.usbharu.hideout.core.infrastructure.springframework.httpsignature import dev.usbharu.hideout.application.external.Transaction import dev.usbharu.hideout.core.domain.exception.HttpSignatureVerifyException -import dev.usbharu.hideout.core.domain.model.actor.ActorRepository import dev.usbharu.hideout.util.RsaUtil import dev.usbharu.httpsignature.common.HttpMethod import dev.usbharu.httpsignature.common.HttpRequest diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/springframework/oauth2/UserDetailsServiceImpl.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/springframework/oauth2/UserDetailsServiceImpl.kt index 4a91c24a..6481007b 100644 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/springframework/oauth2/UserDetailsServiceImpl.kt +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/springframework/oauth2/UserDetailsServiceImpl.kt @@ -19,7 +19,6 @@ package dev.usbharu.hideout.core.infrastructure.springframework.oauth2 import dev.usbharu.hideout.application.config.ApplicationConfig import dev.usbharu.hideout.application.external.Transaction import dev.usbharu.hideout.core.domain.exception.resource.UserNotFoundException -import dev.usbharu.hideout.core.domain.model.actor.ActorRepository import dev.usbharu.hideout.core.domain.model.userdetails.UserDetailRepository import kotlinx.coroutines.runBlocking import org.springframework.security.core.userdetails.UserDetails diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/interfaces/api/auth/AuthController.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/interfaces/api/auth/AuthController.kt index 5951928c..bd61adac 100644 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/interfaces/api/auth/AuthController.kt +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/interfaces/api/auth/AuthController.kt @@ -18,7 +18,6 @@ package dev.usbharu.hideout.core.interfaces.api.auth import dev.usbharu.hideout.application.config.ApplicationConfig import dev.usbharu.hideout.application.config.CaptchaConfig -import dev.usbharu.hideout.core.service.auth.AuthApiService import dev.usbharu.hideout.core.service.auth.RegisterAccountDto import org.springframework.stereotype.Controller import org.springframework.ui.Model diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/query/FollowerQueryService.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/query/FollowerQueryService.kt deleted file mode 100644 index 64b2f8c2..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/query/FollowerQueryService.kt +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (C) 2024 usbharu - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package dev.usbharu.hideout.core.query - -import dev.usbharu.hideout.core.domain.model.actor.Actor - -@Deprecated("Use RelationshipQueryService") -interface FollowerQueryService { - suspend fun findFollowersById(id: Long): List - suspend fun alreadyFollow(actorId: Long, followerId: Long): Boolean -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/service/auth/AuthApiService.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/service/auth/AuthApiService.kt deleted file mode 100644 index 130ef8a8..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/service/auth/AuthApiService.kt +++ /dev/null @@ -1,23 +0,0 @@ -/* - * Copyright (C) 2024 usbharu - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package dev.usbharu.hideout.core.service.auth - -import dev.usbharu.hideout.core.domain.model.actor.Actor - -interface AuthApiService { - suspend fun registerAccount(registerAccountDto: RegisterAccountDto): Actor -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/service/auth/AuthApiServiceImpl.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/service/auth/AuthApiServiceImpl.kt deleted file mode 100644 index b1a2c6ed..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/service/auth/AuthApiServiceImpl.kt +++ /dev/null @@ -1,67 +0,0 @@ -/* - * Copyright (C) 2024 usbharu - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package dev.usbharu.hideout.core.service.auth - -import com.fasterxml.jackson.databind.ObjectMapper -import com.fasterxml.jackson.module.kotlin.readValue -import dev.usbharu.hideout.application.config.CaptchaConfig -import dev.usbharu.hideout.core.domain.model.actor.Actor -import dev.usbharu.hideout.core.service.user.UserCreateDto -import dev.usbharu.hideout.core.service.user.UserService -import io.ktor.client.* -import io.ktor.client.request.* -import io.ktor.client.statement.* -import org.slf4j.LoggerFactory -import org.springframework.stereotype.Service - -@Service -class AuthApiServiceImpl( - private val httpClient: HttpClient, - private val captchaConfig: CaptchaConfig, - private val objectMapper: ObjectMapper, - private val userService: UserService, -) : - AuthApiService { - override suspend fun registerAccount(registerAccountDto: RegisterAccountDto): Actor { - if (captchaConfig.reCaptchaSecretKey != null && captchaConfig.reCaptchaSiteKey != null) { - val get = - httpClient.get( - "https://www.google.com/recaptcha/api/siteverify?secret=" + - captchaConfig.reCaptchaSecretKey + "&response=" + registerAccountDto.recaptchaResponse - ) - val recaptchaResult = objectMapper.readValue(get.bodyAsText()) - logger.debug("reCAPTCHA: {}", recaptchaResult) - require(recaptchaResult.success) - require(!(recaptchaResult.score < 0.5)) - } - - val createLocalUser = userService.createLocalUser( - UserCreateDto( - registerAccountDto.username, - registerAccountDto.username, - "", - registerAccountDto.password - ) - ) - - return createLocalUser - } - - companion object { - private val logger = LoggerFactory.getLogger(AuthApiServiceImpl::class.java) - } -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/service/filter/MuteProcessService.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/service/filter/MuteProcessService.kt deleted file mode 100644 index 8bba0722..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/service/filter/MuteProcessService.kt +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright (C) 2024 usbharu - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package dev.usbharu.hideout.core.service.filter - -import dev.usbharu.hideout.core.domain.model.filter.FilterType -import dev.usbharu.hideout.core.domain.model.post.Post -import dev.usbharu.hideout.core.query.model.FilterQueryModel - -interface MuteProcessService { - suspend fun processMute(post: Post, context: List, filters: List): FilterResult? - suspend fun processMutes( - posts: List, - context: List, - filters: List - ): Map -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/service/filter/MuteProcessServiceImpl.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/service/filter/MuteProcessServiceImpl.kt deleted file mode 100644 index 491b3985..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/service/filter/MuteProcessServiceImpl.kt +++ /dev/null @@ -1,139 +0,0 @@ -/* - * Copyright (C) 2024 usbharu - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package dev.usbharu.hideout.core.service.filter - -import dev.usbharu.hideout.core.domain.model.filter.FilterMode.* -import dev.usbharu.hideout.core.domain.model.filter.FilterType -import dev.usbharu.hideout.core.domain.model.post.Post -import dev.usbharu.hideout.core.query.model.FilterQueryModel -import org.slf4j.LoggerFactory -import org.springframework.stereotype.Service - -@Service -class MuteProcessServiceImpl : MuteProcessService { - override suspend fun processMute( - post: Post, - context: List, - filters: List - ): FilterResult? { - val preprocess = preprocess(context, filters) - - return processMute(post, preprocess) - } - - private suspend fun processMute( - post: Post, - preprocess: List - ): FilterResult? { - logger.trace("process mute post: {}", post) - if (post.overview != null) { - val processMute = processMute(post.overview, preprocess) - - if (processMute != null) { - return processMute - } - } - - val processMute = processMute(post.text, preprocess) - - if (processMute != null) { - return processMute - } - - return null - } - - override suspend fun processMutes( - posts: List, - context: List, - filters: List - ): Map { - val preprocess = preprocess(context, filters) - - return posts.mapNotNull { it to (processMute(it, preprocess) ?: return@mapNotNull null) }.toMap() - } - - private suspend fun processMute(string: String, filters: List): FilterResult? { - for (filter in filters) { - val matchEntire = filter.regex.find(string) - - if (matchEntire != null) { - return FilterResult(filter.filter, matchEntire.value) - } - } - - return null - } - - private fun preprocess(context: List, filters: List): List { - val filterQueryModelList = filters - .filter { it.context.any(context::contains) } - .map { - PreProcessedFilter( - it, - precompileRegex(it) - ) - } - - return filterQueryModelList - } - - private fun precompileRegex(filter: FilterQueryModel): Regex { - logger.trace("precompile regex. filter: {}", filter) - - val regexList = mutableListOf() - - val noneRegexStrings = mutableListOf() - val wholeRegexStrings = mutableListOf() - - for (keyword in filter.keywords) { - when (keyword.mode) { - WHOLE_WORD -> wholeRegexStrings.add(keyword.keyword) - REGEX -> regexList.add(Regex(keyword.keyword)) - NONE -> noneRegexStrings.add(keyword.keyword) - } - } - - val noneRegex = noneRegexStrings.joinToString("|", "(", ")") - val wholeRegex = wholeRegexStrings.joinToString("|", "\\b(", ")\\b") - - val regex = if (noneRegexStrings.isNotEmpty() && wholeRegexStrings.isNotEmpty()) { - Regex("$noneRegex|$wholeRegex") - } else if (noneRegexStrings.isNotEmpty()) { - noneRegex.toRegex() - } else if (wholeRegexStrings.isNotEmpty()) { - wholeRegex.toRegex() - } else { - null - } - - if (regex != null) { - regexList.add(regex) - } - - val pattern = regexList.joinToString(")|(", "(", ")") - logger.trace("precompiled regex {}", pattern) - - return Regex(pattern) - } - - data class PreProcessedFilter(val filter: FilterQueryModel, val regex: Regex) - - companion object { - private val logger = LoggerFactory.getLogger(MuteProcessServiceImpl::class.java) - } -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/service/follow/SendFollowDto.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/service/follow/SendFollowDto.kt deleted file mode 100644 index 2e85a805..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/service/follow/SendFollowDto.kt +++ /dev/null @@ -1,21 +0,0 @@ -/* - * Copyright (C) 2024 usbharu - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package dev.usbharu.hideout.core.service.follow - -import dev.usbharu.hideout.core.domain.model.actor.Actor - -data class SendFollowDto(val actorId: Actor, val followTargetActorId: Actor) diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/service/notification/NotificationServiceImpl.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/service/notification/NotificationServiceImpl.kt index 37cc9fb4..189ac6fc 100644 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/service/notification/NotificationServiceImpl.kt +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/service/notification/NotificationServiceImpl.kt @@ -17,12 +17,9 @@ package dev.usbharu.hideout.core.service.notification import dev.usbharu.hideout.application.config.ApplicationConfig -import dev.usbharu.hideout.core.domain.model.actor.ActorRepository import dev.usbharu.hideout.core.domain.model.notification.Notification import dev.usbharu.hideout.core.domain.model.notification.NotificationRepository -import dev.usbharu.hideout.core.domain.model.post.PostRepository import dev.usbharu.hideout.core.domain.model.reaction.ReactionRepository -import dev.usbharu.hideout.core.domain.model.relationship.RelationshipRepository import org.slf4j.LoggerFactory import org.springframework.stereotype.Service import java.time.Instant diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/service/notification/NotificationStore.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/service/notification/NotificationStore.kt deleted file mode 100644 index 3f03f549..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/service/notification/NotificationStore.kt +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright (C) 2024 usbharu - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package dev.usbharu.hideout.core.service.notification - -import dev.usbharu.hideout.core.domain.model.actor.Actor -import dev.usbharu.hideout.core.domain.model.notification.Notification -import dev.usbharu.hideout.core.domain.model.post.Post -import dev.usbharu.hideout.core.domain.model.reaction.Reaction - -interface NotificationStore { - suspend fun publishNotification( - notification: Notification, - user: Actor, - sourceActor: Actor?, - post: Post?, - reaction: Reaction? - ): Boolean - - suspend fun unpulishNotification(notificationId: Long): Boolean -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/service/post/PostService.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/service/post/PostService.kt deleted file mode 100644 index 1a177666..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/service/post/PostService.kt +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright (C) 2024 usbharu - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package dev.usbharu.hideout.core.service.post - -import dev.usbharu.hideout.core.domain.model.post.Post -import org.springframework.stereotype.Service - -@Service -interface PostService { - suspend fun createLocal(post: PostCreateDto): Post - suspend fun createRemote(post: Post): Post - suspend fun deleteLocal(post: Post) - suspend fun deleteRemote(post: Post) - suspend fun deleteByActor(actorId: Long) - suspend fun restoreByRemoteActor(actorId: Long) -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/service/post/PostServiceImpl.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/service/post/PostServiceImpl.kt deleted file mode 100644 index d1d7a136..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/service/post/PostServiceImpl.kt +++ /dev/null @@ -1,138 +0,0 @@ -/* - * Copyright (C) 2024 usbharu - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package dev.usbharu.hideout.core.service.post - -import dev.usbharu.hideout.activitypub.service.activity.create.ApSendCreateService -import dev.usbharu.hideout.activitypub.service.activity.delete.APSendDeleteService -import dev.usbharu.hideout.core.domain.exception.resource.DuplicateException -import dev.usbharu.hideout.core.domain.exception.resource.PostNotFoundException -import dev.usbharu.hideout.core.domain.exception.resource.UserNotFoundException -import dev.usbharu.hideout.core.domain.model.actor.ActorRepository -import dev.usbharu.hideout.core.domain.model.post.Post -import dev.usbharu.hideout.core.domain.model.post.PostRepository -import dev.usbharu.hideout.core.domain.model.reaction.ReactionRepository -import dev.usbharu.hideout.core.service.timeline.TimelineService -import org.slf4j.LoggerFactory -import org.springframework.stereotype.Service -import java.time.Instant - -@Service -class PostServiceImpl( - private val postRepository: PostRepository, - private val actorRepository: ActorRepository, - private val timelineService: TimelineService, - private val postBuilder: Post.PostBuilder, - private val apSendCreateService: ApSendCreateService, - private val reactionRepository: ReactionRepository, - private val apSendDeleteService: APSendDeleteService, -) : PostService { - - override suspend fun createLocal(post: PostCreateDto): Post { - logger.info("START Create Local Post user: {}, media: {}", post.userId, post.mediaIds.size) - val create = internalCreate(post, true) - apSendCreateService.createNote(create) - logger.info("SUCCESS Create Local Post url: {}", create.url) - return create - } - - override suspend fun createRemote(post: Post): Post { - logger.info("START Create Remote Post user: {}, remote url: {}", post.actorId, post.apId) - val actor = - actorRepository.findById(post.actorId) ?: throw UserNotFoundException.withId(post.actorId) - val createdPost = internalCreate(post, false) - logger.info("SUCCESS Create Remote Post url: {}", createdPost.url) - return createdPost - } - - override suspend fun deleteLocal(post: Post) { - if (post.deleted) { - return - } - reactionRepository.deleteByPostId(post.id) - postRepository.save(post.delete()) - val actor = actorRepository.findById(post.actorId) - ?: throw IllegalStateException("actor: ${post.actorId} was not found.") - - apSendDeleteService.sendDeleteNote(post) - - actorRepository.save(actor.decrementPostsCount()) - } - - override suspend fun deleteRemote(post: Post) { - if (post.deleted) { - return - } - reactionRepository.deleteByPostId(post.id) - postRepository.save(post.delete()) - - val actor = actorRepository.findById(post.actorId) - ?: throw IllegalStateException("actor: ${post.actorId} was not found.") - - actorRepository.save(actor.decrementPostsCount()) - } - - override suspend fun deleteByActor(actorId: Long) { - val actor = actorRepository.findById(actorId) - ?: throw IllegalStateException("actor: $actorId was not found.") - - postRepository.findByActorId(actorId).filterNot { it.deleted }.forEach { postRepository.save(it.delete()) } - - actorRepository.save(actor.copy(postsCount = 0, lastPostDate = null)) - } - - override suspend fun restoreByRemoteActor(actorId: Long) { - val actor = actorRepository.findById(actorId) ?: throw UserNotFoundException.withId(actorId) - - val postList = postRepository.findByActorIdAndDeleted(actorId, true).map { it.restore() } - - postRepository.saveAll(postList) - - actorRepository.save(actor.copy(postsCount = actor.postsCount.plus(postList.size))) - } - - private suspend fun internalCreate(post: Post, isLocal: Boolean): Post { - return try { - val save = postRepository.save(post) - timelineService.publishTimeline(post, isLocal) - save - } catch (_: DuplicateException) { - postRepository.findByApId(post.apId) ?: throw PostNotFoundException.withApId(post.apId) - } - } - - private suspend fun internalCreate(post: PostCreateDto, isLocal: Boolean): Post { - val user = actorRepository.findById(post.userId) ?: throw UserNotFoundException("${post.userId} was not found") - val id = postRepository.generateId() - val createPost = postBuilder.of( - id = id, - actorId = post.userId, - overview = post.overview, - content = post.text, - createdAt = Instant.now().toEpochMilli(), - visibility = post.visibility, - url = "${user.url}/posts/$id", - mediaIds = post.mediaIds, - replyId = post.repolyId, - repostId = post.repostId, - ) - return internalCreate(createPost, isLocal) - } - - companion object { - private val logger = LoggerFactory.getLogger(PostServiceImpl::class.java) - } -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/service/reaction/ReactionServiceImpl.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/service/reaction/ReactionServiceImpl.kt index a2ccd6aa..26fe8d61 100644 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/service/reaction/ReactionServiceImpl.kt +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/service/reaction/ReactionServiceImpl.kt @@ -19,7 +19,6 @@ package dev.usbharu.hideout.core.service.reaction import dev.usbharu.hideout.activitypub.service.activity.like.APReactionService import dev.usbharu.hideout.core.domain.exception.resource.DuplicateException import dev.usbharu.hideout.core.domain.model.emoji.Emoji -import dev.usbharu.hideout.core.domain.model.post.PostRepository import dev.usbharu.hideout.core.domain.model.reaction.Reaction import dev.usbharu.hideout.core.domain.model.reaction.ReactionRepository import dev.usbharu.hideout.core.service.notification.NotificationService diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/service/relationship/RelationshipService.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/service/relationship/RelationshipService.kt deleted file mode 100644 index 513ade8c..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/service/relationship/RelationshipService.kt +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright (C) 2024 usbharu - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package dev.usbharu.hideout.core.service.relationship - -interface RelationshipService { - suspend fun followRequest(actorId: Long, targetId: Long) - suspend fun block(actorId: Long, targetId: Long) - - /** - * フォローリクエストを承認します - * [actorId]が[targetId]からのフォローリクエストを承認します - * - * @param actorId 承認操作をするユーザー - * @param targetId 承認するフォローリクエストを送ってきたユーザー - * @param force 強制的にAcceptアクティビティを発行する - */ - suspend fun acceptFollowRequest(actorId: Long, targetId: Long, force: Boolean = false) - suspend fun rejectFollowRequest(actorId: Long, targetId: Long) - suspend fun ignoreFollowRequest(actorId: Long, targetId: Long) - suspend fun unfollow(actorId: Long, targetId: Long) - suspend fun unblock(actorId: Long, targetId: Long) - suspend fun mute(actorId: Long, targetId: Long) - suspend fun unmute(actorId: Long, targetId: Long) -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/service/relationship/RelationshipServiceImpl.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/service/relationship/RelationshipServiceImpl.kt deleted file mode 100644 index 057a5224..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/service/relationship/RelationshipServiceImpl.kt +++ /dev/null @@ -1,341 +0,0 @@ -/* - * Copyright (C) 2024 usbharu - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package dev.usbharu.hideout.core.service.relationship - -import dev.usbharu.hideout.activitypub.service.activity.accept.ApSendAcceptService -import dev.usbharu.hideout.activitypub.service.activity.follow.APSendFollowService -import dev.usbharu.hideout.activitypub.service.activity.reject.ApSendRejectService -import dev.usbharu.hideout.activitypub.service.activity.undo.APSendUndoService -import dev.usbharu.hideout.application.config.ApplicationConfig -import dev.usbharu.hideout.core.domain.exception.resource.UserNotFoundException -import dev.usbharu.hideout.core.domain.model.actor.Actor -import dev.usbharu.hideout.core.domain.model.actor.ActorRepository -import dev.usbharu.hideout.core.domain.model.relationship.Relationship -import dev.usbharu.hideout.core.domain.model.relationship.RelationshipRepository -import dev.usbharu.hideout.core.service.follow.SendFollowDto -import dev.usbharu.hideout.core.service.notification.FollowNotificationRequest -import dev.usbharu.hideout.core.service.notification.FollowRequestNotificationRequest -import dev.usbharu.hideout.core.service.notification.NotificationService -import org.slf4j.LoggerFactory -import org.springframework.stereotype.Service - -@Service -class RelationshipServiceImpl( - private val applicationConfig: ApplicationConfig, - private val relationshipRepository: RelationshipRepository, - private val apSendFollowService: APSendFollowService, - private val apSendAcceptService: ApSendAcceptService, - private val apSendRejectService: ApSendRejectService, - private val apSendUndoService: APSendUndoService, - private val actorRepository: ActorRepository, - private val notificationService: NotificationService, -) : RelationshipService { - override suspend fun followRequest(actorId: Long, targetId: Long) { - logger.info("START Follow Request userId: {} targetId: {}", actorId, targetId) - - val relationship = - relationshipRepository.findByUserIdAndTargetUserId(actorId, targetId)?.copy(followRequest = true) - ?: Relationship( - actorId = actorId, - targetActorId = targetId, - following = false, - blocking = false, - muting = false, - followRequest = true, - ignoreFollowRequestToTarget = false - ) - - val inverseRelationship = relationshipRepository.findByUserIdAndTargetUserId(targetId, actorId) ?: Relationship( - actorId = targetId, - targetActorId = actorId, - following = false, - blocking = false, - muting = false, - followRequest = false, - ignoreFollowRequestToTarget = false - ) - - if (inverseRelationship.blocking) { - logger.debug("FAILED Blocked by target. userId: {} targetId: {}", actorId, targetId) - return - } - - if (relationship.blocking) { - logger.debug("FAILED Blocking user. userId: {} targetId: {}", actorId, targetId) - return - } - if (relationship.ignoreFollowRequestToTarget) { - logger.debug("SUCCESS Ignore Follow Request. userId: {} targetId: {}", actorId, targetId) - return - } - - if (relationship.following) { - logger.debug("SUCCESS User already follow. userId: {} targetId: {}", actorId, targetId) - acceptFollowRequest(targetId, actorId, true) - return - } - - relationshipRepository.save(relationship) - - val remoteUser = isRemoteUser(targetId) - - if (remoteUser != null) { - val user = actorRepository.findById(actorId) ?: throw UserNotFoundException.withId(actorId) - apSendFollowService.sendFollow(SendFollowDto(user, remoteUser)) - } else { - val target = actorRepository.findById(targetId) ?: throw UserNotFoundException.withId(targetId) - if (target.locked.not()) { - acceptFollowRequest(targetId, actorId) - } else { - notificationService.publishNotify(FollowRequestNotificationRequest(targetId, actorId)) - } - } - - logger.info("SUCCESS Follow Request userId: {} targetId: {}", actorId, targetId) - } - - override suspend fun block(actorId: Long, targetId: Long) { - val relationship = relationshipRepository.findByUserIdAndTargetUserId(actorId, targetId) - - val user = actorRepository.findById(actorId) ?: throw UserNotFoundException.withId(actorId) - val targetActor = actorRepository.findById(targetId) ?: throw UserNotFoundException.withId(targetId) - if (relationship?.following == true) { - actorRepository.save(user.decrementFollowing()) - actorRepository.save(targetActor.decrementFollowers()) - } - - val blockedRelationship = relationship - ?.copy(blocking = true, followRequest = false, following = false) ?: Relationship( - actorId = actorId, - targetActorId = targetId, - following = false, - blocking = true, - muting = false, - followRequest = false, - ignoreFollowRequestToTarget = false - ) - - val inverseRelationship = relationshipRepository.findByUserIdAndTargetUserId(targetId, actorId) - - if (inverseRelationship?.following == true) { - actorRepository.save(targetActor.decrementFollowing()) - actorRepository.save(user.decrementFollowers()) - } - - val blockedInverseRelationship = inverseRelationship - ?.copy(followRequest = false, following = false) - - relationshipRepository.save(blockedRelationship) - if (blockedInverseRelationship != null) { - relationshipRepository.save(blockedInverseRelationship) - } - } - - override suspend fun acceptFollowRequest(actorId: Long, targetId: Long, force: Boolean) { - logger.info("START Accept follow request userId: {} targetId: {}", actorId, targetId) - - val relationship = relationshipRepository.findByUserIdAndTargetUserId(targetId, actorId) - - val inverseRelationship = relationshipRepository.findByUserIdAndTargetUserId(actorId, targetId) ?: Relationship( - actorId = targetId, - targetActorId = actorId, - following = false, - blocking = false, - muting = false, - followRequest = false, - ignoreFollowRequestToTarget = false - ) - - if (relationship == null) { - logger.warn("FAILED Follow Request Not Found. (Relationship) userId: {} targetId: {}", actorId, targetId) - return - } - - if (relationship.followRequest.not() && force.not()) { - logger.warn("FAILED Follow Request Not Found. (Follow Request) userId: {} targetId: {}", actorId, targetId) - return - } - - if (relationship.blocking) { - logger.warn("FAILED Blocking user userId: {} targetId: {}", actorId, targetId) - throw IllegalStateException( - "Cannot accept a follow request from a blocked user. userId: $actorId targetId: $targetId" - ) - } - - if (inverseRelationship.blocking) { - logger.warn("FAILED BLocked by user userId: {} targetId: {}", actorId, targetId) - throw IllegalStateException( - "Cannot accept a follow request from a blocking user. userId: $actorId targetId: $targetId" - ) - } - - val copy = relationship.copy(followRequest = false, following = true, blocking = false) - - val user = actorRepository.findById(actorId) ?: throw UserNotFoundException.withId(actorId) - - actorRepository.save(user.incrementFollowers()) - - relationshipRepository.save(copy) - - val remoteActor = actorRepository.findById(targetId) ?: throw UserNotFoundException.withId(targetId) - - actorRepository.save(remoteActor.incrementFollowing()) - - if (isRemoteActor(remoteActor)) { - apSendAcceptService.sendAcceptFollow(user, remoteActor) - } - notificationService.publishNotify(FollowNotificationRequest(actorId, targetId)) - } - - override suspend fun rejectFollowRequest(actorId: Long, targetId: Long) { - val relationship = relationshipRepository.findByUserIdAndTargetUserId(targetId, actorId) - - if (relationship == null) { - logger.warn("FAILED Follow Request Not Found. (Relationship) userId: {} targetId: {}", actorId, targetId) - return - } - - if (relationship.followRequest.not() && relationship.following.not()) { - logger.warn("FAILED Follow Request Not Found. (Follow Request) userId: {} targetId: {}", actorId, targetId) - return - } - - val copy = relationship.copy(followRequest = false, following = false) - - relationshipRepository.save(copy) - - val remoteUser = isRemoteUser(targetId) - - if (remoteUser != null) { - val user = actorRepository.findById(actorId) ?: throw UserNotFoundException.withId(actorId) - apSendRejectService.sendRejectFollow(user, remoteUser) - } - } - - override suspend fun ignoreFollowRequest(actorId: Long, targetId: Long) { - val relationship = relationshipRepository.findByUserIdAndTargetUserId(targetId, actorId) - ?.copy(ignoreFollowRequestToTarget = true) - ?: Relationship( - actorId = targetId, - targetActorId = actorId, - following = false, - blocking = false, - muting = false, - followRequest = false, - ignoreFollowRequestToTarget = true - ) - - relationshipRepository.save(relationship) - } - - override suspend fun unfollow(actorId: Long, targetId: Long) { - val relationship = relationshipRepository.findByUserIdAndTargetUserId(actorId, targetId) - - if (relationship == null) { - logger.warn("FAILED Unfollow. (Relationship) userId: {} targetId: {}", actorId, targetId) - return - } - - val user = actorRepository.findById(actorId) ?: throw UserNotFoundException.withId(actorId) - val targetActor = actorRepository.findById(targetId) ?: throw UserNotFoundException.withId(targetId) - - if (relationship.following) { - actorRepository.save(user.decrementFollowing()) - actorRepository.save(targetActor.decrementFollowers()) - } - - if (relationship.following.not()) { - logger.warn("SUCCESS User already unfollow. userId: {} targetId: {}", actorId, targetId) - return - } - - val copy = relationship.copy(following = false) - - relationshipRepository.save(copy) - - val remoteUser = isRemoteUser(targetId) - - if (remoteUser != null) { - apSendUndoService.sendUndoFollow(user, remoteUser) - } - } - - override suspend fun unblock(actorId: Long, targetId: Long) { - val relationship = relationshipRepository.findByUserIdAndTargetUserId(actorId, targetId) - - if (relationship == null) { - logger.warn("FAILED Unblock. (Relationship) userId: {} targetId: {}", actorId, targetId) - return - } - - if (relationship.blocking.not()) { - logger.warn("SUCCESS User is not blocking. userId: {] targetId: {}", actorId, targetId) - return - } - - val copy = relationship.copy(blocking = false) - relationshipRepository.save(copy) - } - - override suspend fun mute(actorId: Long, targetId: Long) { - val relationship = relationshipRepository.findByUserIdAndTargetUserId(actorId, targetId)?.copy(muting = true) - ?: Relationship( - actorId = actorId, - targetActorId = targetId, - following = false, - blocking = false, - muting = true, - followRequest = false, - ignoreFollowRequestToTarget = false - ) - - relationshipRepository.save(relationship) - } - - override suspend fun unmute(actorId: Long, targetId: Long) { - val relationship = relationshipRepository.findByUserIdAndTargetUserId(actorId, targetId)?.copy(muting = false) - - if (relationship == null) { - logger.warn("FAILED Mute. (Relationship) userId: {} targetId: {}", actorId, targetId) - return - } - - relationshipRepository.save(relationship) - } - - private fun isRemoteActor(actor: Actor): Boolean = actor.domain != applicationConfig.url.host - - private suspend fun isRemoteUser(userId: Long): Actor? { - logger.trace("isRemoteUser({})", userId) - val user = - actorRepository.findById(userId) ?: throw UserNotFoundException.withId(userId) - - logger.trace("user info {}", user) - - if (user.domain == applicationConfig.url.host) { - logger.trace("user: {} is local user", userId) - return null - } - logger.trace("user: {} is remote user", userId) - return user - } - - companion object { - private val logger = LoggerFactory.getLogger(RelationshipServiceImpl::class.java) - } -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/service/timeline/TimelineService.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/service/timeline/TimelineService.kt deleted file mode 100644 index 1bdec5c0..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/service/timeline/TimelineService.kt +++ /dev/null @@ -1,87 +0,0 @@ -/* - * Copyright (C) 2024 usbharu - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package dev.usbharu.hideout.core.service.timeline - -import dev.usbharu.hideout.core.domain.exception.resource.UserNotFoundException -import dev.usbharu.hideout.core.domain.model.actor.ActorRepository -import dev.usbharu.hideout.core.domain.model.post.Post -import dev.usbharu.hideout.core.domain.model.post.Visibility -import dev.usbharu.hideout.core.domain.model.timeline.Timeline -import dev.usbharu.hideout.core.domain.model.timeline.TimelineRepository -import dev.usbharu.hideout.core.query.FollowerQueryService -import org.slf4j.LoggerFactory -import org.springframework.stereotype.Service - -@Service -class TimelineService( - private val followerQueryService: FollowerQueryService, - private val timelineRepository: TimelineRepository, - private val actorRepository: ActorRepository -) { - suspend fun publishTimeline(post: Post, isLocal: Boolean) { - val findFollowersById = followerQueryService.findFollowersById(post.actorId).toMutableList() - if (isLocal) { - // 自分自身も含める必要がある - val user = actorRepository.findById(post.actorId) ?: throw UserNotFoundException.withId(post.actorId) - findFollowersById.add(user) - } - val timelines = findFollowersById.map { - Timeline( - id = timelineRepository.generateId(), - userId = it.id, - timelineId = 0, - postId = post.id, - postActorId = post.actorId, - createdAt = post.createdAt, - replyId = post.replyId, - repostId = post.repostId, - visibility = post.visibility, - sensitive = post.sensitive, - isLocal = isLocal, - isPureRepost = post.repostId == null || (post.text.isBlank() && post.overview.isNullOrBlank()), - mediaIds = post.mediaIds, - emojiIds = post.emojiIds - ) - }.toMutableList() - if (post.visibility == Visibility.PUBLIC) { - timelines.add( - Timeline( - id = timelineRepository.generateId(), - userId = 0, - timelineId = 0, - postId = post.id, - postActorId = post.actorId, - createdAt = post.createdAt, - replyId = post.replyId, - repostId = post.repostId, - visibility = post.visibility, - sensitive = post.sensitive, - isLocal = isLocal, - isPureRepost = post.repostId == null || (post.text.isBlank() && post.overview.isNullOrBlank()), - mediaIds = post.mediaIds, - emojiIds = post.emojiIds - ) - ) - } - timelineRepository.saveAll(timelines) - logger.debug("SUCCESS Timeline published. {}", timelines.size) - } - - companion object { - private val logger = LoggerFactory.getLogger(TimelineService::class.java) - } -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/service/user/UserAuthServiceImpl.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/service/user/UserAuthServiceImpl.kt index ad372b37..77c90ace 100644 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/service/user/UserAuthServiceImpl.kt +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/service/user/UserAuthServiceImpl.kt @@ -17,7 +17,6 @@ package dev.usbharu.hideout.core.service.user import dev.usbharu.hideout.application.config.ApplicationConfig -import dev.usbharu.hideout.core.domain.model.actor.ActorRepository import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder import org.springframework.stereotype.Service import java.security.* diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/service/user/UserService.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/service/user/UserService.kt deleted file mode 100644 index f1d42b42..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/service/user/UserService.kt +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright (C) 2024 usbharu - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package dev.usbharu.hideout.core.service.user - -import dev.usbharu.hideout.core.domain.model.actor.Actor -import org.springframework.stereotype.Service - -@Service -interface UserService { - - suspend fun usernameAlreadyUse(username: String): Boolean - - suspend fun createLocalUser(user: UserCreateDto): Actor - - suspend fun createRemoteUser(user: RemoteUserCreateDto, idOverride: Long? = null): Actor - - suspend fun updateUser(userId: Long, updateUserDto: UpdateUserDto) - - suspend fun deleteRemoteActor(actorId: Long) - - suspend fun restorationRemoteActor(actorId: Long) - - suspend fun deleteLocalUser(userId: Long) - - suspend fun updateUserStatistics(userId: Long) -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/service/user/UserServiceImpl.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/service/user/UserServiceImpl.kt deleted file mode 100644 index fbc80538..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/service/user/UserServiceImpl.kt +++ /dev/null @@ -1,227 +0,0 @@ -/* - * Copyright (C) 2024 usbharu - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package dev.usbharu.hideout.core.service.user - -import dev.usbharu.hideout.activitypub.service.activity.delete.APSendDeleteService -import dev.usbharu.hideout.application.config.ApplicationConfig -import dev.usbharu.hideout.core.domain.exception.resource.DuplicateException -import dev.usbharu.hideout.core.domain.exception.resource.UserNotFoundException -import dev.usbharu.hideout.core.domain.model.actor.Actor -import dev.usbharu.hideout.core.domain.model.actor.ActorRepository -import dev.usbharu.hideout.core.domain.model.deletedActor.DeletedActor -import dev.usbharu.hideout.core.domain.model.deletedActor.DeletedActorRepository -import dev.usbharu.hideout.core.domain.model.post.PostRepository -import dev.usbharu.hideout.core.domain.model.reaction.ReactionRepository -import dev.usbharu.hideout.core.domain.model.relationship.RelationshipRepository -import dev.usbharu.hideout.core.domain.model.userdetails.UserDetail -import dev.usbharu.hideout.core.domain.model.userdetails.UserDetailRepository -import dev.usbharu.hideout.core.external.job.UpdateActorTask -import dev.usbharu.hideout.core.service.instance.InstanceService -import dev.usbharu.hideout.core.service.post.PostService -import dev.usbharu.owl.producer.api.OwlProducer -import org.slf4j.LoggerFactory -import org.springframework.stereotype.Service -import java.time.Instant - -@Service -@Suppress("LongParameterList") -class UserServiceImpl( - private val actorRepository: ActorRepository, - private val userAuthService: UserAuthService, - private val actorBuilder: Actor.UserBuilder, - private val applicationConfig: ApplicationConfig, - private val instanceService: InstanceService, - private val userDetailRepository: UserDetailRepository, - private val deletedActorRepository: DeletedActorRepository, - private val reactionRepository: ReactionRepository, - private val relationshipRepository: RelationshipRepository, - private val postService: PostService, - private val apSendDeleteService: APSendDeleteService, - private val postRepository: PostRepository, - private val owlProducer: OwlProducer, -) : - UserService { - - override suspend fun usernameAlreadyUse(username: String): Boolean { - val findByNameAndDomain = actorRepository.findByNameAndDomain(username, applicationConfig.url.host) - return findByNameAndDomain != null - } - - override suspend fun createLocalUser(user: UserCreateDto): Actor { - if (applicationConfig.private) { - throw IllegalStateException("Instance is a private mode.") - } - - val nextId = actorRepository.nextId() - val hashedPassword = userAuthService.hash(user.password) - val keyPair = userAuthService.generateKeyPair() - val userUrl = "${applicationConfig.url}/users/${user.name}" - val userEntity = actorBuilder.of( - id = nextId, - name = user.name, - domain = applicationConfig.url.host, - screenName = user.screenName, - description = user.description, - inbox = "$userUrl/inbox", - outbox = "$userUrl/outbox", - url = userUrl, - publicKey = keyPair.public.toPem(), - privateKey = keyPair.private.toPem(), - createdAt = Instant.now(), - following = "$userUrl/following", - followers = "$userUrl/followers", - keyId = "$userUrl#pubkey", - locked = false, - instance = 0 - ) - val save = actorRepository.save(userEntity) - userDetailRepository.save(UserDetail(nextId, hashedPassword, true)) - return save - } - - override suspend fun createRemoteUser(user: RemoteUserCreateDto, idOverride: Long?): Actor { - logger.info("START Create New remote user. name: {} url: {}", user.name, user.url) - - val deletedActor = deletedActorRepository.findByNameAndDomain(user.name, user.domain) - - if (deletedActor != null) { - logger.warn("FAILED Deleted actor. user: ${user.name} domain: ${user.domain}") - throw IllegalStateException("Cannot create Deleted actor.") - } - - val instance = instanceService.fetchInstance(user.url, user.sharedInbox) - - val nextId = actorRepository.nextId() - val userEntity = actorBuilder.of( - id = idOverride ?: nextId, - name = user.name, - domain = user.domain, - screenName = user.screenName, - description = user.description, - inbox = user.inbox, - outbox = user.outbox, - url = user.url, - publicKey = user.publicKey, - createdAt = Instant.now(), - followers = user.followers, - following = user.following, - keyId = user.keyId, - instance = instance.id, - locked = user.locked ?: false - ) - return try { - val save = actorRepository.save(userEntity) - logger.warn("SUCCESS Create New remote user. id: {} name: {} url: {}", userEntity.id, user.name, user.url) - save - } catch (_: DuplicateException) { - actorRepository.findByUrl(user.url)!! - } - } - - 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, - locked = updateUserDto.locked - ) - ) - - userDetailRepository.save( - userDetail.copy( - autoAcceptFolloweeFollowRequest = updateUserDto.autoAcceptFolloweeFollowRequest - ) - ) - } - - override suspend fun deleteRemoteActor(actorId: Long) { - val actor = actorRepository.findByIdWithLock(actorId) ?: throw UserNotFoundException.withId(actorId) - val deletedActor = DeletedActor( - id = actor.id, - name = actor.name, - domain = actor.domain, - apId = actor.url, - publicKey = actor.publicKey, - deletedAt = Instant.now() - ) - relationshipRepository.deleteByActorIdOrTargetActorId(actorId, actorId) - - reactionRepository.deleteByActorId(actorId) - - postService.deleteByActor(actorId) - - actorRepository.delete(actor.id) - deletedActorRepository.save(deletedActor) - } - - override suspend fun restorationRemoteActor(actorId: Long) { - val deletedActor = deletedActorRepository.findById(actorId) - ?: return - - deletedActorRepository.delete(deletedActor) - - owlProducer.publishTask(UpdateActorTask(deletedActor.id, deletedActor.apId)) - } - - override suspend fun deleteLocalUser(userId: Long) { - val actor = actorRepository.findByIdWithLock(userId) ?: throw UserNotFoundException.withId(userId) - apSendDeleteService.sendDeleteActor(actor) - val deletedActor = DeletedActor( - id = actor.id, - name = actor.name, - domain = actor.domain, - apId = actor.url, - publicKey = actor.publicKey, - deletedAt = Instant.now() - ) - relationshipRepository.deleteByActorIdOrTargetActorId(userId, userId) - - reactionRepository.deleteByActorId(actor.id) - - postService.deleteByActor(actor.id) - actorRepository.delete(actor.id) - val userDetail = - userDetailRepository.findByActorId(actor.id) ?: throw IllegalStateException("user detail not found.") - userDetailRepository.delete(userDetail) - deletedActorRepository.save(deletedActor) - } - - override suspend fun updateUserStatistics(userId: Long) { - val actor = actorRepository.findByIdWithLock(userId) ?: throw UserNotFoundException.withId(userId) - - val followerCount = relationshipRepository.countByTargetIdAndFollowing(userId, true) - val followingCount = relationshipRepository.countByUserIdAndFollowing(userId, true) - val postsCount = postRepository.countByActorId(userId) - - actorRepository.save( - actor.copy( - followersCount = followerCount, - followingCount = followingCount, - postsCount = postsCount - ) - ) - } - - companion object { - private val logger = LoggerFactory.getLogger(UserServiceImpl::class.java) - } -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/mastodon/config/MastodonApiSecurityConfig.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/mastodon/config/MastodonApiSecurityConfig.kt deleted file mode 100644 index c4bce048..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/mastodon/config/MastodonApiSecurityConfig.kt +++ /dev/null @@ -1,173 +0,0 @@ -/* - * Copyright (C) 2024 usbharu - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package dev.usbharu.hideout.mastodon.config - -import dev.usbharu.hideout.application.infrastructure.springframework.RoleHierarchyAuthorizationManagerFactory -import org.springframework.context.annotation.Bean -import org.springframework.context.annotation.Configuration -import org.springframework.core.annotation.Order -import org.springframework.http.HttpMethod.* -import org.springframework.security.access.hierarchicalroles.RoleHierarchy -import org.springframework.security.access.hierarchicalroles.RoleHierarchyImpl -import org.springframework.security.config.annotation.web.builders.HttpSecurity -import org.springframework.security.config.annotation.web.invoke -import org.springframework.security.web.SecurityFilterChain - -@Configuration -class MastodonApiSecurityConfig { - @Bean - @Order(4) - @Suppress("LongMethod") - fun mastodonApiSecurityFilterChain( - http: HttpSecurity, - rf: RoleHierarchyAuthorizationManagerFactory, - ): SecurityFilterChain { - http { - securityMatcher("/api/v1/**", "/api/v2/**") - authorizeHttpRequests { - authorize(POST, "/api/v1/apps", permitAll) - authorize(GET, "/api/v1/instance/**", permitAll) - authorize(POST, "/api/v1/accounts", authenticated) - - authorize(GET, "/api/v1/accounts/verify_credentials", rf.hasScope("read:accounts")) - authorize(GET, "/api/v1/accounts/relationships", rf.hasScope("read:follows")) - authorize(GET, "/api/v1/accounts/*", permitAll) - authorize(GET, "/api/v1/accounts/*/statuses", permitAll) - authorize(POST, "/api/v1/accounts/*/follow", rf.hasScope("write:follows")) - authorize(POST, "/api/v1/accounts/*/unfollow", rf.hasScope("write:follows")) - authorize(POST, "/api/v1/accounts/*/block", rf.hasScope("write:blocks")) - authorize(POST, "/api/v1/accounts/*/unblock", rf.hasScope("write:blocks")) - authorize(POST, "/api/v1/accounts/*/mute", rf.hasScope("write:mutes")) - authorize(POST, "/api/v1/accounts/*/unmute", rf.hasScope("write:mutes")) - authorize(GET, "/api/v1/mutes", rf.hasScope("read:mutes")) - - authorize(POST, "/api/v1/media", rf.hasScope("write:media")) - authorize(POST, "/api/v1/statuses", rf.hasScope("write:statuses")) - authorize(GET, "/api/v1/statuses/*", permitAll) - authorize(POST, "/api/v1/statuses/*/favourite", rf.hasScope("write:favourites")) - authorize(POST, "/api/v1/statuses/*/unfavourite", rf.hasScope("write:favourites")) - authorize(PUT, "/api/v1/statuses/*/emoji_reactions/*", rf.hasScope("write:favourites")) - - authorize(GET, "/api/v1/timelines/public", permitAll) - authorize(GET, "/api/v1/timelines/home", rf.hasScope("read:statuses")) - - authorize(GET, "/api/v2/filters", rf.hasScope("read:filters")) - authorize(POST, "/api/v2/filters", rf.hasScope("write:filters")) - - authorize(GET, "/api/v2/filters/*", rf.hasScope("read:filters")) - authorize(PUT, "/api/v2/filters/*", rf.hasScope("write:filters")) - authorize(DELETE, "/api/v2/filters/*", rf.hasScope("write:filters")) - - authorize(GET, "/api/v2/filters/*/keywords", rf.hasScope("read:filters")) - authorize(POST, "/api/v2/filters/*/keywords", rf.hasScope("write:filters")) - - authorize(GET, "/api/v2/filters/keywords/*", rf.hasScope("read:filters")) - authorize(PUT, "/api/v2/filters/keywords/*", rf.hasScope("write:filters")) - authorize(DELETE, "/api/v2/filters/keywords/*", rf.hasScope("write:filters")) - - authorize(GET, "/api/v2/filters/*/statuses", rf.hasScope("read:filters")) - authorize(POST, "/api/v2/filters/*/statuses", rf.hasScope("write:filters")) - - authorize(GET, "/api/v2/filters/statuses/*", rf.hasScope("read:filters")) - authorize(DELETE, "/api/v2/filters/statuses/*", rf.hasScope("write:filters")) - - authorize(GET, "/api/v1/filters", rf.hasScope("read:filters")) - authorize(POST, "/api/v1/filters", rf.hasScope("write:filters")) - - authorize(GET, "/api/v1/filters/*", rf.hasScope("read:filters")) - authorize(POST, "/api/v1/filters/*", rf.hasScope("write:filters")) - authorize(DELETE, "/api/v1/filters/*", rf.hasScope("write:filters")) - - authorize(GET, "/api/v1/notifications", rf.hasScope("read:notifications")) - authorize(GET, "/api/v1/notifications/*", rf.hasScope("read:notifications")) - authorize(POST, "/api/v1/notifications/clear", rf.hasScope("write:notifications")) - authorize(POST, "/api/v1/notifications/*/dismiss", rf.hasScope("write:notifications")) - - authorize(anyRequest, authenticated) - } - - oauth2ResourceServer { - jwt { } - } - - csrf { - ignoringRequestMatchers("/api/v1/apps") - } - } - - return http.build() - } - - @Bean - fun roleHierarchy(): RoleHierarchy { - val roleHierarchyImpl = RoleHierarchyImpl() - - roleHierarchyImpl.setHierarchy( - """ - SCOPE_read > SCOPE_read:accounts - SCOPE_read > SCOPE_read:accounts - SCOPE_read > SCOPE_read:blocks - SCOPE_read > SCOPE_read:bookmarks - SCOPE_read > SCOPE_read:favourites - SCOPE_read > SCOPE_read:filters - SCOPE_read > SCOPE_read:follows - SCOPE_read > SCOPE_read:lists - SCOPE_read > SCOPE_read:mutes - SCOPE_read > SCOPE_read:notifications - SCOPE_read > SCOPE_read:search - SCOPE_read > SCOPE_read:statuses - SCOPE_write > SCOPE_write:accounts - SCOPE_write > SCOPE_write:blocks - SCOPE_write > SCOPE_write:bookmarks - SCOPE_write > SCOPE_write:conversations - SCOPE_write > SCOPE_write:favourites - SCOPE_write > SCOPE_write:filters - SCOPE_write > SCOPE_write:follows - SCOPE_write > SCOPE_write:lists - SCOPE_write > SCOPE_write:media - SCOPE_write > SCOPE_write:mutes - SCOPE_write > SCOPE_write:notifications - SCOPE_write > SCOPE_write:reports - SCOPE_write > SCOPE_write:statuses - SCOPE_follow > SCOPE_write:blocks - SCOPE_follow > SCOPE_write:follows - SCOPE_follow > SCOPE_write:mutes - SCOPE_follow > SCOPE_read:blocks - SCOPE_follow > SCOPE_read:follows - SCOPE_follow > SCOPE_read:mutes - SCOPE_admin > SCOPE_admin:read - SCOPE_admin > SCOPE_admin:write - SCOPE_admin:read > SCOPE_admin:read:accounts - SCOPE_admin:read > SCOPE_admin:read:reports - SCOPE_admin:read > SCOPE_admin:read:domain_allows - SCOPE_admin:read > SCOPE_admin:read:domain_blocks - SCOPE_admin:read > SCOPE_admin:read:ip_blocks - SCOPE_admin:read > SCOPE_admin:read:email_domain_blocks - SCOPE_admin:read > SCOPE_admin:read:canonical_email_blocks - SCOPE_admin:write > SCOPE_admin:write:accounts - SCOPE_admin:write > SCOPE_admin:write:reports - SCOPE_admin:write > SCOPE_admin:write:domain_allows - SCOPE_admin:write > SCOPE_admin:write:domain_blocks - SCOPE_admin:write > SCOPE_admin:write:ip_blocks - SCOPE_admin:write > SCOPE_admin:write:email_domain_blocks - SCOPE_admin:write > SCOPE_admin:write:canonical_email_blocks - """.trimIndent() - ) - - return roleHierarchyImpl - } -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/mastodon/domain/exception/AccountNotFoundException.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/mastodon/domain/exception/AccountNotFoundException.kt deleted file mode 100644 index 8f6d5b79..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/mastodon/domain/exception/AccountNotFoundException.kt +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright (C) 2024 usbharu - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package dev.usbharu.hideout.mastodon.domain.exception - -import dev.usbharu.hideout.domain.mastodon.model.generated.NotFoundResponse -import dev.usbharu.hideout.mastodon.domain.model.MastodonApiErrorResponse - -class AccountNotFoundException : ClientException { - constructor(response: MastodonApiErrorResponse) : super(response) - constructor(message: String?, response: MastodonApiErrorResponse) : super(message, response) - constructor(message: String?, cause: Throwable?, response: MastodonApiErrorResponse) : super( - message, - cause, - response - ) - - constructor(cause: Throwable?, response: MastodonApiErrorResponse) : super(cause, response) - constructor( - message: String?, - cause: Throwable?, - enableSuppression: Boolean, - writableStackTrace: Boolean, - response: MastodonApiErrorResponse, - ) : super(message, cause, enableSuppression, writableStackTrace, response) - - fun getTypedResponse(): MastodonApiErrorResponse = - response as MastodonApiErrorResponse - - companion object { - fun ofId(id: Long): AccountNotFoundException = AccountNotFoundException( - "id: $id was not found.", - MastodonApiErrorResponse( - NotFoundResponse( - "Record not found" - ), - 404 - ), - ) - } -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/mastodon/domain/exception/ClientException.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/mastodon/domain/exception/ClientException.kt deleted file mode 100644 index 3414889a..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/mastodon/domain/exception/ClientException.kt +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright (C) 2024 usbharu - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package dev.usbharu.hideout.mastodon.domain.exception - -import dev.usbharu.hideout.mastodon.domain.model.MastodonApiErrorResponse - -open class ClientException : MastodonApiException { - constructor(response: MastodonApiErrorResponse<*>) : super(response) - constructor(message: String?, response: MastodonApiErrorResponse<*>) : super(message, response) - constructor(message: String?, cause: Throwable?, response: MastodonApiErrorResponse<*>) : super( - message, - cause, - response - ) - - constructor(cause: Throwable?, response: MastodonApiErrorResponse<*>) : super(cause, response) - constructor( - message: String?, - cause: Throwable?, - enableSuppression: Boolean, - writableStackTrace: Boolean, - response: MastodonApiErrorResponse<*>, - ) : super(message, cause, enableSuppression, writableStackTrace, response) -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/mastodon/domain/exception/MastodonApiException.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/mastodon/domain/exception/MastodonApiException.kt deleted file mode 100644 index c3afd55a..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/mastodon/domain/exception/MastodonApiException.kt +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Copyright (C) 2024 usbharu - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package dev.usbharu.hideout.mastodon.domain.exception - -import dev.usbharu.hideout.mastodon.domain.model.MastodonApiErrorResponse - -@Suppress("UnnecessaryAbstractClass") -abstract class MastodonApiException : RuntimeException { - - val response: MastodonApiErrorResponse<*> - - constructor(response: MastodonApiErrorResponse<*>) : super() { - this.response = response - } - - constructor(message: String?, response: MastodonApiErrorResponse<*>) : super(message) { - this.response = response - } - - constructor(message: String?, cause: Throwable?, response: MastodonApiErrorResponse<*>) : super(message, cause) { - this.response = response - } - - constructor(cause: Throwable?, response: MastodonApiErrorResponse<*>) : super(cause) { - this.response = response - } - - constructor( - message: String?, - cause: Throwable?, - enableSuppression: Boolean, - writableStackTrace: Boolean, - response: MastodonApiErrorResponse<*>, - ) : super( - message, - cause, - enableSuppression, - writableStackTrace - ) { - this.response = response - } -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/mastodon/domain/exception/ServerException.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/mastodon/domain/exception/ServerException.kt deleted file mode 100644 index d2d6b187..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/mastodon/domain/exception/ServerException.kt +++ /dev/null @@ -1,21 +0,0 @@ -/* - * Copyright (C) 2024 usbharu - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package dev.usbharu.hideout.mastodon.domain.exception - -import dev.usbharu.hideout.mastodon.domain.model.MastodonApiErrorResponse - -open class ServerException(response: MastodonApiErrorResponse<*>) : MastodonApiException(response) diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/mastodon/domain/exception/StatusNotFoundException.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/mastodon/domain/exception/StatusNotFoundException.kt deleted file mode 100644 index 934403ec..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/mastodon/domain/exception/StatusNotFoundException.kt +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Copyright (C) 2024 usbharu - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package dev.usbharu.hideout.mastodon.domain.exception - -import dev.usbharu.hideout.domain.mastodon.model.generated.NotFoundResponse -import dev.usbharu.hideout.mastodon.domain.model.MastodonApiErrorResponse - -class StatusNotFoundException : ClientException { - - constructor(response: MastodonApiErrorResponse) : super(response) - - constructor(message: String?, response: MastodonApiErrorResponse) : super(message, response) - constructor(message: String?, cause: Throwable?, response: MastodonApiErrorResponse) : super( - message, - cause, - response - ) - constructor(cause: Throwable?, response: MastodonApiErrorResponse) : super(cause, response) - - constructor( - message: String?, - cause: Throwable?, - enableSuppression: Boolean, - writableStackTrace: Boolean, - response: MastodonApiErrorResponse, - ) : super(message, cause, enableSuppression, writableStackTrace, response) - - fun getTypedResponse(): MastodonApiErrorResponse = - response as MastodonApiErrorResponse - - companion object { - fun ofId(id: Long): StatusNotFoundException = StatusNotFoundException( - "id: $id was not found.", - MastodonApiErrorResponse( - NotFoundResponse( - "Record not found" - ), - 404 - ), - ) - } -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/mastodon/domain/model/MastodonApiErrorResponse.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/mastodon/domain/model/MastodonApiErrorResponse.kt deleted file mode 100644 index ee19ebcf..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/mastodon/domain/model/MastodonApiErrorResponse.kt +++ /dev/null @@ -1,19 +0,0 @@ -/* - * Copyright (C) 2024 usbharu - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package dev.usbharu.hideout.mastodon.domain.model - -data class MastodonApiErrorResponse(val response: R, val statusCode: Int) diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/mastodon/domain/model/MastodonNotification.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/mastodon/domain/model/MastodonNotification.kt deleted file mode 100644 index e334b04a..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/mastodon/domain/model/MastodonNotification.kt +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright (C) 2024 usbharu - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package dev.usbharu.hideout.mastodon.domain.model - -import org.springframework.data.annotation.Id -import org.springframework.data.mongodb.core.mapping.Document -import java.time.Instant - -@Document -data class MastodonNotification( - @Id - val id: Long, - val userId: Long, - val type: NotificationType, - val createdAt: Instant, - val accountId: Long, - val statusId: Long?, - val reportId: Long?, - val relationshipServeranceEvent: Long? -) diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/mastodon/domain/model/MastodonNotificationRepository.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/mastodon/domain/model/MastodonNotificationRepository.kt deleted file mode 100644 index d1e0ac54..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/mastodon/domain/model/MastodonNotificationRepository.kt +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright (C) 2024 usbharu - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package dev.usbharu.hideout.mastodon.domain.model - -import dev.usbharu.hideout.application.infrastructure.exposed.Page -import dev.usbharu.hideout.application.infrastructure.exposed.PaginationList - -interface MastodonNotificationRepository { - suspend fun save(mastodonNotification: MastodonNotification): MastodonNotification - suspend fun deleteById(id: Long) - suspend fun findById(id: Long): MastodonNotification? - - @Suppress("FunctionMaxLength") - suspend fun findByUserIdAndInTypesAndInSourceActorId( - loginUser: Long, - types: List, - accountId: List, - page: Page - ): PaginationList - - suspend fun deleteByUserId(userId: Long) - suspend fun deleteByUserIdAndId(userId: Long, id: Long) -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/mastodon/domain/model/NotificationType.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/mastodon/domain/model/NotificationType.kt deleted file mode 100644 index a38cfa82..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/mastodon/domain/model/NotificationType.kt +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright (C) 2024 usbharu - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package dev.usbharu.hideout.mastodon.domain.model - -@Suppress("EnumEntryName", "EnumNaming", "EnumEntryNameCase") -enum class NotificationType { - mention, - status, - reblog, - follow, - follow_request, - favourite, - poll, - update, - admin_sign_up, - admin_report, - severed_relationships; - - companion object { - fun parse(string: String): NotificationType? = when (string) { - "mention" -> mention - "status" -> status - "reblog" -> reblog - "follow" -> follow - "follow_request" -> follow_request - "favourite" -> favourite - "poll" -> poll - "update" -> update - "admin.sign_up" -> admin_sign_up - "admin.report" -> admin_report - "servered_relationships" -> severed_relationships - else -> null - } - } -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/mastodon/infrastructure/exposedquery/AccountQueryServiceImpl.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/mastodon/infrastructure/exposedquery/AccountQueryServiceImpl.kt deleted file mode 100644 index 75cea253..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/mastodon/infrastructure/exposedquery/AccountQueryServiceImpl.kt +++ /dev/null @@ -1,74 +0,0 @@ -/* - * Copyright (C) 2024 usbharu - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package dev.usbharu.hideout.mastodon.infrastructure.exposedquery - -import dev.usbharu.hideout.application.config.ApplicationConfig -import dev.usbharu.hideout.core.infrastructure.exposedrepository.Actors -import dev.usbharu.hideout.domain.mastodon.model.generated.Account -import dev.usbharu.hideout.mastodon.query.AccountQueryService -import org.jetbrains.exposed.sql.ResultRow -import org.jetbrains.exposed.sql.selectAll -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 query = Actors.selectAll().where { Actors.id eq accountId } - - return query - .singleOrNull() - ?.let { toAccount(it) } - } - - override suspend fun findByIds(accountIds: List): List { - val query = Actors.selectAll().where { Actors.id inList accountIds } - - return query - .map { toAccount(it) } - } - - private fun toAccount( - resultRow: ResultRow - ): 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[Actors.lastPostAt]?.toString(), - statusesCount = resultRow[Actors.postsCount], - followersCount = resultRow[Actors.followersCount], - followingCount = resultRow[Actors.followingCount], - ) - } -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/mastodon/infrastructure/exposedquery/StatusQueryServiceImpl.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/mastodon/infrastructure/exposedquery/StatusQueryServiceImpl.kt deleted file mode 100644 index 3194f368..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/mastodon/infrastructure/exposedquery/StatusQueryServiceImpl.kt +++ /dev/null @@ -1,250 +0,0 @@ -/* - * Copyright (C) 2024 usbharu - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package dev.usbharu.hideout.mastodon.infrastructure.exposedquery - -import dev.usbharu.hideout.application.infrastructure.exposed.Page -import dev.usbharu.hideout.application.infrastructure.exposed.PaginationList -import dev.usbharu.hideout.application.infrastructure.exposed.withPagination -import dev.usbharu.hideout.core.domain.model.emoji.CustomEmoji -import dev.usbharu.hideout.core.domain.model.media.toMediaAttachments -import dev.usbharu.hideout.core.infrastructure.exposedrepository.* -import dev.usbharu.hideout.domain.mastodon.model.generated.Account -import dev.usbharu.hideout.domain.mastodon.model.generated.Status -import dev.usbharu.hideout.domain.mastodon.model.generated.Status.Visibility.* -import dev.usbharu.hideout.mastodon.interfaces.api.status.StatusQuery -import dev.usbharu.hideout.mastodon.query.StatusQueryService -import org.jetbrains.exposed.sql.ResultRow -import org.jetbrains.exposed.sql.andWhere -import org.jetbrains.exposed.sql.selectAll -import org.springframework.stereotype.Repository -import java.time.Instant -import dev.usbharu.hideout.domain.mastodon.model.generated.CustomEmoji as MastodonEmoji - -@Suppress("IncompleteDestructuring") -@Repository -class StatusQueryServiceImpl : StatusQueryService { - override suspend fun findByPostIds(ids: List): List = findByPostIdsWithMedia(ids) - - override suspend fun findByPostIdsWithMediaIds(statusQueries: List): List { - val postIdSet = mutableSetOf() - postIdSet.addAll(statusQueries.flatMap { listOfNotNull(it.postId, it.replyId, it.repostId) }) - val mediaIdSet = mutableSetOf() - mediaIdSet.addAll(statusQueries.flatMap { it.mediaIds }) - - val emojiIdSet = mutableSetOf() - emojiIdSet.addAll(statusQueries.flatMap { it.emojiIds }) - - val postMap = Posts - .leftJoin(Actors) - .selectAll().where { Posts.id inList postIdSet } - .associate { it[Posts.id] to toStatus(it) } - val mediaMap = Media.selectAll().where { Media.id inList mediaIdSet } - .associate { - it[Media.id] to it.toMedia().toMediaAttachments() - } - - val emojiMap = CustomEmojis.selectAll().where { CustomEmojis.id inList emojiIdSet }.associate { - it[CustomEmojis.id] to it.toCustomEmoji().toMastodonEmoji() - } - return statusQueries.mapNotNull { statusQuery -> - postMap[statusQuery.postId]?.copy( - inReplyToId = statusQuery.replyId?.toString(), - inReplyToAccountId = postMap[statusQuery.replyId]?.account?.id, - reblog = postMap[statusQuery.repostId], - mediaAttachments = statusQuery.mediaIds.mapNotNull { mediaMap[it] }, - emojis = statusQuery.emojiIds.mapNotNull { emojiMap[it] } - ) - } - } - - override suspend fun accountsStatus( - accountId: Long, - onlyMedia: Boolean, - excludeReplies: Boolean, - excludeReblogs: Boolean, - pinned: Boolean, - tagged: String?, - includeFollowers: Boolean, - page: Page - ): PaginationList { - val query = Posts - .leftJoin(PostsMedia) - .leftJoin(Actors) - .leftJoin(Media) - .selectAll().where { Posts.actorId eq accountId } - - if (onlyMedia) { - query.andWhere { PostsMedia.mediaId.isNotNull() } - } - if (excludeReplies) { - query.andWhere { Posts.replyId.isNotNull() } - } - if (excludeReblogs) { - query.andWhere { Posts.repostId.isNotNull() } - } - if (includeFollowers) { - query.andWhere { Posts.visibility inList listOf(public.ordinal, unlisted.ordinal, private.ordinal) } - } else { - query.andWhere { Posts.visibility inList listOf(public.ordinal, unlisted.ordinal) } - } - - val pairs = query - .withPagination(page, Posts.id) - .groupBy { it[Posts.id] } - .map { it.value } - .map { - toStatus(it.first()).copy( - mediaAttachments = it.mapNotNull { resultRow -> - resultRow.toMediaOrNull()?.toMediaAttachments() - } - ) to it.first()[Posts.repostId] - } - - val statuses = resolveReplyAndRepost(pairs) - return PaginationList( - statuses, - statuses.firstOrNull()?.id?.toLongOrNull(), - statuses.lastOrNull()?.id?.toLongOrNull() - ) - } - - override suspend fun findByPostId(id: Long): Status? { - val map = Posts - .leftJoin(PostsMedia) - .leftJoin(Actors) - .leftJoin(Media) - .selectAll() - .where { Posts.id eq id } - .groupBy { it[Posts.id] } - .map { it.value } - .map { - toStatus(it.first()).copy( - mediaAttachments = it.mapNotNull { resultRow -> - resultRow.toMediaOrNull()?.toMediaAttachments() - }, - emojis = it.mapNotNull { resultRow -> resultRow.toCustomEmojiOrNull()?.toMastodonEmoji() } - ) to it.first()[Posts.repostId] - } - return resolveReplyAndRepost(map).singleOrNull() - } - - private fun resolveReplyAndRepost(pairs: List>): List { - val statuses = pairs.map { it.first } - return pairs - .map { - if (it.second != null) { - it.first.copy(reblog = statuses.find { (id) -> id == it.second.toString() }) - } else { - it.first - } - } - .map { - if (it.inReplyToId != null) { - println("statuses trace: $statuses") - println("inReplyToId trace: ${it.inReplyToId}") - it.copy(inReplyToAccountId = statuses.find { (id) -> id == it.inReplyToId }?.account?.id) - } else { - it - } - } - } - - private suspend fun findByPostIdsWithMedia(ids: List): List { - val pairs = Posts - .leftJoin(PostsMedia) - .leftJoin(PostsEmojis) - .leftJoin(CustomEmojis) - .leftJoin(Actors) - .leftJoin(Media) - .selectAll().where { Posts.id inList ids } - .groupBy { it[Posts.id] } - .map { it.value } - .map { - toStatus(it.first()).copy( - mediaAttachments = it.mapNotNull { resultRow -> - resultRow.toMediaOrNull()?.toMediaAttachments() - }, - emojis = it.mapNotNull { resultRow -> resultRow.toCustomEmojiOrNull()?.toMastodonEmoji() } - ) to it.first()[Posts.repostId] - } - return resolveReplyAndRepost(pairs) - } -} - -private fun CustomEmoji.toMastodonEmoji(): MastodonEmoji = MastodonEmoji( - shortcode = this.name, - url = this.url, - staticUrl = this.url, - visibleInPicker = true, - category = this.category.orEmpty() -) - -private fun toStatus(it: ResultRow) = Status( - id = it[Posts.id].toString(), - uri = it[Posts.apId], - createdAt = Instant.ofEpochMilli(it[Posts.createdAt]).toString(), - account = Account( - id = it[Actors.id].toString(), - username = it[Actors.name], - acct = "${it[Actors.name]}@${it[Actors.domain]}", - url = it[Actors.url], - displayName = it[Actors.screenName], - note = it[Actors.description], - avatar = it[Actors.url] + "/icon.jpg", - avatarStatic = it[Actors.url] + "/icon.jpg", - header = it[Actors.url] + "/header.jpg", - headerStatic = it[Actors.url] + "/header.jpg", - locked = it[Actors.locked], - fields = emptyList(), - emojis = emptyList(), - bot = false, - group = false, - discoverable = true, - createdAt = Instant.ofEpochMilli(it[Actors.createdAt]).toString(), - lastStatusAt = it[Actors.lastPostAt]?.toString(), - statusesCount = it[Actors.postsCount], - followersCount = it[Actors.followersCount], - followingCount = it[Actors.followingCount], - noindex = false, - moved = false, - suspendex = false, - limited = false - ), - content = it[Posts.text], - visibility = when (it[Posts.visibility]) { - 0 -> public - 1 -> unlisted - 2 -> private - 3 -> direct - else -> 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 -) diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/mastodon/infrastructure/exposedrepository/ExposedMastodonNotificationRepository.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/mastodon/infrastructure/exposedrepository/ExposedMastodonNotificationRepository.kt deleted file mode 100644 index 7f928b08..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/mastodon/infrastructure/exposedrepository/ExposedMastodonNotificationRepository.kt +++ /dev/null @@ -1,131 +0,0 @@ -/* - * Copyright (C) 2024 usbharu - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package dev.usbharu.hideout.mastodon.infrastructure.exposedrepository - -import dev.usbharu.hideout.application.infrastructure.exposed.Page -import dev.usbharu.hideout.application.infrastructure.exposed.PaginationList -import dev.usbharu.hideout.application.infrastructure.exposed.withPagination -import dev.usbharu.hideout.core.infrastructure.exposedrepository.AbstractRepository -import dev.usbharu.hideout.mastodon.domain.model.MastodonNotification -import dev.usbharu.hideout.mastodon.domain.model.MastodonNotificationRepository -import dev.usbharu.hideout.mastodon.domain.model.NotificationType -import org.jetbrains.exposed.sql.* -import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq -import org.jetbrains.exposed.sql.javatime.timestamp -import org.slf4j.Logger -import org.slf4j.LoggerFactory -import org.springframework.beans.factory.annotation.Qualifier -import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty -import org.springframework.stereotype.Repository - -@Repository -@Qualifier("jdbc") -@ConditionalOnProperty("hideout.use-mongodb", havingValue = "false", matchIfMissing = true) -class ExposedMastodonNotificationRepository : MastodonNotificationRepository, AbstractRepository() { - override val logger: Logger - get() = Companion.logger - - override suspend fun save(mastodonNotification: MastodonNotification): MastodonNotification = query { - val singleOrNull = - MastodonNotifications.selectAll().where { MastodonNotifications.id eq mastodonNotification.id } - .singleOrNull() - if (singleOrNull == null) { - MastodonNotifications.insert { - it[id] = mastodonNotification.id - it[type] = mastodonNotification.type.name - it[createdAt] = mastodonNotification.createdAt - it[accountId] = mastodonNotification.accountId - it[statusId] = mastodonNotification.statusId - it[reportId] = mastodonNotification.reportId - it[relationshipServeranceEventId] = - mastodonNotification.relationshipServeranceEvent - } - } else { - MastodonNotifications.update({ MastodonNotifications.id eq mastodonNotification.id }) { - it[type] = mastodonNotification.type.name - it[createdAt] = mastodonNotification.createdAt - it[accountId] = mastodonNotification.accountId - it[statusId] = mastodonNotification.statusId - it[reportId] = mastodonNotification.reportId - it[relationshipServeranceEventId] = - mastodonNotification.relationshipServeranceEvent - } - } - mastodonNotification - } - - override suspend fun deleteById(id: Long): Unit = query { - MastodonNotifications.deleteWhere { - MastodonNotifications.id eq id - } - } - - override suspend fun findById(id: Long): MastodonNotification? = query { - MastodonNotifications.selectAll().where { MastodonNotifications.id eq id }.singleOrNull() - ?.toMastodonNotification() - } - - override suspend fun findByUserIdAndInTypesAndInSourceActorId( - loginUser: Long, - types: List, - accountId: List, - page: Page - ): PaginationList = query { - val query = MastodonNotifications.selectAll().where { MastodonNotifications.userId eq loginUser } - val result = query.withPagination(page, MastodonNotifications.id) - - return@query PaginationList(result.map { it.toMastodonNotification() }, result.next, result.prev) - } - - override suspend fun deleteByUserId(userId: Long) { - MastodonNotifications.deleteWhere { - MastodonNotifications.userId eq userId - } - } - - override suspend fun deleteByUserIdAndId(userId: Long, id: Long) { - MastodonNotifications.deleteWhere { - MastodonNotifications.userId eq userId and (MastodonNotifications.id eq id) - } - } - - companion object { - private val logger = LoggerFactory.getLogger(ExposedMastodonNotificationRepository::class.java) - } -} - -fun ResultRow.toMastodonNotification(): MastodonNotification = MastodonNotification( - id = this[MastodonNotifications.id], - userId = this[MastodonNotifications.userId], - type = NotificationType.valueOf(this[MastodonNotifications.type]), - createdAt = this[MastodonNotifications.createdAt], - accountId = this[MastodonNotifications.accountId], - statusId = this[MastodonNotifications.statusId], - reportId = this[MastodonNotifications.reportId], - relationshipServeranceEvent = this[MastodonNotifications.relationshipServeranceEventId], -) - -object MastodonNotifications : Table("mastodon_notifications") { - val id = long("id") - val userId = long("user_id") - val type = varchar("type", 100) - val createdAt = timestamp("created_at") - val accountId = long("account_id") - val statusId = long("status_id").nullable() - val reportId = long("report_id").nullable() - val relationshipServeranceEventId = long("relationship_serverance_event_id").nullable() -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/mastodon/infrastructure/mongorepository/MongoMastodonNotificationRepository.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/mastodon/infrastructure/mongorepository/MongoMastodonNotificationRepository.kt deleted file mode 100644 index e95d204f..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/mastodon/infrastructure/mongorepository/MongoMastodonNotificationRepository.kt +++ /dev/null @@ -1,27 +0,0 @@ -/* - * Copyright (C) 2024 usbharu - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package dev.usbharu.hideout.mastodon.infrastructure.mongorepository - -import dev.usbharu.hideout.mastodon.domain.model.MastodonNotification -import org.springframework.data.mongodb.repository.MongoRepository - -interface MongoMastodonNotificationRepository : MongoRepository { - - fun deleteByUserId(userId: Long): Long - - fun deleteByIdAndUserId(id: Long, userId: Long): Long -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/mastodon/infrastructure/mongorepository/MongoMastodonNotificationRepositoryWrapper.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/mastodon/infrastructure/mongorepository/MongoMastodonNotificationRepositoryWrapper.kt deleted file mode 100644 index 4fd243e9..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/mastodon/infrastructure/mongorepository/MongoMastodonNotificationRepositoryWrapper.kt +++ /dev/null @@ -1,83 +0,0 @@ -/* - * Copyright (C) 2024 usbharu - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package dev.usbharu.hideout.mastodon.infrastructure.mongorepository - -import dev.usbharu.hideout.application.infrastructure.exposed.Page -import dev.usbharu.hideout.application.infrastructure.exposed.PaginationList -import dev.usbharu.hideout.mastodon.domain.model.MastodonNotification -import dev.usbharu.hideout.mastodon.domain.model.MastodonNotificationRepository -import dev.usbharu.hideout.mastodon.domain.model.NotificationType -import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty -import org.springframework.data.domain.Sort -import org.springframework.data.mongodb.core.MongoTemplate -import org.springframework.data.mongodb.core.query.Criteria -import org.springframework.data.mongodb.core.query.Query -import org.springframework.stereotype.Repository -import kotlin.jvm.optionals.getOrNull - -@Repository -@ConditionalOnProperty("hideout.use-mongodb", havingValue = "true", matchIfMissing = false) -class MongoMastodonNotificationRepositoryWrapper( - private val mongoMastodonNotificationRepository: MongoMastodonNotificationRepository, - private val mongoTemplate: MongoTemplate -) : - MastodonNotificationRepository { - override suspend fun save(mastodonNotification: MastodonNotification): MastodonNotification = - mongoMastodonNotificationRepository.save(mastodonNotification) - - override suspend fun deleteById(id: Long) = mongoMastodonNotificationRepository.deleteById(id) - - override suspend fun findById(id: Long): MastodonNotification? = - mongoMastodonNotificationRepository.findById(id).getOrNull() - - override suspend fun findByUserIdAndInTypesAndInSourceActorId( - loginUser: Long, - types: List, - accountId: List, - page: Page - ): PaginationList { - val query = Query() - - page.limit?.let { query.limit(it) } - - val mastodonNotifications = if (page.minId != null) { - query.with(Sort.by(Sort.Direction.ASC, "id")) - page.minId?.let { query.addCriteria(Criteria.where("id").gt(it)) } - page.maxId?.let { query.addCriteria(Criteria.where("id").lt(it)) } - mongoTemplate.find(query, MastodonNotification::class.java).reversed() - } else { - query.with(Sort.by(Sort.Direction.DESC, "id")) - page.sinceId?.let { query.addCriteria(Criteria.where("id").gt(it)) } - page.maxId?.let { query.addCriteria(Criteria.where("id").lt(it)) } - mongoTemplate.find(query, MastodonNotification::class.java) - } - - return PaginationList( - mastodonNotifications, - mastodonNotifications.firstOrNull()?.id, - mastodonNotifications.lastOrNull()?.id - ) - } - - override suspend fun deleteByUserId(userId: Long) { - mongoMastodonNotificationRepository.deleteByUserId(userId) - } - - override suspend fun deleteByUserIdAndId(userId: Long, id: Long) { - mongoMastodonNotificationRepository.deleteByIdAndUserId(id, userId) - } -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/mastodon/infrastructure/springweb/MastodonApiControllerAdvice.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/mastodon/infrastructure/springweb/MastodonApiControllerAdvice.kt deleted file mode 100644 index b154f52b..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/mastodon/infrastructure/springweb/MastodonApiControllerAdvice.kt +++ /dev/null @@ -1,111 +0,0 @@ -/* - * Copyright (C) 2024 usbharu - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package dev.usbharu.hideout.mastodon.infrastructure.springweb - -import dev.usbharu.hideout.domain.mastodon.model.generated.NotFoundResponse -import dev.usbharu.hideout.domain.mastodon.model.generated.UnprocessableEntityResponse -import dev.usbharu.hideout.domain.mastodon.model.generated.UnprocessableEntityResponseDetails -import dev.usbharu.hideout.mastodon.domain.exception.AccountNotFoundException -import dev.usbharu.hideout.mastodon.domain.exception.StatusNotFoundException -import dev.usbharu.hideout.mastodon.interfaces.api.account.MastodonAccountApiController -import dev.usbharu.hideout.mastodon.interfaces.api.apps.MastodonAppsApiController -import dev.usbharu.hideout.mastodon.interfaces.api.filter.MastodonFilterApiController -import dev.usbharu.hideout.mastodon.interfaces.api.instance.MastodonInstanceApiController -import dev.usbharu.hideout.mastodon.interfaces.api.media.MastodonMediaApiController -import dev.usbharu.hideout.mastodon.interfaces.api.notification.MastodonNotificationApiController -import dev.usbharu.hideout.mastodon.interfaces.api.status.MastodonStatusesApiContoller -import dev.usbharu.hideout.mastodon.interfaces.api.timeline.MastodonTimelineApiController -import org.slf4j.LoggerFactory -import org.springframework.http.HttpStatus -import org.springframework.http.ResponseEntity -import org.springframework.validation.BindException -import org.springframework.validation.FieldError -import org.springframework.web.bind.annotation.ControllerAdvice -import org.springframework.web.bind.annotation.ExceptionHandler - -@ControllerAdvice( - assignableTypes = [ - MastodonAccountApiController::class, - MastodonAppsApiController::class, - MastodonFilterApiController::class, - MastodonInstanceApiController::class, - MastodonMediaApiController::class, - MastodonNotificationApiController::class, - MastodonStatusesApiContoller::class, - MastodonTimelineApiController::class - ] -) -class MastodonApiControllerAdvice { - - @ExceptionHandler(BindException::class) - fun handleException(ex: BindException): ResponseEntity { - logger.debug("Failed bind entity.", ex) - - val details = mutableMapOf>() - - ex.allErrors.forEach { - val defaultMessage = it.defaultMessage - when { - it is FieldError -> { - val code = when (it.code) { - "Email" -> "ERR_INVALID" - "Pattern" -> "ERR_INVALID" - else -> "ERR_INVALID" - } - details.getOrPut(it.field) { - mutableListOf() - }.add(UnprocessableEntityResponseDetails(code, defaultMessage.orEmpty())) - } - - defaultMessage?.startsWith("Parameter specified as non-null is null:") == true -> { - val parameter = defaultMessage.substringAfterLast("parameter ") - - details.getOrPut(parameter) { - mutableListOf() - }.add(UnprocessableEntityResponseDetails("ERR_BLANK", "can't be blank")) - } - - else -> { - logger.warn("Unknown validation error", ex) - } - } - } - - val message = details.map { - it.key + " " + it.value.joinToString { responseDetails -> responseDetails.description } - }.joinToString() - - return ResponseEntity.unprocessableEntity() - .body(UnprocessableEntityResponse(message, details)) - } - - @ExceptionHandler(StatusNotFoundException::class) - fun handleException(ex: StatusNotFoundException): ResponseEntity { - logger.warn("Status not found.", ex) - return ResponseEntity.status(HttpStatus.NOT_FOUND).body(ex.getTypedResponse().response) - } - - @ExceptionHandler(AccountNotFoundException::class) - fun handleException(ex: AccountNotFoundException): ResponseEntity { - logger.warn("Account not found.", ex) - return ResponseEntity.status(HttpStatus.NOT_FOUND).body(ex.getTypedResponse().response) - } - - companion object { - private val logger = LoggerFactory.getLogger(MastodonApiControllerAdvice::class.java) - } -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/mastodon/interfaces/api/account/MastodonAccountApiController.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/mastodon/interfaces/api/account/MastodonAccountApiController.kt deleted file mode 100644 index a2e391af..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/mastodon/interfaces/api/account/MastodonAccountApiController.kt +++ /dev/null @@ -1,250 +0,0 @@ -/* - * Copyright (C) 2024 usbharu - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package dev.usbharu.hideout.mastodon.interfaces.api.account - -import dev.usbharu.hideout.application.config.ApplicationConfig -import dev.usbharu.hideout.application.external.Transaction -import dev.usbharu.hideout.application.infrastructure.exposed.Page -import dev.usbharu.hideout.application.infrastructure.exposed.toHttpHeader -import dev.usbharu.hideout.controller.mastodon.generated.AccountApi -import dev.usbharu.hideout.core.infrastructure.springframework.security.LoginUserContextHolder -import dev.usbharu.hideout.core.service.user.UserCreateDto -import dev.usbharu.hideout.domain.mastodon.model.generated.* -import dev.usbharu.hideout.mastodon.service.account.AccountApiService -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.asFlow -import kotlinx.coroutines.runBlocking -import org.springframework.http.HttpHeaders -import org.springframework.http.HttpStatus -import org.springframework.http.ResponseEntity -import org.springframework.stereotype.Controller -import java.net.URI - -@Controller -class MastodonAccountApiController( - private val accountApiService: AccountApiService, - private val transaction: Transaction, - private val loginUserContextHolder: LoginUserContextHolder, - private val applicationConfig: ApplicationConfig -) : AccountApi { - - override suspend fun apiV1AccountsIdFollowPost( - id: String, - followRequestBody: FollowRequestBody? - ): ResponseEntity { - val userid = loginUserContextHolder.getLoginUserId() - - return ResponseEntity.ok(accountApiService.follow(userid, id.toLong())) - } - - override suspend fun apiV1AccountsIdGet(id: String): ResponseEntity = - ResponseEntity.ok(accountApiService.account(id.toLong())) - - override suspend fun apiV1AccountsVerifyCredentialsGet(): ResponseEntity = ResponseEntity( - accountApiService.verifyCredentials(loginUserContextHolder.getLoginUserId()), - HttpStatus.OK - ) - - override suspend fun apiV1AccountsPost(accountsCreateRequest: AccountsCreateRequest): ResponseEntity { - transaction.transaction { - accountApiService.registerAccount( - UserCreateDto( - accountsCreateRequest.username, - accountsCreateRequest.username, - "", - accountsCreateRequest.password - ) - ) - } - val httpHeaders = HttpHeaders() - httpHeaders.location = URI("/users/${accountsCreateRequest.username}") - return ResponseEntity(Unit, httpHeaders, HttpStatus.FOUND) - } - - override fun apiV1AccountsIdStatusesGet( - id: String, - maxId: String?, - sinceId: String?, - minId: String?, - limit: Int, - onlyMedia: Boolean, - excludeReplies: Boolean, - excludeReblogs: Boolean, - pinned: Boolean, - tagged: String? - ): ResponseEntity> = runBlocking { - val userid = loginUserContextHolder.getLoginUserIdOrNull() - val statuses = accountApiService.accountsStatuses( - userid = id.toLong(), - onlyMedia = onlyMedia, - excludeReplies = excludeReplies, - excludeReblogs = excludeReblogs, - pinned = pinned, - tagged = tagged, - loginUser = userid, - page = Page.of( - maxId?.toLongOrNull(), - sinceId?.toLongOrNull(), - minId?.toLongOrNull(), - limit.coerceIn(0, 80) - ) - ) - val httpHeader = statuses.toHttpHeader( - { "${applicationConfig.url}/api/v1/accounts/$id/statuses?min_id=$it" }, - { "${applicationConfig.url}/api/v1/accounts/$id/statuses?max_id=$it" }, - ) - - if (httpHeader != null) { - return@runBlocking ResponseEntity.ok().header("Link", httpHeader).body(statuses.asFlow()) - } - - ResponseEntity.ok(statuses.asFlow()) - } - - override fun apiV1AccountsRelationshipsGet( - id: List?, - withSuspended: Boolean - ): ResponseEntity> = runBlocking { - val userid = loginUserContextHolder.getLoginUserId() - - ResponseEntity.ok( - accountApiService.relationships(userid, id.orEmpty().mapNotNull { it.toLongOrNull() }, withSuspended) - .asFlow() - ) - } - - override suspend fun apiV1AccountsIdBlockPost(id: String): ResponseEntity { - val userid = loginUserContextHolder.getLoginUserId() - - val block = accountApiService.block(userid, id.toLong()) - - return ResponseEntity.ok(block) - } - - override suspend fun apiV1AccountsIdUnblockPost(id: String): ResponseEntity { - val userid = loginUserContextHolder.getLoginUserId() - - val unblock = accountApiService.unblock(userid, id.toLong()) - - return ResponseEntity.ok(unblock) - } - - override suspend fun apiV1AccountsIdUnfollowPost(id: String): ResponseEntity { - val userid = loginUserContextHolder.getLoginUserId() - - val unfollow = accountApiService.unfollow(userid, id.toLong()) - - return ResponseEntity.ok(unfollow) - } - - override suspend fun apiV1AccountsIdRemoveFromFollowersPost(id: String): ResponseEntity { - val userid = loginUserContextHolder.getLoginUserId() - - val removeFromFollowers = accountApiService.removeFromFollowers(userid, id.toLong()) - - return ResponseEntity.ok(removeFromFollowers) - } - - override suspend fun apiV1AccountsUpdateCredentialsPatch(updateCredentials: UpdateCredentials?): ResponseEntity { - val userid = loginUserContextHolder.getLoginUserId() - - val removeFromFollowers = accountApiService.updateProfile(userid, updateCredentials) - - return ResponseEntity.ok(removeFromFollowers) - } - - override suspend fun apiV1FollowRequestsAccountIdAuthorizePost(accountId: String): ResponseEntity { - val userid = loginUserContextHolder.getLoginUserId() - - val acceptFollowRequest = accountApiService.acceptFollowRequest(userid, accountId.toLong()) - - return ResponseEntity.ok(acceptFollowRequest) - } - - override suspend fun apiV1FollowRequestsAccountIdRejectPost(accountId: String): ResponseEntity { - val userid = loginUserContextHolder.getLoginUserId() - - val rejectFollowRequest = accountApiService.rejectFollowRequest(userid, accountId.toLong()) - - return ResponseEntity.ok(rejectFollowRequest) - } - - override fun apiV1FollowRequestsGet(maxId: String?, sinceId: String?, limit: Int?): ResponseEntity> = - runBlocking { - val userid = loginUserContextHolder.getLoginUserId() - - val followRequests = accountApiService.followRequests( - userid, - false, - Page.PageByMaxId( - maxId?.toLongOrNull(), - sinceId?.toLongOrNull(), - limit?.coerceIn(0, 80) ?: 40 - ) - - ) - - val httpHeader = followRequests.toHttpHeader( - { "${applicationConfig.url}/api/v1/follow_requests?max_id=$it" }, - { "${applicationConfig.url}/api/v1/follow_requests?min_id=$it" }, - ) - - if (httpHeader != null) { - return@runBlocking ResponseEntity.ok().header("Link", httpHeader).body(followRequests.asFlow()) - } - - ResponseEntity.ok(followRequests.asFlow()) - } - - override suspend fun apiV1AccountsIdMutePost(id: String): ResponseEntity { - val userid = loginUserContextHolder.getLoginUserId() - - val mute = accountApiService.mute(userid, id.toLong()) - - return ResponseEntity.ok(mute) - } - - override suspend fun apiV1AccountsIdUnmutePost(id: String): ResponseEntity { - val userid = loginUserContextHolder.getLoginUserId() - - val unmute = accountApiService.unmute(userid, id.toLong()) - - return ResponseEntity.ok(unmute) - } - - override fun apiV1MutesGet(maxId: String?, sinceId: String?, limit: Int?): ResponseEntity> = - runBlocking { - val userid = loginUserContextHolder.getLoginUserId() - - val mutes = - accountApiService.mutesAccount( - userid, - Page.PageByMaxId(maxId?.toLongOrNull(), sinceId?.toLongOrNull(), limit?.coerceIn(0, 80) ?: 40) - ) - - val httpHeader = mutes.toHttpHeader( - { "${applicationConfig.url}/api/v1/mutes?max_id=$it" }, - { "${applicationConfig.url}/api/v1/mutes?since_id=$it" }, - ) - - if (httpHeader != null) { - return@runBlocking ResponseEntity.ok().header("Link", httpHeader).body(mutes.asFlow()) - } - - return@runBlocking ResponseEntity.ok(mutes.asFlow()) - } -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/mastodon/interfaces/api/apps/MastodonAppsApiController.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/mastodon/interfaces/api/apps/MastodonAppsApiController.kt deleted file mode 100644 index 8424d6d9..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/mastodon/interfaces/api/apps/MastodonAppsApiController.kt +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright (C) 2024 usbharu - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package dev.usbharu.hideout.mastodon.interfaces.api.apps - -import dev.usbharu.hideout.controller.mastodon.generated.AppApi -import dev.usbharu.hideout.domain.mastodon.model.generated.Application -import dev.usbharu.hideout.domain.mastodon.model.generated.AppsRequest -import dev.usbharu.hideout.mastodon.service.app.AppApiService -import org.springframework.http.HttpStatus -import org.springframework.http.ResponseEntity -import org.springframework.stereotype.Controller -import org.springframework.web.bind.annotation.RequestMapping -import org.springframework.web.bind.annotation.RequestMethod -import org.springframework.web.bind.annotation.RequestParam - -@Controller -class MastodonAppsApiController(private val appApiService: AppApiService) : AppApi { - override suspend fun apiV1AppsPost(appsRequest: AppsRequest): ResponseEntity { - return ResponseEntity( - appApiService.createApp(appsRequest), - HttpStatus.OK - ) - } - - @RequestMapping( - method = [RequestMethod.POST], - value = ["/api/v1/apps"], - produces = ["application/json"], - consumes = ["application/x-www-form-urlencoded"] - ) - suspend fun apiV1AppsPost(@RequestParam map: Map): ResponseEntity { - val appsRequest = - AppsRequest(map.getValue("client_name"), map.getValue("redirect_uris"), map["scopes"], map["website"]) - return ResponseEntity( - appApiService.createApp(appsRequest), - HttpStatus.OK - ) - } -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/mastodon/interfaces/api/filter/MastodonFilterApiController.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/mastodon/interfaces/api/filter/MastodonFilterApiController.kt deleted file mode 100644 index 28f5d3df..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/mastodon/interfaces/api/filter/MastodonFilterApiController.kt +++ /dev/null @@ -1,174 +0,0 @@ -/* - * Copyright (C) 2024 usbharu - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package dev.usbharu.hideout.mastodon.interfaces.api.filter - -import dev.usbharu.hideout.controller.mastodon.generated.FilterApi -import dev.usbharu.hideout.core.infrastructure.springframework.security.LoginUserContextHolder -import dev.usbharu.hideout.domain.mastodon.model.generated.* -import dev.usbharu.hideout.mastodon.service.filter.MastodonFilterApiService -import kotlinx.coroutines.flow.Flow -import org.springframework.http.ResponseEntity -import org.springframework.stereotype.Controller - -@Controller -class MastodonFilterApiController( - private val mastodonFilterApiService: MastodonFilterApiService, - private val loginUserContextHolder: LoginUserContextHolder -) : FilterApi { - - override suspend fun apiV1FiltersIdDelete(id: String): ResponseEntity { - mastodonFilterApiService.deleteV1FilterById(loginUserContextHolder.getLoginUserId(), id.toLong()) - return ResponseEntity.ok().build() - } - - override suspend fun apiV1FiltersIdGet( - id: String - ): ResponseEntity { - return ResponseEntity.ok( - mastodonFilterApiService.getV1FilterById( - loginUserContextHolder.getLoginUserId(), - id.toLong() - ) - ) - } - - override suspend fun apiV1FiltersIdPut( - id: String, - phrase: String?, - context: List?, - irreversible: Boolean?, - wholeWord: Boolean?, - expiresIn: Int? - ): ResponseEntity = super.apiV1FiltersIdPut(id, phrase, context, irreversible, wholeWord, expiresIn) - - override suspend fun apiV1FiltersPost(v1FilterPostRequest: V1FilterPostRequest): ResponseEntity { - return ResponseEntity.ok( - mastodonFilterApiService.createByV1Filter(loginUserContextHolder.getLoginUserId(), v1FilterPostRequest) - ) - } - - override suspend fun apiV2FiltersFilterIdKeywordsPost( - filterId: String, - filterKeywordsPostRequest: FilterKeywordsPostRequest - ): ResponseEntity { - return ResponseEntity.ok( - mastodonFilterApiService.addKeyword( - loginUserContextHolder.getLoginUserId(), - filterId.toLong(), - filterKeywordsPostRequest - ) - ) - } - - override suspend fun apiV2FiltersFilterIdStatusesPost( - filterId: String, - filterStatusRequest: FilterStatusRequest - ): ResponseEntity { - return ResponseEntity.ok( - mastodonFilterApiService.addFilterStatus( - loginUserContextHolder.getLoginUserId(), - filterId.toLong(), - filterStatusRequest - ) - ) - } - - override fun apiV1FiltersGet(): ResponseEntity> = - ResponseEntity.ok(mastodonFilterApiService.v1Filters(loginUserContextHolder.getLoginUserId())) - - override fun apiV2FiltersFilterIdKeywordsGet(filterId: String): ResponseEntity> { - return ResponseEntity.ok( - mastodonFilterApiService.filterKeywords( - loginUserContextHolder.getLoginUserId(), - filterId.toLong() - ) - ) - } - - override fun apiV2FiltersFilterIdStatusesGet(filterId: String): ResponseEntity> { - return ResponseEntity.ok( - mastodonFilterApiService.filterStatuses( - loginUserContextHolder.getLoginUserId(), - filterId.toLong() - ) - ) - } - - override fun apiV2FiltersGet(): ResponseEntity> = - ResponseEntity.ok(mastodonFilterApiService.filters(loginUserContextHolder.getLoginUserId())) - - override suspend fun apiV2FiltersIdDelete(id: String): ResponseEntity { - mastodonFilterApiService.deleteById(loginUserContextHolder.getLoginUserId(), id.toLong()) - return ResponseEntity.ok().build() - } - - override suspend fun apiV2FiltersIdGet(id: String): ResponseEntity = - ResponseEntity.ok(mastodonFilterApiService.getById(loginUserContextHolder.getLoginUserId(), id.toLong())) - - override suspend fun apiV2FiltersIdPut( - id: String, - title: String?, - context: List?, - filterAction: String?, - expiresIn: Int?, - keywordsAttributes: List? - ): ResponseEntity = - super.apiV2FiltersIdPut(id, title, context, filterAction, expiresIn, keywordsAttributes) - - override suspend fun apiV2FiltersKeywordsIdDelete(id: String): ResponseEntity { - mastodonFilterApiService.deleteKeyword(loginUserContextHolder.getLoginUserId(), id.toLong()) - return ResponseEntity.ok().build() - } - - override suspend fun apiV2FiltersKeywordsIdGet(id: String): ResponseEntity { - return ResponseEntity.ok( - mastodonFilterApiService.getKeywordById( - loginUserContextHolder.getLoginUserId(), - id.toLong() - ) - ) - } - - override suspend fun apiV2FiltersKeywordsIdPut( - id: String, - keyword: String?, - wholeWord: Boolean?, - regex: Boolean? - ): ResponseEntity = super.apiV2FiltersKeywordsIdPut(id, keyword, wholeWord, regex) - - override suspend fun apiV2FiltersPost(filterPostRequest: FilterPostRequest): ResponseEntity = - ResponseEntity.ok( - mastodonFilterApiService.createFilter( - loginUserContextHolder.getLoginUserId(), - filterPostRequest - ) - ) - - override suspend fun apiV2FiltersStatusesIdDelete(id: String): ResponseEntity { - mastodonFilterApiService.deleteFilterStatusById(loginUserContextHolder.getLoginUserId(), id.toLong()) - return ResponseEntity.ok().build() - } - - override suspend fun apiV2FiltersStatusesIdGet(id: String): ResponseEntity { - return ResponseEntity.ok( - mastodonFilterApiService.getFilterStatusById( - loginUserContextHolder.getLoginUserId(), - id.toLong() - ) - ) - } -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/mastodon/interfaces/api/instance/MastodonInstanceApiController.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/mastodon/interfaces/api/instance/MastodonInstanceApiController.kt deleted file mode 100644 index 220625ce..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/mastodon/interfaces/api/instance/MastodonInstanceApiController.kt +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright (C) 2024 usbharu - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package dev.usbharu.hideout.mastodon.interfaces.api.instance - -import dev.usbharu.hideout.controller.mastodon.generated.InstanceApi -import dev.usbharu.hideout.domain.mastodon.model.generated.V1Instance -import dev.usbharu.hideout.mastodon.service.instance.InstanceApiService -import org.springframework.http.HttpStatus -import org.springframework.http.ResponseEntity -import org.springframework.stereotype.Controller - -@Controller -class MastodonInstanceApiController(private val instanceApiService: InstanceApiService) : InstanceApi { - override suspend fun apiV1InstanceGet(): ResponseEntity = - ResponseEntity(instanceApiService.v1Instance(), HttpStatus.OK) -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/mastodon/interfaces/api/media/MastodonMediaApiController.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/mastodon/interfaces/api/media/MastodonMediaApiController.kt deleted file mode 100644 index adcdb770..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/mastodon/interfaces/api/media/MastodonMediaApiController.kt +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright (C) 2024 usbharu - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package dev.usbharu.hideout.mastodon.interfaces.api.media - -import dev.usbharu.hideout.controller.mastodon.generated.MediaApi -import dev.usbharu.hideout.domain.mastodon.model.generated.MediaAttachment -import dev.usbharu.hideout.mastodon.service.media.MediaApiService -import org.springframework.http.ResponseEntity -import org.springframework.stereotype.Controller -import org.springframework.web.multipart.MultipartFile - -@Controller -class MastodonMediaApiController(private val mediaApiService: MediaApiService) : MediaApi { - override suspend fun apiV1MediaPost( - file: MultipartFile, - thumbnail: MultipartFile?, - description: String?, - focus: String? - ): ResponseEntity { - return ResponseEntity.ok( - mediaApiService.postMedia( - MediaRequest( - file, - thumbnail, - description, - focus - ) - ) - ) - } -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/mastodon/interfaces/api/media/MediaRequest.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/mastodon/interfaces/api/media/MediaRequest.kt deleted file mode 100644 index d9637193..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/mastodon/interfaces/api/media/MediaRequest.kt +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Copyright (C) 2024 usbharu - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package dev.usbharu.hideout.mastodon.interfaces.api.media - -import org.springframework.web.multipart.MultipartFile - -data class MediaRequest( - val file: MultipartFile, - val thumbnail: MultipartFile?, - val description: String?, - val focus: String? -) diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/mastodon/interfaces/api/notification/MastodonNotificationApiController.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/mastodon/interfaces/api/notification/MastodonNotificationApiController.kt deleted file mode 100644 index 9b0a466a..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/mastodon/interfaces/api/notification/MastodonNotificationApiController.kt +++ /dev/null @@ -1,86 +0,0 @@ -/* - * Copyright (C) 2024 usbharu - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package dev.usbharu.hideout.mastodon.interfaces.api.notification - -import dev.usbharu.hideout.application.config.ApplicationConfig -import dev.usbharu.hideout.application.infrastructure.exposed.Page -import dev.usbharu.hideout.application.infrastructure.exposed.toHttpHeader -import dev.usbharu.hideout.controller.mastodon.generated.NotificationsApi -import dev.usbharu.hideout.core.infrastructure.springframework.security.LoginUserContextHolder -import dev.usbharu.hideout.domain.mastodon.model.generated.Notification -import dev.usbharu.hideout.mastodon.domain.model.NotificationType -import dev.usbharu.hideout.mastodon.service.notification.NotificationApiService -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.asFlow -import kotlinx.coroutines.runBlocking -import org.springframework.http.ResponseEntity -import org.springframework.stereotype.Controller - -@Controller -class MastodonNotificationApiController( - private val loginUserContextHolder: LoginUserContextHolder, - private val notificationApiService: NotificationApiService, - private val applicationConfig: ApplicationConfig -) : NotificationsApi { - override suspend fun apiV1NotificationsClearPost(): ResponseEntity { - notificationApiService.clearAll(loginUserContextHolder.getLoginUserId()) - return ResponseEntity.ok(null) - } - - override fun apiV1NotificationsGet( - maxId: String?, - sinceId: String?, - minId: String?, - limit: Int?, - types: List?, - excludeTypes: List?, - accountId: List? - ): ResponseEntity> = runBlocking { - val notifications = notificationApiService.notifications( - loginUser = loginUserContextHolder.getLoginUserId(), - types = types.orEmpty().mapNotNull { NotificationType.parse(it) }, - excludeTypes = excludeTypes.orEmpty().mapNotNull { NotificationType.parse(it) }, - accountId = accountId.orEmpty().mapNotNull { it.toLongOrNull() }, - page = Page.of( - maxId?.toLongOrNull(), - sinceId?.toLongOrNull(), - minId?.toLongOrNull(), - limit?.coerceIn(0, 80) ?: 40 - ) - ) - - val httpHeader = notifications.toHttpHeader( - { "${applicationConfig.url}/api/v1/notifications?min_id=$it" }, - { "${applicationConfig.url}/api/v1/notifications?max_id=$it" } - ) ?: return@runBlocking ResponseEntity.ok( - notifications.asFlow() - ) - - ResponseEntity.ok().header("Link", httpHeader).body(notifications.asFlow()) - } - - override suspend fun apiV1NotificationsIdDismissPost(id: String): ResponseEntity { - notificationApiService.dismiss(loginUserContextHolder.getLoginUserId(), id.toLong()) - return ResponseEntity.ok(null) - } - - override suspend fun apiV1NotificationsIdGet(id: String): ResponseEntity { - val notification = notificationApiService.fingById(loginUserContextHolder.getLoginUserId(), id.toLong()) - - return ResponseEntity.ok(notification) - } -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/mastodon/interfaces/api/status/MastodonStatusesApiContoller.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/mastodon/interfaces/api/status/MastodonStatusesApiContoller.kt deleted file mode 100644 index 20858ec6..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/mastodon/interfaces/api/status/MastodonStatusesApiContoller.kt +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Copyright (C) 2024 usbharu - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package dev.usbharu.hideout.mastodon.interfaces.api.status - -import dev.usbharu.hideout.controller.mastodon.generated.StatusApi -import dev.usbharu.hideout.core.infrastructure.springframework.security.LoginUserContextHolder -import dev.usbharu.hideout.domain.mastodon.model.generated.Status -import dev.usbharu.hideout.mastodon.service.status.StatusesApiService -import org.springframework.http.HttpStatus -import org.springframework.http.ResponseEntity -import org.springframework.stereotype.Controller - -@Controller -class MastodonStatusesApiContoller( - private val statusesApiService: StatusesApiService, - private val loginUserContextHolder: LoginUserContextHolder -) : StatusApi { - override suspend fun apiV1StatusesPost( - devUsbharuHideoutDomainModelMastodonStatusesRequest: StatusesRequest - ): ResponseEntity { - val userid = loginUserContextHolder.getLoginUserId() - - return ResponseEntity( - statusesApiService.postStatus( - devUsbharuHideoutDomainModelMastodonStatusesRequest, - userid - ), - HttpStatus.OK - ) - } - - override suspend fun apiV1StatusesIdEmojiReactionsEmojiDelete(id: String, emoji: String): ResponseEntity { - val uid = - loginUserContextHolder.getLoginUserId() - - return ResponseEntity.ok(statusesApiService.removeEmojiReactions(id.toLong(), uid, emoji)) - } - - override suspend fun apiV1StatusesIdEmojiReactionsEmojiPut(id: String, emoji: String): ResponseEntity { - val uid = - loginUserContextHolder.getLoginUserId() - - return ResponseEntity.ok(statusesApiService.emojiReactions(id.toLong(), uid, emoji)) - } - - override suspend fun apiV1StatusesIdGet(id: String): ResponseEntity { - val uid = loginUserContextHolder.getLoginUserIdOrNull() - - return ResponseEntity.ok(statusesApiService.findById(id.toLong(), uid)) - } -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/mastodon/interfaces/api/status/StatusQuery.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/mastodon/interfaces/api/status/StatusQuery.kt deleted file mode 100644 index 19d2cc8f..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/mastodon/interfaces/api/status/StatusQuery.kt +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (C) 2024 usbharu - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package dev.usbharu.hideout.mastodon.interfaces.api.status - -data class StatusQuery( - val postId: Long, - val replyId: Long?, - val repostId: Long?, - val mediaIds: List, - val emojiIds: List -) diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/mastodon/interfaces/api/status/StatusesRequest.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/mastodon/interfaces/api/status/StatusesRequest.kt deleted file mode 100644 index 1005680d..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/mastodon/interfaces/api/status/StatusesRequest.kt +++ /dev/null @@ -1,124 +0,0 @@ -/* - * Copyright (C) 2024 usbharu - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package dev.usbharu.hideout.mastodon.interfaces.api.status - -import com.fasterxml.jackson.annotation.JsonProperty -import dev.usbharu.hideout.core.domain.model.post.Visibility -import dev.usbharu.hideout.domain.mastodon.model.generated.Status -import dev.usbharu.hideout.domain.mastodon.model.generated.StatusesRequestPoll -import dev.usbharu.hideout.mastodon.interfaces.api.status.StatusesRequest.Visibility.* - -@Suppress("VariableNaming", "EnumEntryName") -class StatusesRequest { - @JsonProperty("status") - var status: String? = null - - @JsonProperty("media_ids") - var media_ids: List = emptyList() - - @JsonProperty("poll") - var poll: StatusesRequestPoll? = null - - @JsonProperty("in_reply_to_id") - var in_reply_to_id: String? = null - - @JsonProperty("sensitive") - var sensitive: Boolean? = null - - @JsonProperty("spoiler_text") - var spoiler_text: String? = null - - @JsonProperty("visibility") - var visibility: Visibility? = null - - @JsonProperty("language") - var language: String? = null - - @JsonProperty("scheduled_at") - var scheduled_at: String? = null - override fun equals(other: Any?): Boolean { - if (this === other) return true - if (other !is StatusesRequest) return false - - if (status != other.status) return false - if (media_ids != other.media_ids) return false - if (poll != other.poll) return false - if (in_reply_to_id != other.in_reply_to_id) return false - if (sensitive != other.sensitive) return false - if (spoiler_text != other.spoiler_text) return false - if (visibility != other.visibility) return false - if (language != other.language) return false - if (scheduled_at != other.scheduled_at) return false - - return true - } - - override fun hashCode(): Int { - var result = status?.hashCode() ?: 0 - result = 31 * result + media_ids.hashCode() - result = 31 * result + (poll?.hashCode() ?: 0) - result = 31 * result + (in_reply_to_id?.hashCode() ?: 0) - result = 31 * result + (sensitive?.hashCode() ?: 0) - result = 31 * result + (spoiler_text?.hashCode() ?: 0) - result = 31 * result + (visibility?.hashCode() ?: 0) - result = 31 * result + (language?.hashCode() ?: 0) - result = 31 * result + (scheduled_at?.hashCode() ?: 0) - return result - } - - override fun toString(): String { - return "StatusesRequest(" + - "status=$status, " + - "media_ids=$media_ids, " + - "poll=$poll, " + - "in_reply_to_id=$in_reply_to_id, " + - "sensitive=$sensitive, " + - "spoiler_text=$spoiler_text, " + - "visibility=$visibility, " + - "language=$language, " + - "scheduled_at=$scheduled_at" + - ")" - } - - @Suppress("EnumNaming", "EnumEntryNameCase") - enum class Visibility { - `public`, - unlisted, - private, - direct - } -} - -fun StatusesRequest.Visibility?.toPostVisibility(): Visibility { - return when (this) { - public -> Visibility.PUBLIC - unlisted -> Visibility.UNLISTED - private -> Visibility.FOLLOWERS - direct -> Visibility.DIRECT - null -> Visibility.PUBLIC - } -} - -fun StatusesRequest.Visibility?.toStatusVisibility(): Status.Visibility { - return when (this) { - public -> Status.Visibility.public - unlisted -> Status.Visibility.unlisted - private -> Status.Visibility.private - direct -> Status.Visibility.direct - null -> Status.Visibility.public - } -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/mastodon/interfaces/api/timeline/MastodonTimelineApiController.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/mastodon/interfaces/api/timeline/MastodonTimelineApiController.kt deleted file mode 100644 index 7bbc8f8d..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/mastodon/interfaces/api/timeline/MastodonTimelineApiController.kt +++ /dev/null @@ -1,95 +0,0 @@ -/* - * Copyright (C) 2024 usbharu - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package dev.usbharu.hideout.mastodon.interfaces.api.timeline - -import dev.usbharu.hideout.application.config.ApplicationConfig -import dev.usbharu.hideout.application.infrastructure.exposed.Page -import dev.usbharu.hideout.application.infrastructure.exposed.toHttpHeader -import dev.usbharu.hideout.controller.mastodon.generated.TimelineApi -import dev.usbharu.hideout.core.infrastructure.springframework.security.LoginUserContextHolder -import dev.usbharu.hideout.domain.mastodon.model.generated.Status -import dev.usbharu.hideout.mastodon.service.timeline.TimelineApiService -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.asFlow -import kotlinx.coroutines.runBlocking -import org.springframework.http.HttpStatus -import org.springframework.http.ResponseEntity -import org.springframework.stereotype.Controller - -@Controller -class MastodonTimelineApiController( - private val timelineApiService: TimelineApiService, - private val loginUserContextHolder: LoginUserContextHolder, - private val applicationConfig: ApplicationConfig, -) : TimelineApi { - override fun apiV1TimelinesHomeGet( - maxId: String?, - sinceId: String?, - minId: String?, - limit: Int? - ): ResponseEntity> = runBlocking { - val homeTimeline = timelineApiService.homeTimeline( - userId = loginUserContextHolder.getLoginUserId(), - page = Page.of( - maxId = maxId?.toLongOrNull(), - minId = minId?.toLongOrNull(), - sinceId = sinceId?.toLongOrNull(), - limit = limit?.coerceIn(0, 80) ?: 40 - ) - ) - - val httpHeader = homeTimeline.toHttpHeader( - { "${applicationConfig.url}/api/v1/home?max_id=$it" }, - { "${applicationConfig.url}/api/v1/home?min_id=$it" } - ) ?: return@runBlocking ResponseEntity( - homeTimeline.asFlow(), - HttpStatus.OK - ) - ResponseEntity.ok().header("Link", httpHeader).body(homeTimeline.asFlow()) - } - - override fun apiV1TimelinesPublicGet( - local: Boolean?, - remote: Boolean?, - onlyMedia: Boolean?, - maxId: String?, - sinceId: String?, - minId: String?, - limit: Int? - ): ResponseEntity> = runBlocking { - val publicTimeline = timelineApiService.publicTimeline( - localOnly = local ?: false, - remoteOnly = remote ?: false, - mediaOnly = onlyMedia ?: false, - page = Page.of( - maxId = maxId?.toLongOrNull(), - minId = minId?.toLongOrNull(), - sinceId = sinceId?.toLongOrNull(), - limit = limit?.coerceIn(0, 80) ?: 40 - ) - ) - - val httpHeader = publicTimeline.toHttpHeader( - { "${applicationConfig.url}/api/v1/public?max_id=$it" }, - { "${applicationConfig.url}/api/v1/public?min_id=$it" } - ) ?: return@runBlocking ResponseEntity( - publicTimeline.asFlow(), - HttpStatus.OK - ) - ResponseEntity.ok().header("Link", httpHeader).body(publicTimeline.asFlow()) - } -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/mastodon/query/AccountQueryService.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/mastodon/query/AccountQueryService.kt deleted file mode 100644 index 61b49950..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/mastodon/query/AccountQueryService.kt +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Copyright (C) 2024 usbharu - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -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/hideout-core/src/main/kotlin/dev/usbharu/hideout/mastodon/query/StatusQueryService.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/mastodon/query/StatusQueryService.kt deleted file mode 100644 index e5640509..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/mastodon/query/StatusQueryService.kt +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright (C) 2024 usbharu - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package dev.usbharu.hideout.mastodon.query - -import dev.usbharu.hideout.application.infrastructure.exposed.Page -import dev.usbharu.hideout.application.infrastructure.exposed.PaginationList -import dev.usbharu.hideout.domain.mastodon.model.generated.Status -import dev.usbharu.hideout.mastodon.interfaces.api.status.StatusQuery - -interface StatusQueryService { - suspend fun findByPostIds(ids: List): List - suspend fun findByPostIdsWithMediaIds(statusQueries: List): List - - @Suppress("LongParameterList") - suspend fun accountsStatus( - accountId: Long, - onlyMedia: Boolean = false, - excludeReplies: Boolean = false, - excludeReblogs: Boolean = false, - pinned: Boolean = false, - tagged: String?, - includeFollowers: Boolean = false, - page: Page - ): PaginationList - - suspend fun findByPostId(id: Long): Status? -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/mastodon/service/account/AccountApiService.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/mastodon/service/account/AccountApiService.kt deleted file mode 100644 index 0e3afc2f..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/mastodon/service/account/AccountApiService.kt +++ /dev/null @@ -1,379 +0,0 @@ -/* - * Copyright (C) 2024 usbharu - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package dev.usbharu.hideout.mastodon.service.account - -import dev.usbharu.hideout.application.external.Transaction -import dev.usbharu.hideout.application.infrastructure.exposed.Page -import dev.usbharu.hideout.application.infrastructure.exposed.PaginationList -import dev.usbharu.hideout.core.domain.exception.resource.UserNotFoundException -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.domain.exception.AccountNotFoundException -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 -import kotlin.math.min - -@Service -@Suppress("TooManyFunctions") -interface AccountApiService { - - @Suppress("LongParameterList") - suspend fun accountsStatuses( - userid: Long, - onlyMedia: Boolean, - excludeReplies: Boolean, - excludeReblogs: Boolean, - pinned: Boolean, - tagged: String?, - loginUser: Long?, - page: Page - ): PaginationList - - suspend fun verifyCredentials(userid: Long): CredentialAccount - suspend fun registerAccount(userCreateDto: UserCreateDto): Unit - suspend fun follow(loginUser: Long, followTargetUserId: Long): Relationship - suspend fun account(id: Long): Account - suspend fun relationships(userid: Long, id: List, withSuspended: Boolean): List - - /** - * ブロック操作を行う - * - * @param userid ブロック操作を行ったユーザーid - * @param target ブロック対象のユーザーid - * @return ブロック後のブロック対象ユーザーとの[Relationship] - */ - suspend fun block(userid: Long, target: Long): Relationship - 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 - - suspend fun followRequests( - loginUser: Long, - withIgnore: Boolean, - pageByMaxId: Page.PageByMaxId - ): PaginationList - - suspend fun acceptFollowRequest(loginUser: Long, target: Long): Relationship - suspend fun rejectFollowRequest(loginUser: Long, target: Long): Relationship - suspend fun mute(userid: Long, target: Long): Relationship - suspend fun unmute(userid: Long, target: Long): Relationship - suspend fun mutesAccount(userid: Long, pageByMaxId: Page.PageByMaxId): PaginationList -} - -@Service -class AccountApiServiceImpl( - private val accountService: AccountService, - private val transaction: Transaction, - private val userService: UserService, - private val statusQueryService: StatusQueryService, - private val relationshipService: RelationshipService, - private val relationshipRepository: RelationshipRepository, - private val mediaService: MediaService -) : - AccountApiService { - - override suspend fun accountsStatuses( - userid: Long, - onlyMedia: Boolean, - excludeReplies: Boolean, - excludeReblogs: Boolean, - pinned: Boolean, - tagged: String?, - loginUser: Long?, - page: Page - ): PaginationList { - val canViewFollowers = if (loginUser == null) { - false - } else if (loginUser == userid) { - true - } else { - transaction.transaction { - isFollowing(loginUser, userid) - } - } - - return transaction.transaction { - statusQueryService.accountsStatus( - accountId = userid, - onlyMedia = onlyMedia, - excludeReplies = excludeReplies, - excludeReblogs = excludeReblogs, - pinned = pinned, - tagged = tagged, - includeFollowers = canViewFollowers, - page = page - ) - } - } - - override suspend fun verifyCredentials(userid: Long): CredentialAccount = transaction.transaction { - userService.updateUserStatistics(userid) - - val account = accountService.findById(userid) - from(account) - } - - override suspend fun registerAccount(userCreateDto: UserCreateDto) { - userService.createLocalUser(UserCreateDto(userCreateDto.name, userCreateDto.name, "", userCreateDto.password)) - } - - override suspend fun follow(loginUser: Long, followTargetUserId: Long): Relationship = transaction.transaction { - relationshipService.followRequest(loginUser, followTargetUserId) - - return@transaction fetchRelationship(loginUser, followTargetUserId) - } - - override suspend fun account(id: Long): Account { - return try { - transaction.transaction { - userService.updateUserStatistics(id) - return@transaction accountService.findById(id) - } - } catch (_: UserNotFoundException) { - throw AccountNotFoundException.ofId(id) - } - } - - override suspend fun relationships(userid: Long, id: List, withSuspended: Boolean): List = - transaction.transaction { - if (id.isEmpty()) { - return@transaction emptyList() - } - - logger.warn("id is too long! ({}) truncate to 20", id.size) - - val subList = id.subList(0, min(id.size, 20)) - - return@transaction subList.map { - fetchRelationship(userid, it) - } - } - - override suspend fun block(userid: Long, target: Long) = transaction.transaction { - relationshipService.block(userid, target) - - fetchRelationship(userid, target) - } - - override suspend fun unblock(userid: Long, target: Long): Relationship = transaction.transaction { - relationshipService.unblock(userid, target) - - return@transaction fetchRelationship(userid, target) - } - - override suspend fun unfollow(userid: Long, target: Long): Relationship = transaction.transaction { - relationshipService.unfollow(userid, target) - - return@transaction fetchRelationship(userid, target) - } - - override suspend fun removeFromFollowers(userid: Long, target: Long): Relationship = transaction.transaction { - relationshipService.rejectFollowRequest(userid, target) - - 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, - locked = updateCredentials?.locked ?: account.locked, - autoAcceptFolloweeFollowRequest = false - ) - userService.updateUser(userid, updateUserDto) - - accountService.findById(userid) - } - - override suspend fun followRequests( - loginUser: Long, - withIgnore: Boolean, - pageByMaxId: Page.PageByMaxId - ): PaginationList = transaction.transaction { - val request = - relationshipRepository.findByTargetIdAndFollowRequestAndIgnoreFollowRequest( - loginUser, - true, - withIgnore, - pageByMaxId - ) - val actorIds = request.map { it.actorId } - - return@transaction PaginationList(accountService.findByIds(actorIds), request.next, request.prev) - } - - 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) - } - - override suspend fun mute(userid: Long, target: Long): Relationship = transaction.transaction { - relationshipService.mute(userid, target) - - return@transaction fetchRelationship(userid, target) - } - - override suspend fun unmute(userid: Long, target: Long): Relationship = transaction.transaction { - relationshipService.mute(userid, target) - - return@transaction fetchRelationship(userid, target) - } - - override suspend fun mutesAccount(userid: Long, pageByMaxId: Page.PageByMaxId): PaginationList { - val mutedAccounts = relationshipRepository.findByActorIdAndMuting(userid, true, pageByMaxId) - - return PaginationList( - accountService.findByIds(mutedAccounts.map { it.targetActorId }), - mutedAccounts.next, - mutedAccounts.prev - ) - } - - private fun from(account: Account): CredentialAccount { - return CredentialAccount( - id = account.id, - username = account.username, - acct = account.acct, - url = account.url, - displayName = account.displayName, - note = account.note, - avatar = account.avatar, - avatarStatic = account.avatarStatic, - header = account.header, - headerStatic = account.headerStatic, - locked = account.locked, - fields = account.fields, - emojis = account.emojis, - bot = account.bot, - group = account.group, - discoverable = account.discoverable, - createdAt = account.createdAt, - lastStatusAt = account.lastStatusAt, - statusesCount = account.statusesCount, - followersCount = account.followersCount, - noindex = account.noindex, - moved = account.moved, - suspendex = account.suspendex, - limited = account.limited, - followingCount = account.followingCount, - source = AccountSource( - account.note, - account.fields, - AccountSource.Privacy.public, - false, - 0 - ), - role = Role(0, "Admin", "", 32) - ) - } - - private suspend fun fetchRelationship(userid: Long, targetId: Long): Relationship { - val relationship = relationshipRepository.findByUserIdAndTargetUserId(userid, targetId) - ?: dev.usbharu.hideout.core.domain.model.relationship.Relationship( - actorId = userid, - targetActorId = targetId, - following = false, - blocking = false, - muting = false, - followRequest = false, - ignoreFollowRequestToTarget = false - ) - - val inverseRelationship = relationshipRepository.findByUserIdAndTargetUserId(targetId, userid) - ?: dev.usbharu.hideout.core.domain.model.relationship.Relationship( - actorId = targetId, - targetActorId = userid, - following = false, - blocking = false, - muting = false, - followRequest = false, - ignoreFollowRequestToTarget = false - ) - - userService.updateUserStatistics(userid) - userService.updateUserStatistics(targetId) - - return Relationship( - id = targetId.toString(), - following = relationship.following, - showingReblogs = true, - notifying = false, - followedBy = inverseRelationship.following, - blocking = relationship.blocking, - blockedBy = inverseRelationship.blocking, - muting = relationship.muting, - mutingNotifications = relationship.muting, - requested = relationship.followRequest, - domainBlocking = false, - endorsed = false, - note = "" - ) - } - - private suspend fun isFollowing(userid: Long, target: Long): Boolean = - relationshipRepository.findByUserIdAndTargetUserId(userid, target)?.following ?: false - - companion object { - private val logger = LoggerFactory.getLogger(AccountApiServiceImpl::class.java) - } -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/mastodon/service/account/AccountService.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/mastodon/service/account/AccountService.kt deleted file mode 100644 index e5a51f47..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/mastodon/service/account/AccountService.kt +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright (C) 2024 usbharu - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package dev.usbharu.hideout.mastodon.service.account - -import dev.usbharu.hideout.domain.mastodon.model.generated.Account -import dev.usbharu.hideout.mastodon.query.AccountQueryService -import org.springframework.stereotype.Service - -@Service -interface AccountService { - suspend fun findById(id: Long): Account - suspend fun findByIds(ids: List): List -} - -@Service -class AccountServiceImpl( - private val accountQueryService: AccountQueryService -) : AccountService { - override suspend fun findById(id: Long): Account = - accountQueryService.findById(id) ?: throw IllegalArgumentException("Account $id not found.") - - override suspend fun findByIds(ids: List): List = accountQueryService.findByIds(ids) -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/mastodon/service/app/AppApiService.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/mastodon/service/app/AppApiService.kt deleted file mode 100644 index 3127d169..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/mastodon/service/app/AppApiService.kt +++ /dev/null @@ -1,85 +0,0 @@ -/* - * Copyright (C) 2024 usbharu - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package dev.usbharu.hideout.mastodon.service.app - -import dev.usbharu.hideout.application.external.Transaction -import dev.usbharu.hideout.core.infrastructure.springframework.oauth2.SecureTokenGenerator -import dev.usbharu.hideout.domain.mastodon.model.generated.Application -import dev.usbharu.hideout.domain.mastodon.model.generated.AppsRequest -import org.springframework.security.crypto.password.PasswordEncoder -import org.springframework.security.oauth2.core.AuthorizationGrantType -import org.springframework.security.oauth2.core.ClientAuthenticationMethod -import org.springframework.security.oauth2.server.authorization.client.RegisteredClient -import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository -import org.springframework.security.oauth2.server.authorization.settings.ClientSettings -import org.springframework.security.oauth2.server.authorization.settings.TokenSettings -import org.springframework.stereotype.Service -import java.time.Duration -import java.util.* - -@Service -interface AppApiService { - suspend fun createApp(appsRequest: AppsRequest): Application -} - -@Service -class AppApiServiceImpl( - private val registeredClientRepository: RegisteredClientRepository, - private val secureTokenGenerator: SecureTokenGenerator, - private val passwordEncoder: PasswordEncoder, - private val transaction: Transaction -) : AppApiService { - override suspend fun createApp(appsRequest: AppsRequest): Application { - return transaction.transaction { - val id = UUID.randomUUID().toString() - val clientSecret = secureTokenGenerator.generate() - val registeredClient = RegisteredClient.withId(id) - .clientId(id) - .clientSecret(passwordEncoder.encode(clientSecret)) - .clientName(appsRequest.clientName) - .clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_POST) - .clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_JWT) - .clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC) - .authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE) - .authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS) - .authorizationGrantType(AuthorizationGrantType.REFRESH_TOKEN) - .redirectUri(appsRequest.redirectUris) - .tokenSettings( - TokenSettings.builder() - .accessTokenTimeToLive( - Duration.ofSeconds(31536000000) - ) - .build() - ) - .clientSettings(ClientSettings.builder().requireAuthorizationConsent(true).build()) - .scopes { it.addAll(parseScope(appsRequest.scopes.orEmpty())) } - .build() - registeredClientRepository.save(registeredClient) - - Application( - name = appsRequest.clientName, - vapidKey = "invalid-vapid-key", - website = appsRequest.website, - clientId = id, - clientSecret = clientSecret, - redirectUri = appsRequest.redirectUris - ) - } - } - - private fun parseScope(string: String): Set = string.split(" ").toSet() -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/mastodon/service/filter/MastodonFilterApiService.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/mastodon/service/filter/MastodonFilterApiService.kt deleted file mode 100644 index 0f4c4157..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/mastodon/service/filter/MastodonFilterApiService.kt +++ /dev/null @@ -1,301 +0,0 @@ -/* - * Copyright (C) 2024 usbharu - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package dev.usbharu.hideout.mastodon.service.filter - -import dev.usbharu.hideout.core.domain.model.filter.FilterAction.hide -import dev.usbharu.hideout.core.domain.model.filter.FilterAction.warn -import dev.usbharu.hideout.core.domain.model.filter.FilterMode -import dev.usbharu.hideout.core.domain.model.filter.FilterRepository -import dev.usbharu.hideout.core.domain.model.filter.FilterType.* -import dev.usbharu.hideout.core.domain.model.filterkeyword.FilterKeywordRepository -import dev.usbharu.hideout.core.query.model.FilterQueryModel -import dev.usbharu.hideout.core.query.model.FilterQueryService -import dev.usbharu.hideout.core.service.filter.MuteService -import dev.usbharu.hideout.domain.mastodon.model.generated.* -import dev.usbharu.hideout.domain.mastodon.model.generated.FilterPostRequest.FilterAction -import dev.usbharu.hideout.domain.mastodon.model.generated.V1Filter.Context -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.asFlow -import kotlinx.coroutines.flow.emptyFlow -import kotlinx.coroutines.runBlocking -import org.springframework.stereotype.Service - -@Suppress("TooManyFunctions") -interface MastodonFilterApiService { - fun v1Filters(userId: Long): Flow - - suspend fun deleteV1FilterById(userId: Long, id: Long) - - suspend fun getV1FilterById(userId: Long, id: Long): V1Filter? - - suspend fun createByV1Filter(userId: Long, v1FilterRequest: V1FilterPostRequest): V1Filter - - fun filterKeywords(userId: Long, filterId: Long): Flow - - suspend fun addKeyword(userId: Long, filterId: Long, keyword: FilterKeywordsPostRequest): FilterKeyword - - fun filterStatuses(userId: Long, filterId: Long): Flow - - suspend fun addFilterStatus(userId: Long, filterId: Long, filterStatusRequest: FilterStatusRequest): FilterStatus - - fun filters(userId: Long): Flow - - suspend fun deleteById(userId: Long, filterId: Long) - - suspend fun getById(userId: Long, filterId: Long): Filter? - - suspend fun deleteKeyword(userId: Long, keywordId: Long) - - suspend fun getKeywordById(userId: Long, keywordId: Long): FilterKeyword? - - suspend fun createFilter(userId: Long, filterPostRequest: FilterPostRequest): Filter - - suspend fun deleteFilterStatusById(userId: Long, filterPostsId: Long) - - suspend fun getFilterStatusById(userId: Long, filterPostsId: Long): FilterStatus? -} - -@Service -class MastodonFilterApiServiceImpl( - private val muteService: MuteService, - private val filterQueryService: FilterQueryService, - private val filterRepository: FilterRepository, - private val filterKeywordRepository: FilterKeywordRepository -) : MastodonFilterApiService { - override fun v1Filters(userId: Long): Flow { - return runBlocking { filterQueryService.findByUserId(userId) }.flatMap { filterQueryModel -> - filterQueryModel.keywords.map { - V1Filter( - id = it.id.toString(), - phrase = it.keyword, - context = filterQueryModel.context.map { filterType -> - when (filterType) { - home -> Context.home - notifications -> Context.notifications - public -> Context.public - thread -> Context.thread - account -> Context.account - } - }, - expiresAt = null, - irreversible = false, - wholeWord = (it.mode != FilterMode.WHOLE_WORD).not() - ) - } - }.asFlow() - } - - override suspend fun deleteV1FilterById(userId: Long, id: Long) { - val keywordId = filterQueryService.findByUserIdAndKeywordId(userId, id)?.keywords?.singleOrNull()?.id ?: return - - filterKeywordRepository.deleteById(keywordId) - } - - override suspend fun getV1FilterById(userId: Long, id: Long): V1Filter? { - val filterQueryModel = filterQueryService.findByUserIdAndKeywordId(userId, id) ?: return null - - val filterKeyword = filterQueryModel.keywords.firstOrNull() ?: return null - - return v1Filter(filterQueryModel, filterKeyword) - } - - private fun v1Filter( - filterQueryModel: FilterQueryModel, - filterKeyword: dev.usbharu.hideout.core.domain.model.filterkeyword.FilterKeyword - ) = V1Filter( - id = filterQueryModel.id.toString(), - phrase = filterKeyword.keyword, - context = filterQueryModel.context.map { - when (it) { - home -> Context.home - notifications -> Context.notifications - public -> Context.public - thread -> Context.thread - account -> Context.account - } - }, - expiresAt = null, - irreversible = false, - wholeWord = filterKeyword.mode == FilterMode.WHOLE_WORD - ) - - override suspend fun createByV1Filter(userId: Long, v1FilterRequest: V1FilterPostRequest): V1Filter { - val createFilter = muteService.createFilter( - title = v1FilterRequest.phrase, - context = v1FilterRequest.context.map { - when (it) { - V1FilterPostRequest.Context.home -> home - V1FilterPostRequest.Context.notifications -> notifications - V1FilterPostRequest.Context.public -> public - V1FilterPostRequest.Context.thread -> thread - V1FilterPostRequest.Context.account -> account - } - }, - action = warn, - keywords = listOf( - dev.usbharu.hideout.core.service.filter.FilterKeyword( - v1FilterRequest.phrase, - if (v1FilterRequest.wholeWord == true) { - FilterMode.WHOLE_WORD - } else { - FilterMode.NONE - } - ) - ), - loginUser = userId - ) - - return v1Filter(createFilter, createFilter.keywords.first()) - } - - override fun filterKeywords(userId: Long, filterId: Long): Flow = - runBlocking { filterQueryService.findByUserIdAndId(userId, filterId) } - ?.keywords - ?.map { - toFilterKeyword( - it - ) - } - .orEmpty() - .asFlow() - - override suspend fun addKeyword(userId: Long, filterId: Long, keyword: FilterKeywordsPostRequest): FilterKeyword { - val id = filterQueryService.findByUserIdAndId(userId, filterId)?.id - ?: throw IllegalArgumentException("filter not found.") - - val filterKeyword = filterKeywordRepository.save( - dev.usbharu.hideout.core.domain.model.filterkeyword.FilterKeyword( - id = filterKeywordRepository.generateId(), - filterId = id, - keyword = keyword.keyword, - mode = if (keyword.regex == true) { - FilterMode.REGEX - } else if (keyword.wholeWord == true) { - FilterMode.WHOLE_WORD - } else { - FilterMode.NONE - } - ) - ) - - return toFilterKeyword(filterKeyword) - } - - override fun filterStatuses(userId: Long, filterId: Long): Flow = emptyFlow() - - override suspend fun addFilterStatus( - userId: Long, - filterId: Long, - filterStatusRequest: FilterStatusRequest - ): FilterStatus { - TODO() - } - - override fun filters(userId: Long): Flow = - runBlocking { filterQueryService.findByUserId(userId) }.map { filterQueryModel -> - toFilter(filterQueryModel) - }.asFlow() - - private fun toFilter(filterQueryModel: FilterQueryModel) = Filter( - id = filterQueryModel.id.toString(), - title = filterQueryModel.name, - context = filterQueryModel.context.map { - when (it) { - home -> Filter.Context.home - notifications -> Filter.Context.notifications - public -> Filter.Context.public - thread -> Filter.Context.thread - account -> Filter.Context.account - } - }, - expiresAt = null, - filterAction = when (filterQueryModel.filterAction) { - warn -> Filter.FilterAction.warn - hide -> Filter.FilterAction.hide - }, - keywords = filterQueryModel.keywords.map { - toFilterKeyword(it) - }, - statuses = null - ) - - private fun toFilterKeyword(it: dev.usbharu.hideout.core.domain.model.filterkeyword.FilterKeyword) = FilterKeyword( - it.id.toString(), - it.keyword, - it.mode == FilterMode.WHOLE_WORD - ) - - override suspend fun deleteById(userId: Long, filterId: Long) = - filterRepository.deleteByUserIdAndId(userId, filterId) - - override suspend fun getById(userId: Long, filterId: Long): Filter? = - filterQueryService.findByUserIdAndId(userId, filterId)?.let { toFilter(it) } - - override suspend fun deleteKeyword(userId: Long, keywordId: Long) { - val id = filterQueryService.findByUserIdAndKeywordId(userId, keywordId)?.keywords?.singleOrNull()?.id ?: return - - filterKeywordRepository.deleteById(id) - } - - override suspend fun getKeywordById(userId: Long, keywordId: Long): FilterKeyword? { - return filterQueryService - .findByUserIdAndKeywordId(userId, keywordId) - ?.keywords - ?.firstOrNull() - ?.let { toFilterKeyword(it) } - } - - override suspend fun createFilter(userId: Long, filterPostRequest: FilterPostRequest): Filter { - val keywords = filterPostRequest.keywordsAttributes.orEmpty().map { - dev.usbharu.hideout.core.service.filter.FilterKeyword( - it.keyword, - if (it.regex == true) { - FilterMode.REGEX - } else if (it.wholeWord == true) { - FilterMode.WHOLE_WORD - } else { - FilterMode.NONE - } - ) - } - return toFilter( - muteService.createFilter( - title = filterPostRequest.title, - context = filterPostRequest.context.map { - when (it) { - FilterPostRequest.Context.home -> home - FilterPostRequest.Context.notifications -> notifications - FilterPostRequest.Context.public -> public - FilterPostRequest.Context.thread -> thread - FilterPostRequest.Context.account -> account - } - }, - action = when (filterPostRequest.filterAction) { - FilterAction.warn -> warn - FilterAction.hide -> warn - null -> warn - }, - keywords = keywords, - loginUser = userId - ) - ) - } - - override suspend fun deleteFilterStatusById(userId: Long, filterPostsId: Long) = Unit - - override suspend fun getFilterStatusById(userId: Long, filterPostsId: Long): FilterStatus? = null -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/mastodon/service/instance/InstanceApiService.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/mastodon/service/instance/InstanceApiService.kt deleted file mode 100644 index d24fe791..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/mastodon/service/instance/InstanceApiService.kt +++ /dev/null @@ -1,99 +0,0 @@ -/* - * Copyright (C) 2024 usbharu - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package dev.usbharu.hideout.mastodon.service.instance - -import dev.usbharu.hideout.application.config.ApplicationConfig -import dev.usbharu.hideout.domain.mastodon.model.generated.* -import org.springframework.stereotype.Service - -@Service -interface InstanceApiService { - suspend fun v1Instance(): V1Instance -} - -@Service -class InstanceApiServiceImpl(private val applicationConfig: ApplicationConfig) : InstanceApiService { - @Suppress("LongMethod") - override suspend fun v1Instance(): V1Instance { - val url = applicationConfig.url - return V1Instance( - uri = url.host, - title = "Hideout Server", - shortDescription = "Hideout test server", - description = "This server is operated for testing of Hideout." + - " We are not responsible for any events that occur when associating with this server", - email = "i@usbharu.dev", - version = "0.0.1", - urls = V1InstanceUrls("wss://${url.host}"), - stats = V1InstanceStats(1, 0, 0), - thumbnail = null, - languages = listOf("ja-JP"), - registrations = false, - approvalRequired = false, - invitesEnabled = false, - configuration = V1InstanceConfiguration( - accounts = V1InstanceConfigurationAccounts(1), - statuses = V1InstanceConfigurationStatuses( - 300, - 4, - 23 - ), - mediaAttachments = V1InstanceConfigurationMediaAttachments( - emptyList(), - 0, - 0, - 0, - 0 - ), - polls = V1InstanceConfigurationPolls( - 0, - 0, - 0, - 0 - ) - ), - contactAccount = Account( - id = "0", - username = "", - acct = "", - url = "", - displayName = "", - note = "", - avatar = "", - avatarStatic = "", - header = "", - headerStatic = "", - locked = false, - fields = emptyList(), - emojis = emptyList(), - bot = false, - group = false, - discoverable = false, - createdAt = "0", - lastStatusAt = "0", - statusesCount = 1, - followersCount = 0, - noindex = false, - moved = false, - suspendex = false, - limited = false, - followingCount = 0 - ), - rules = emptyList() - ) - } -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/mastodon/service/media/MediaApiService.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/mastodon/service/media/MediaApiService.kt deleted file mode 100644 index 8e806d4d..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/mastodon/service/media/MediaApiService.kt +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Copyright (C) 2024 usbharu - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package dev.usbharu.hideout.mastodon.service.media - -import dev.usbharu.hideout.domain.mastodon.model.generated.MediaAttachment -import dev.usbharu.hideout.mastodon.interfaces.api.media.MediaRequest -import org.springframework.stereotype.Service - -@Service -interface MediaApiService { - suspend fun postMedia(mediaRequest: MediaRequest): MediaAttachment -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/mastodon/service/media/MediaApiServiceImpl.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/mastodon/service/media/MediaApiServiceImpl.kt deleted file mode 100644 index 05f70e8f..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/mastodon/service/media/MediaApiServiceImpl.kt +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Copyright (C) 2024 usbharu - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package dev.usbharu.hideout.mastodon.service.media - -import dev.usbharu.hideout.application.external.Transaction -import dev.usbharu.hideout.core.service.media.FileType -import dev.usbharu.hideout.core.service.media.MediaService -import dev.usbharu.hideout.domain.mastodon.model.generated.MediaAttachment -import dev.usbharu.hideout.mastodon.interfaces.api.media.MediaRequest -import org.springframework.stereotype.Service - -@Service -class MediaApiServiceImpl(private val mediaService: MediaService, private val transaction: Transaction) : - MediaApiService { - - override suspend fun postMedia(mediaRequest: MediaRequest): MediaAttachment { - return transaction.transaction { - val uploadLocalMedia = mediaService.uploadLocalMedia(mediaRequest) - val type = when (uploadLocalMedia.type) { - FileType.Image -> MediaAttachment.Type.image - FileType.Video -> MediaAttachment.Type.video - FileType.Audio -> MediaAttachment.Type.audio - FileType.Unknown -> MediaAttachment.Type.unknown - } - return@transaction MediaAttachment( - id = uploadLocalMedia.id.toString(), - type = type, - url = uploadLocalMedia.url, - previewUrl = uploadLocalMedia.thumbnailUrl, - description = mediaRequest.description, - blurhash = uploadLocalMedia.blurHash, - textUrl = uploadLocalMedia.url - ) - } - } -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/mastodon/service/notification/MastodonNotificationStore.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/mastodon/service/notification/MastodonNotificationStore.kt deleted file mode 100644 index d632e04c..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/mastodon/service/notification/MastodonNotificationStore.kt +++ /dev/null @@ -1,82 +0,0 @@ -/* - * Copyright (C) 2024 usbharu - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package dev.usbharu.hideout.mastodon.service.notification - -import dev.usbharu.hideout.core.domain.model.actor.Actor -import dev.usbharu.hideout.core.domain.model.notification.Notification -import dev.usbharu.hideout.core.domain.model.post.Post -import dev.usbharu.hideout.core.domain.model.reaction.Reaction -import dev.usbharu.hideout.core.service.notification.NotificationStore -import dev.usbharu.hideout.mastodon.domain.model.MastodonNotification -import dev.usbharu.hideout.mastodon.domain.model.MastodonNotificationRepository -import dev.usbharu.hideout.mastodon.domain.model.NotificationType -import org.slf4j.LoggerFactory -import org.springframework.stereotype.Component - -@Component -class MastodonNotificationStore(private val mastodonNotificationRepository: MastodonNotificationRepository) : - NotificationStore { - override suspend fun publishNotification( - notification: Notification, - user: Actor, - sourceActor: Actor?, - post: Post?, - reaction: Reaction? - ): Boolean { - val notificationType = when (notification.type) { - "mention" -> NotificationType.mention - "post" -> NotificationType.status - "repost" -> NotificationType.reblog - "follow" -> NotificationType.follow - "follow-request" -> NotificationType.follow_request - "reaction" -> NotificationType.favourite - else -> { - logger.debug("Notification type does not support. type: {}", notification.type) - return false - } - } - - if (notification.sourceActorId == null) { - logger.debug("Notification does not support. notification.sourceActorId is null") - return false - } - - val mastodonNotification = MastodonNotification( - id = notification.id, - userId = notification.userId, - type = notificationType, - createdAt = notification.createdAt, - accountId = notification.sourceActorId, - statusId = notification.postId, - reportId = null, - relationshipServeranceEvent = null - ) - - mastodonNotificationRepository.save(mastodonNotification) - - return true - } - - override suspend fun unpulishNotification(notificationId: Long): Boolean { - mastodonNotificationRepository.deleteById(notificationId) - return true - } - - companion object { - private val logger = LoggerFactory.getLogger(MastodonNotificationStore::class.java) - } -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/mastodon/service/notification/NotificationApiService.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/mastodon/service/notification/NotificationApiService.kt deleted file mode 100644 index 32c762c6..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/mastodon/service/notification/NotificationApiService.kt +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright (C) 2024 usbharu - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package dev.usbharu.hideout.mastodon.service.notification - -import dev.usbharu.hideout.application.infrastructure.exposed.Page -import dev.usbharu.hideout.application.infrastructure.exposed.PaginationList -import dev.usbharu.hideout.domain.mastodon.model.generated.Notification -import dev.usbharu.hideout.mastodon.domain.model.NotificationType - -interface NotificationApiService { - - suspend fun notifications( - loginUser: Long, - types: List, - excludeTypes: List, - accountId: List, - page: Page - ): PaginationList - - suspend fun fingById(loginUser: Long, notificationId: Long): Notification? - - suspend fun clearAll(loginUser: Long) - - suspend fun dismiss(loginUser: Long, notificationId: Long) -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/mastodon/service/notification/NotificationApiServiceImpl.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/mastodon/service/notification/NotificationApiServiceImpl.kt deleted file mode 100644 index 52caed8f..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/mastodon/service/notification/NotificationApiServiceImpl.kt +++ /dev/null @@ -1,126 +0,0 @@ -/* - * Copyright (C) 2024 usbharu - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package dev.usbharu.hideout.mastodon.service.notification - -import dev.usbharu.hideout.application.external.Transaction -import dev.usbharu.hideout.application.infrastructure.exposed.Page -import dev.usbharu.hideout.application.infrastructure.exposed.PaginationList -import dev.usbharu.hideout.domain.mastodon.model.generated.Notification -import dev.usbharu.hideout.mastodon.domain.model.MastodonNotificationRepository -import dev.usbharu.hideout.mastodon.domain.model.NotificationType -import dev.usbharu.hideout.mastodon.domain.model.NotificationType.* -import dev.usbharu.hideout.mastodon.query.StatusQueryService -import dev.usbharu.hideout.mastodon.service.account.AccountService -import org.springframework.stereotype.Service - -@Service -class NotificationApiServiceImpl( - private val mastodonNotificationRepository: MastodonNotificationRepository, - private val transaction: Transaction, - private val accountService: AccountService, - private val statusQueryService: StatusQueryService -) : - NotificationApiService { - - override suspend fun notifications( - loginUser: Long, - types: List, - excludeTypes: List, - accountId: List, - page: Page - ): PaginationList = transaction.transaction { - val typesTmp = mutableListOf() - - typesTmp.addAll(types) - typesTmp.removeAll(excludeTypes) - - val mastodonNotifications = - mastodonNotificationRepository.findByUserIdAndInTypesAndInSourceActorId( - loginUser, - typesTmp, - accountId, - page - ) - - val accounts = accountService.findByIds( - mastodonNotifications.map { - it.accountId - } - ).associateBy { it.id.toLong() } - - val statuses = statusQueryService.findByPostIds(mastodonNotifications.mapNotNull { it.statusId }) - .associateBy { it.id.toLong() } - - val notifications = mastodonNotifications.map { - Notification( - id = it.id.toString(), - type = convertNotificationType(it.type), - createdAt = it.createdAt.toString(), - account = accounts.getValue(it.accountId), - status = statuses[it.statusId], - report = null, - relationshipSeveranceEvent = null - ) - } - - return@transaction PaginationList(notifications, mastodonNotifications.next, mastodonNotifications.prev) - } - - override suspend fun fingById(loginUser: Long, notificationId: Long): Notification? { - val findById = mastodonNotificationRepository.findById(notificationId) ?: return null - - if (findById.userId != loginUser) { - return null - } - - val account = accountService.findById(findById.accountId) - val status = findById.statusId?.let { statusQueryService.findByPostId(it) } - - return Notification( - id = findById.id.toString(), - type = convertNotificationType(findById.type), - createdAt = findById.createdAt.toString(), - account = account, - status = status, - report = null, - relationshipSeveranceEvent = null - ) - } - - override suspend fun clearAll(loginUser: Long) { - mastodonNotificationRepository.deleteByUserId(loginUser) - } - - override suspend fun dismiss(loginUser: Long, notificationId: Long) { - mastodonNotificationRepository.deleteByUserIdAndId(loginUser, notificationId) - } - - private fun convertNotificationType(notificationType: NotificationType): Notification.Type = - when (notificationType) { - mention -> Notification.Type.mention - status -> Notification.Type.status - reblog -> Notification.Type.reblog - follow -> Notification.Type.follow - follow_request -> Notification.Type.follow - favourite -> Notification.Type.follow_request - poll -> Notification.Type.poll - update -> Notification.Type.update - admin_sign_up -> Notification.Type.adminPeriodSign_up - admin_report -> Notification.Type.adminPeriodReport - severed_relationships -> Notification.Type.severed_relationships - } -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/mastodon/service/status/StatusesApiService.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/mastodon/service/status/StatusesApiService.kt deleted file mode 100644 index 07ae96c8..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/mastodon/service/status/StatusesApiService.kt +++ /dev/null @@ -1,212 +0,0 @@ -/* - * Copyright (C) 2024 usbharu - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package dev.usbharu.hideout.mastodon.service.status - -import dev.usbharu.hideout.activitypub.service.objects.emoji.EmojiService -import dev.usbharu.hideout.application.external.Transaction -import dev.usbharu.hideout.core.domain.model.actor.ActorRepository -import dev.usbharu.hideout.core.domain.model.emoji.UnicodeEmoji -import dev.usbharu.hideout.core.domain.model.media.MediaRepository -import dev.usbharu.hideout.core.domain.model.media.toMediaAttachments -import dev.usbharu.hideout.core.domain.model.post.PostRepository -import dev.usbharu.hideout.core.domain.model.relationship.RelationshipRepository -import dev.usbharu.hideout.core.service.post.PostCreateDto -import dev.usbharu.hideout.core.service.post.PostService -import dev.usbharu.hideout.core.service.reaction.ReactionService -import dev.usbharu.hideout.domain.mastodon.model.generated.Status -import dev.usbharu.hideout.domain.mastodon.model.generated.Status.Visibility.* -import dev.usbharu.hideout.mastodon.domain.exception.StatusNotFoundException -import dev.usbharu.hideout.mastodon.interfaces.api.status.StatusesRequest -import dev.usbharu.hideout.mastodon.interfaces.api.status.toPostVisibility -import dev.usbharu.hideout.mastodon.interfaces.api.status.toStatusVisibility -import dev.usbharu.hideout.mastodon.query.StatusQueryService -import dev.usbharu.hideout.mastodon.service.account.AccountService -import dev.usbharu.hideout.util.EmojiUtil -import org.slf4j.LoggerFactory -import org.springframework.stereotype.Service -import java.time.Instant - -@Service -interface StatusesApiService { - suspend fun postStatus( - statusesRequest: StatusesRequest, - userId: Long, - ): Status - - suspend fun findById( - id: Long, - userId: Long?, - ): Status - - suspend fun emojiReactions( - postId: Long, - userId: Long, - emojiName: String, - ): Status - - suspend fun removeEmojiReactions( - postId: Long, - userId: Long, - emojiName: String, - ): Status -} - -@Service -@Suppress("LongParameterList") -class StatsesApiServiceImpl( - private val postService: PostService, - private val accountService: AccountService, - private val mediaRepository: MediaRepository, - private val transaction: Transaction, - private val actorRepository: ActorRepository, - private val postRepository: PostRepository, - private val statusQueryService: StatusQueryService, - private val relationshipRepository: RelationshipRepository, - private val reactionService: ReactionService, - private val emojiService: EmojiService, -) : - StatusesApiService { - override suspend fun postStatus( - statusesRequest: StatusesRequest, - userId: Long, - ): Status = transaction.transaction { - logger.debug("START create post by mastodon api. {}", statusesRequest) - - val post = postService.createLocal( - PostCreateDto( - text = statusesRequest.status.orEmpty(), - overview = statusesRequest.spoiler_text, - visibility = statusesRequest.visibility.toPostVisibility(), - repolyId = statusesRequest.in_reply_to_id?.toLong(), - userId = userId, - mediaIds = statusesRequest.media_ids.map { it.toLong() } - ) - ) - val account = accountService.findById(userId) - - val replyUser = if (post.replyId != null) { - val findById = postRepository.findById(post.replyId) - if (findById == null) { - null - } else { - actorRepository.findById(findById.actorId)?.id - } - } else { - null - } - - // TODO: n+1解消 - val mediaAttachment = post.mediaIds.mapNotNull { mediaId -> - mediaRepository.findById(mediaId) - }.map { - it.toMediaAttachments() - } - - Status( - id = post.id.toString(), - uri = post.apId, - createdAt = Instant.ofEpochMilli(post.createdAt).toString(), - account = account, - content = post.text, - visibility = statusesRequest.visibility.toStatusVisibility(), - sensitive = post.sensitive, - spoilerText = post.overview.orEmpty(), - mediaAttachments = mediaAttachment, - mentions = emptyList(), - tags = emptyList(), - emojis = emptyList(), - reblogsCount = 0, - favouritesCount = 0, - repliesCount = 0, - url = post.url, - inReplyToId = post.replyId?.toString(), - inReplyToAccountId = replyUser?.toString(), - language = null, - text = post.text, - editedAt = null, - ) - } - - override suspend fun findById(id: Long, userId: Long?): Status = transaction.transaction { - val status = statusQueryService.findByPostId(id) ?: statusNotFound(id) - - return@transaction status(status, userId) - } - - private fun accessDenied(id: String): Nothing { - logger.debug("Access Denied $id") - throw StatusNotFoundException.ofId(id.toLong()) - } - - private fun statusNotFound(id: Long): Nothing { - logger.debug("Status Not Found $id") - throw StatusNotFoundException.ofId(id) - } - - private suspend fun status( - status: Status, - userId: Long?, - ): Status { - return when (status.visibility) { - public -> status - unlisted -> status - private -> { - if (userId == null) { - accessDenied(status.id) - } - - val relationship = - relationshipRepository.findByUserIdAndTargetUserId(userId, status.account.id.toLong()) - ?: accessDenied(status.id) - if (relationship.following) { - return status - } - accessDenied(status.id) - } - - direct -> accessDenied(status.id) - } - } - - override suspend fun emojiReactions(postId: Long, userId: Long, emojiName: String): Status { - status(statusQueryService.findByPostId(postId) ?: statusNotFound(postId), userId) - - val emoji = try { - if (EmojiUtil.isEmoji(emojiName)) { - UnicodeEmoji(emojiName) - } else { - emojiService.findByEmojiName(emojiName)!! - } - } catch (_: IllegalStateException) { - UnicodeEmoji("❤") - } catch (_: NullPointerException) { - UnicodeEmoji("❤") - } - reactionService.sendReaction(emoji, userId, postId) - return statusQueryService.findByPostId(postId) ?: statusNotFound(postId) - } - - override suspend fun removeEmojiReactions(postId: Long, userId: Long, emojiName: String): Status { - reactionService.removeReaction(userId, postId) - - return status(statusQueryService.findByPostId(postId) ?: statusNotFound(postId), userId) - } - - companion object { - private val logger = LoggerFactory.getLogger(StatusesApiService::class.java) - } -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/mastodon/service/timeline/TimelineApiService.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/mastodon/service/timeline/TimelineApiService.kt deleted file mode 100644 index 3bfd728d..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/mastodon/service/timeline/TimelineApiService.kt +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Copyright (C) 2024 usbharu - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package dev.usbharu.hideout.mastodon.service.timeline - -import dev.usbharu.hideout.application.external.Transaction -import dev.usbharu.hideout.application.infrastructure.exposed.Page -import dev.usbharu.hideout.application.infrastructure.exposed.PaginationList -import dev.usbharu.hideout.core.service.timeline.GenerateTimelineService -import dev.usbharu.hideout.domain.mastodon.model.generated.Status -import org.springframework.stereotype.Service - -@Suppress("LongParameterList") -interface TimelineApiService { - - suspend fun publicTimeline( - localOnly: Boolean = false, - remoteOnly: Boolean = false, - mediaOnly: Boolean = false, - page: Page - ): PaginationList - - suspend fun homeTimeline( - userId: Long, - page: Page - ): PaginationList -} - -@Service -class TimelineApiServiceImpl( - private val generateTimelineService: GenerateTimelineService, - private val transaction: Transaction -) : TimelineApiService { - - override suspend fun publicTimeline( - localOnly: Boolean, - remoteOnly: Boolean, - mediaOnly: Boolean, - page: Page - ): PaginationList = transaction.transaction { - return@transaction generateTimelineService.getTimeline(forUserId = 0, localOnly, mediaOnly, page) - } - - override suspend fun homeTimeline(userId: Long, page: Page): PaginationList = - transaction.transaction { - return@transaction generateTimelineService.getTimeline(forUserId = userId, page = page) - } -} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/util/AcctUtil.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/util/AcctUtil.kt deleted file mode 100644 index 36c8f322..00000000 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/util/AcctUtil.kt +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Copyright (C) 2024 usbharu - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package dev.usbharu.hideout.util - -import dev.usbharu.hideout.core.domain.model.actor.Acct - -object AcctUtil { - fun parse(string: String): Acct { - if (string.isBlank()) { - throw IllegalArgumentException("Invalid acct.(Blank)") - } - return when (string.count { c -> c == '@' }) { - 0 -> { - Acct(string) - } - - 1 -> { - if (string.startsWith("@")) { - Acct(string.substring(1 until string.length)) - } else { - Acct(string.substringBefore("@"), string.substringAfter("@")) - } - } - - 2 -> { - if (string.startsWith("@")) { - val substring = string.substring(1 until string.length) - val userName = substring.substringBefore("@") - val domain = substring.substringAfter("@") - Acct( - userName, - domain.ifBlank { null } - ) - } else { - throw IllegalArgumentException("Invalid acct.(@ are in the wrong position)") - } - } - - else -> { - throw IllegalArgumentException("Invalid acct. (Too many @)") - } - } - } -} diff --git a/hideout-core/src/test/kotlin/dev/usbharu/hideout/activitypub/service/activity/accept/ApAcceptProcessorTest.kt b/hideout-core/src/test/kotlin/dev/usbharu/hideout/activitypub/service/activity/accept/ApAcceptProcessorTest.kt index 9396a079..f211ffff 100644 --- a/hideout-core/src/test/kotlin/dev/usbharu/hideout/activitypub/service/activity/accept/ApAcceptProcessorTest.kt +++ b/hideout-core/src/test/kotlin/dev/usbharu/hideout/activitypub/service/activity/accept/ApAcceptProcessorTest.kt @@ -23,8 +23,6 @@ import dev.usbharu.hideout.activitypub.domain.model.Like import dev.usbharu.hideout.activitypub.service.common.ActivityPubProcessContext import dev.usbharu.hideout.activitypub.service.common.ActivityType import dev.usbharu.hideout.application.config.ActivityPubConfig -import dev.usbharu.hideout.core.domain.model.actor.ActorRepository -import dev.usbharu.hideout.core.service.relationship.RelationshipService import dev.usbharu.httpsignature.common.HttpHeaders import dev.usbharu.httpsignature.common.HttpMethod import dev.usbharu.httpsignature.common.HttpRequest diff --git a/hideout-core/src/test/kotlin/dev/usbharu/hideout/activitypub/service/activity/follow/APSendFollowServiceImplTest.kt b/hideout-core/src/test/kotlin/dev/usbharu/hideout/activitypub/service/activity/follow/APSendFollowServiceImplTest.kt index fbe5ffcf..ab8fee9d 100644 --- a/hideout-core/src/test/kotlin/dev/usbharu/hideout/activitypub/service/activity/follow/APSendFollowServiceImplTest.kt +++ b/hideout-core/src/test/kotlin/dev/usbharu/hideout/activitypub/service/activity/follow/APSendFollowServiceImplTest.kt @@ -19,7 +19,6 @@ package dev.usbharu.hideout.activitypub.service.activity.follow import dev.usbharu.hideout.activitypub.domain.model.Follow import dev.usbharu.hideout.activitypub.service.common.APRequestService import dev.usbharu.hideout.application.config.ApplicationConfig -import dev.usbharu.hideout.core.service.follow.SendFollowDto import kotlinx.coroutines.test.runTest import org.junit.jupiter.api.Test import org.mockito.kotlin.eq diff --git a/hideout-core/src/test/kotlin/dev/usbharu/hideout/activitypub/service/common/APResourceResolveServiceImplTest.kt b/hideout-core/src/test/kotlin/dev/usbharu/hideout/activitypub/service/common/APResourceResolveServiceImplTest.kt index 7c71a55a..bb96522a 100644 --- a/hideout-core/src/test/kotlin/dev/usbharu/hideout/activitypub/service/common/APResourceResolveServiceImplTest.kt +++ b/hideout-core/src/test/kotlin/dev/usbharu/hideout/activitypub/service/common/APResourceResolveServiceImplTest.kt @@ -16,7 +16,6 @@ package dev.usbharu.hideout.activitypub.service.common -import dev.usbharu.hideout.core.domain.model.actor.ActorRepository import dev.usbharu.hideout.core.service.resource.InMemoryCacheManager import kotlinx.coroutines.async import kotlinx.coroutines.awaitAll diff --git a/hideout-core/src/test/kotlin/dev/usbharu/hideout/activitypub/service/objects/note/APNoteServiceImplTest.kt b/hideout-core/src/test/kotlin/dev/usbharu/hideout/activitypub/service/objects/note/APNoteServiceImplTest.kt index 87bfbae4..33e745a8 100644 --- a/hideout-core/src/test/kotlin/dev/usbharu/hideout/activitypub/service/objects/note/APNoteServiceImplTest.kt +++ b/hideout-core/src/test/kotlin/dev/usbharu/hideout/activitypub/service/objects/note/APNoteServiceImplTest.kt @@ -32,11 +32,8 @@ import dev.usbharu.hideout.activitypub.service.objects.user.APUserService import dev.usbharu.hideout.application.config.CharacterLimit import dev.usbharu.hideout.application.config.HtmlSanitizeConfig import dev.usbharu.hideout.application.service.id.TwitterSnowflakeIdGenerateService -import dev.usbharu.hideout.core.domain.model.post.Post -import dev.usbharu.hideout.core.domain.model.post.PostRepository import dev.usbharu.hideout.core.service.media.MediaService import dev.usbharu.hideout.core.service.post.DefaultPostContentFormatter -import dev.usbharu.hideout.core.service.post.PostService import io.ktor.client.* import io.ktor.client.call.* import io.ktor.client.plugins.* diff --git a/hideout-core/src/test/kotlin/dev/usbharu/hideout/core/service/notification/NotificationServiceImplTest.kt b/hideout-core/src/test/kotlin/dev/usbharu/hideout/core/service/notification/NotificationServiceImplTest.kt index aab94666..c1daea13 100644 --- a/hideout-core/src/test/kotlin/dev/usbharu/hideout/core/service/notification/NotificationServiceImplTest.kt +++ b/hideout-core/src/test/kotlin/dev/usbharu/hideout/core/service/notification/NotificationServiceImplTest.kt @@ -18,12 +18,9 @@ package dev.usbharu.hideout.core.service.notification import dev.usbharu.hideout.application.config.ApplicationConfig import dev.usbharu.hideout.application.service.id.TwitterSnowflakeIdGenerateService -import dev.usbharu.hideout.core.domain.model.actor.ActorRepository import dev.usbharu.hideout.core.domain.model.notification.Notification import dev.usbharu.hideout.core.domain.model.notification.NotificationRepository -import dev.usbharu.hideout.core.domain.model.post.PostRepository import dev.usbharu.hideout.core.domain.model.reaction.ReactionRepository -import dev.usbharu.hideout.core.domain.model.relationship.RelationshipRepository import kotlinx.coroutines.test.runTest import org.assertj.core.api.Assertions.assertThat import org.junit.jupiter.api.Test diff --git a/hideout-core/src/test/kotlin/dev/usbharu/hideout/core/service/post/PostServiceImplTest.kt b/hideout-core/src/test/kotlin/dev/usbharu/hideout/core/service/post/PostServiceImplTest.kt index 80cbb571..8d7c1676 100644 --- a/hideout-core/src/test/kotlin/dev/usbharu/hideout/core/service/post/PostServiceImplTest.kt +++ b/hideout-core/src/test/kotlin/dev/usbharu/hideout/core/service/post/PostServiceImplTest.kt @@ -21,11 +21,7 @@ import dev.usbharu.hideout.activitypub.service.activity.delete.APSendDeleteServi import dev.usbharu.hideout.application.config.CharacterLimit import dev.usbharu.hideout.application.config.HtmlSanitizeConfig import dev.usbharu.hideout.core.domain.exception.resource.DuplicateException -import dev.usbharu.hideout.core.domain.model.actor.ActorRepository -import dev.usbharu.hideout.core.domain.model.post.Post -import dev.usbharu.hideout.core.domain.model.post.PostRepository import dev.usbharu.hideout.core.domain.model.reaction.ReactionRepository -import dev.usbharu.hideout.core.service.timeline.TimelineService import jakarta.validation.Validation import kotlinx.coroutines.test.runTest import org.assertj.core.api.Assertions.assertThat diff --git a/hideout-core/src/test/kotlin/dev/usbharu/hideout/core/service/reaction/ReactionServiceImplTest.kt b/hideout-core/src/test/kotlin/dev/usbharu/hideout/core/service/reaction/ReactionServiceImplTest.kt index e619f0f0..c02bfa10 100644 --- a/hideout-core/src/test/kotlin/dev/usbharu/hideout/core/service/reaction/ReactionServiceImplTest.kt +++ b/hideout-core/src/test/kotlin/dev/usbharu/hideout/core/service/reaction/ReactionServiceImplTest.kt @@ -20,7 +20,6 @@ package dev.usbharu.hideout.core.service.reaction import dev.usbharu.hideout.activitypub.service.activity.like.APReactionService import dev.usbharu.hideout.application.service.id.TwitterSnowflakeIdGenerateService import dev.usbharu.hideout.core.domain.model.emoji.UnicodeEmoji -import dev.usbharu.hideout.core.domain.model.post.PostRepository import dev.usbharu.hideout.core.domain.model.reaction.Reaction import dev.usbharu.hideout.core.domain.model.reaction.ReactionRepository import dev.usbharu.hideout.core.service.notification.NotificationService diff --git a/hideout-core/src/test/kotlin/dev/usbharu/hideout/core/service/relationship/RelationshipServiceImplTest.kt b/hideout-core/src/test/kotlin/dev/usbharu/hideout/core/service/relationship/RelationshipServiceImplTest.kt index 4af69e07..0d82e8b5 100644 --- a/hideout-core/src/test/kotlin/dev/usbharu/hideout/core/service/relationship/RelationshipServiceImplTest.kt +++ b/hideout-core/src/test/kotlin/dev/usbharu/hideout/core/service/relationship/RelationshipServiceImplTest.kt @@ -21,10 +21,7 @@ import dev.usbharu.hideout.activitypub.service.activity.follow.APSendFollowServi import dev.usbharu.hideout.activitypub.service.activity.reject.ApSendRejectService import dev.usbharu.hideout.activitypub.service.activity.undo.APSendUndoService import dev.usbharu.hideout.application.config.ApplicationConfig -import dev.usbharu.hideout.core.domain.model.actor.ActorRepository import dev.usbharu.hideout.core.domain.model.relationship.Relationship -import dev.usbharu.hideout.core.domain.model.relationship.RelationshipRepository -import dev.usbharu.hideout.core.service.follow.SendFollowDto import dev.usbharu.hideout.core.service.notification.NotificationService import kotlinx.coroutines.test.runTest import org.junit.jupiter.api.Test diff --git a/hideout-core/src/test/kotlin/dev/usbharu/hideout/core/service/timeline/TimelineServiceTest.kt b/hideout-core/src/test/kotlin/dev/usbharu/hideout/core/service/timeline/TimelineServiceTest.kt index e5806fc5..29db21c2 100644 --- a/hideout-core/src/test/kotlin/dev/usbharu/hideout/core/service/timeline/TimelineServiceTest.kt +++ b/hideout-core/src/test/kotlin/dev/usbharu/hideout/core/service/timeline/TimelineServiceTest.kt @@ -17,12 +17,9 @@ package dev.usbharu.hideout.core.service.timeline import dev.usbharu.hideout.application.service.id.TwitterSnowflakeIdGenerateService -import dev.usbharu.hideout.core.domain.model.actor.Actor -import dev.usbharu.hideout.core.domain.model.actor.ActorRepository import dev.usbharu.hideout.core.domain.model.post.Visibility import dev.usbharu.hideout.core.domain.model.timeline.Timeline import dev.usbharu.hideout.core.domain.model.timeline.TimelineRepository -import dev.usbharu.hideout.core.query.FollowerQueryService import kotlinx.coroutines.test.runTest import org.assertj.core.api.Assertions.assertThat import org.junit.jupiter.api.Test diff --git a/hideout-core/src/test/kotlin/dev/usbharu/hideout/core/service/user/ActorServiceTest.kt b/hideout-core/src/test/kotlin/dev/usbharu/hideout/core/service/user/ActorServiceTest.kt index 6a574dc6..bfd88778 100644 --- a/hideout-core/src/test/kotlin/dev/usbharu/hideout/core/service/user/ActorServiceTest.kt +++ b/hideout-core/src/test/kotlin/dev/usbharu/hideout/core/service/user/ActorServiceTest.kt @@ -21,16 +21,11 @@ package dev.usbharu.hideout.core.service.user import dev.usbharu.hideout.activitypub.service.activity.delete.APSendDeleteService import dev.usbharu.hideout.application.config.ApplicationConfig import dev.usbharu.hideout.application.config.CharacterLimit -import dev.usbharu.hideout.core.domain.model.actor.Actor -import dev.usbharu.hideout.core.domain.model.actor.ActorRepository import dev.usbharu.hideout.core.domain.model.deletedActor.DeletedActorRepository import dev.usbharu.hideout.core.domain.model.instance.Instance -import dev.usbharu.hideout.core.domain.model.post.PostRepository import dev.usbharu.hideout.core.domain.model.reaction.ReactionRepository -import dev.usbharu.hideout.core.domain.model.relationship.RelationshipRepository import dev.usbharu.hideout.core.domain.model.userdetails.UserDetailRepository import dev.usbharu.hideout.core.service.instance.InstanceService -import dev.usbharu.hideout.core.service.post.PostService import dev.usbharu.owl.producer.api.OwlProducer import jakarta.validation.Validation import kotlinx.coroutines.ExperimentalCoroutinesApi diff --git a/hideout-core/src/test/kotlin/dev/usbharu/hideout/mastodon/service/account/AccountApiServiceImplTest.kt b/hideout-core/src/test/kotlin/dev/usbharu/hideout/mastodon/service/account/AccountApiServiceImplTest.kt index de1477c6..e1676e7a 100644 --- a/hideout-core/src/test/kotlin/dev/usbharu/hideout/mastodon/service/account/AccountApiServiceImplTest.kt +++ b/hideout-core/src/test/kotlin/dev/usbharu/hideout/mastodon/service/account/AccountApiServiceImplTest.kt @@ -19,12 +19,7 @@ package dev.usbharu.hideout.mastodon.service.account import dev.usbharu.hideout.application.external.Transaction import dev.usbharu.hideout.application.infrastructure.exposed.Page import dev.usbharu.hideout.application.infrastructure.exposed.PaginationList -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 import dev.usbharu.hideout.domain.mastodon.model.generated.Relationship import dev.usbharu.hideout.domain.mastodon.model.generated.Status diff --git a/hideout-core/src/test/kotlin/utils/PostBuilder.kt b/hideout-core/src/test/kotlin/utils/PostBuilder.kt index 4ddd2e89..41b9f746 100644 --- a/hideout-core/src/test/kotlin/utils/PostBuilder.kt +++ b/hideout-core/src/test/kotlin/utils/PostBuilder.kt @@ -19,7 +19,6 @@ package utils import dev.usbharu.hideout.application.config.CharacterLimit import dev.usbharu.hideout.application.config.HtmlSanitizeConfig import dev.usbharu.hideout.application.service.id.TwitterSnowflakeIdGenerateService -import dev.usbharu.hideout.core.domain.model.post.Post import dev.usbharu.hideout.core.domain.model.post.Visibility import dev.usbharu.hideout.core.service.post.DefaultPostContentFormatter import jakarta.validation.Validation diff --git a/hideout-core/src/test/kotlin/utils/UserBuilder.kt b/hideout-core/src/test/kotlin/utils/UserBuilder.kt index 0aff8e44..fd929c81 100644 --- a/hideout-core/src/test/kotlin/utils/UserBuilder.kt +++ b/hideout-core/src/test/kotlin/utils/UserBuilder.kt @@ -19,7 +19,6 @@ package utils import dev.usbharu.hideout.application.config.ApplicationConfig import dev.usbharu.hideout.application.config.CharacterLimit import dev.usbharu.hideout.application.service.id.TwitterSnowflakeIdGenerateService -import dev.usbharu.hideout.core.domain.model.actor.Actor import jakarta.validation.Validation import kotlinx.coroutines.runBlocking import java.net.URL diff --git a/hideout-mastodon/build.gradle.kts b/hideout-mastodon/build.gradle.kts new file mode 100644 index 00000000..45be2756 --- /dev/null +++ b/hideout-mastodon/build.gradle.kts @@ -0,0 +1,21 @@ +plugins { + kotlin("jvm") version "1.9.23" +} + +group = "dev.usbharu" +version = "1.0-SNAPSHOT" + +repositories { + mavenCentral() +} + +dependencies { + testImplementation(kotlin("test")) +} + +tasks.test { + useJUnitPlatform() +} +kotlin { + jvmToolchain(21) +} \ No newline at end of file diff --git a/hideout-mastodon/gradle/wrapper/gradle-wrapper.jar b/hideout-mastodon/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000000000000000000000000000000000000..249e5832f090a2944b7473328c07c9755baa3196 GIT binary patch literal 60756 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 '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit + +APP_NAME="Gradle" +APP_BASE_NAME=${0##*/} + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD=$JAVA_HOME/jre/sh/java + else + JAVACMD=$JAVA_HOME/bin/java + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD=java + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + +# Collect all arguments for the java command; +# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of +# shell script including quotes and variable substitutions, so put them in +# double quotes to make sure that they get re-expanded; and +# * put everything else in single quotes, so that it's not re-expanded. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/hideout-mastodon/gradlew.bat b/hideout-mastodon/gradlew.bat new file mode 100644 index 00000000..107acd32 --- /dev/null +++ b/hideout-mastodon/gradlew.bat @@ -0,0 +1,89 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto execute + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/hideout-mastodon/settings.gradle.kts b/hideout-mastodon/settings.gradle.kts new file mode 100644 index 00000000..c023f31a --- /dev/null +++ b/hideout-mastodon/settings.gradle.kts @@ -0,0 +1,5 @@ +plugins { + id("org.gradle.toolchains.foojay-resolver-convention") version "0.5.0" +} +rootProject.name = "hideout-mastodon" + diff --git a/hideout-mastodon/src/main/kotlin/Main.kt b/hideout-mastodon/src/main/kotlin/Main.kt new file mode 100644 index 00000000..27f6ee1a --- /dev/null +++ b/hideout-mastodon/src/main/kotlin/Main.kt @@ -0,0 +1,5 @@ +package dev.usbharu + +fun main() { + println("Hello World!") +} \ No newline at end of file diff --git a/hideout-worker/src/main/kotlin/dev/usbharu/hideout/worker/DeliverAcceptTaskRunner.kt b/hideout-worker/src/main/kotlin/dev/usbharu/hideout/worker/DeliverAcceptTaskRunner.kt index b557e259..7d939be0 100644 --- a/hideout-worker/src/main/kotlin/dev/usbharu/hideout/worker/DeliverAcceptTaskRunner.kt +++ b/hideout-worker/src/main/kotlin/dev/usbharu/hideout/worker/DeliverAcceptTaskRunner.kt @@ -18,7 +18,6 @@ package dev.usbharu.hideout.worker import dev.usbharu.hideout.activitypub.service.common.APRequestService import dev.usbharu.hideout.application.external.Transaction -import dev.usbharu.hideout.core.domain.model.actor.ActorRepository import dev.usbharu.hideout.core.external.job.DeliverAcceptTask import dev.usbharu.hideout.core.external.job.DeliverAcceptTaskDef import dev.usbharu.owl.consumer.AbstractTaskRunner diff --git a/hideout-worker/src/main/kotlin/dev/usbharu/hideout/worker/DeliverCreateTaskRunner.kt b/hideout-worker/src/main/kotlin/dev/usbharu/hideout/worker/DeliverCreateTaskRunner.kt index 7780d00d..9d880986 100644 --- a/hideout-worker/src/main/kotlin/dev/usbharu/hideout/worker/DeliverCreateTaskRunner.kt +++ b/hideout-worker/src/main/kotlin/dev/usbharu/hideout/worker/DeliverCreateTaskRunner.kt @@ -18,7 +18,6 @@ package dev.usbharu.hideout.worker import dev.usbharu.hideout.activitypub.service.common.APRequestService import dev.usbharu.hideout.application.external.Transaction -import dev.usbharu.hideout.core.domain.model.actor.ActorRepository import dev.usbharu.hideout.core.external.job.DeliverCreateTask import dev.usbharu.hideout.core.external.job.DeliverCreateTaskDef import dev.usbharu.owl.consumer.AbstractTaskRunner diff --git a/hideout-worker/src/main/kotlin/dev/usbharu/hideout/worker/DeliverDeleteTaskRunner.kt b/hideout-worker/src/main/kotlin/dev/usbharu/hideout/worker/DeliverDeleteTaskRunner.kt index 92961616..9ce3dad7 100644 --- a/hideout-worker/src/main/kotlin/dev/usbharu/hideout/worker/DeliverDeleteTaskRunner.kt +++ b/hideout-worker/src/main/kotlin/dev/usbharu/hideout/worker/DeliverDeleteTaskRunner.kt @@ -17,7 +17,6 @@ package dev.usbharu.hideout.worker import dev.usbharu.hideout.activitypub.service.common.APRequestService -import dev.usbharu.hideout.core.domain.model.actor.ActorRepository import dev.usbharu.hideout.core.external.job.DeliverDeleteTask import dev.usbharu.hideout.core.external.job.DeliverDeleteTaskDef import dev.usbharu.owl.consumer.AbstractTaskRunner diff --git a/hideout-worker/src/main/kotlin/dev/usbharu/hideout/worker/DeliverReactionTaskRunner.kt b/hideout-worker/src/main/kotlin/dev/usbharu/hideout/worker/DeliverReactionTaskRunner.kt index 6408a73c..4867056a 100644 --- a/hideout-worker/src/main/kotlin/dev/usbharu/hideout/worker/DeliverReactionTaskRunner.kt +++ b/hideout-worker/src/main/kotlin/dev/usbharu/hideout/worker/DeliverReactionTaskRunner.kt @@ -17,7 +17,6 @@ package dev.usbharu.hideout.worker import dev.usbharu.hideout.activitypub.service.common.APRequestService -import dev.usbharu.hideout.core.domain.model.actor.ActorRepository import dev.usbharu.hideout.core.external.job.DeliverReactionTask import dev.usbharu.hideout.core.external.job.DeliverReactionTaskDef import dev.usbharu.owl.consumer.AbstractTaskRunner diff --git a/hideout-worker/src/main/kotlin/dev/usbharu/hideout/worker/DeliverRejectTaskRunner.kt b/hideout-worker/src/main/kotlin/dev/usbharu/hideout/worker/DeliverRejectTaskRunner.kt index 7558197b..73179f07 100644 --- a/hideout-worker/src/main/kotlin/dev/usbharu/hideout/worker/DeliverRejectTaskRunner.kt +++ b/hideout-worker/src/main/kotlin/dev/usbharu/hideout/worker/DeliverRejectTaskRunner.kt @@ -18,7 +18,6 @@ package dev.usbharu.hideout.worker import dev.usbharu.hideout.activitypub.service.common.APRequestService import dev.usbharu.hideout.application.external.Transaction -import dev.usbharu.hideout.core.domain.model.actor.ActorRepository import dev.usbharu.hideout.core.external.job.DeliverRejectTask import dev.usbharu.hideout.core.external.job.DeliverRejectTaskDef import dev.usbharu.owl.consumer.AbstractTaskRunner diff --git a/hideout-worker/src/main/kotlin/dev/usbharu/hideout/worker/DeliverUndoTaskRunner.kt b/hideout-worker/src/main/kotlin/dev/usbharu/hideout/worker/DeliverUndoTaskRunner.kt index c811d2a6..949435ba 100644 --- a/hideout-worker/src/main/kotlin/dev/usbharu/hideout/worker/DeliverUndoTaskRunner.kt +++ b/hideout-worker/src/main/kotlin/dev/usbharu/hideout/worker/DeliverUndoTaskRunner.kt @@ -18,7 +18,6 @@ package dev.usbharu.hideout.worker import dev.usbharu.hideout.activitypub.service.common.APRequestService import dev.usbharu.hideout.application.external.Transaction -import dev.usbharu.hideout.core.domain.model.actor.ActorRepository import dev.usbharu.hideout.core.external.job.DeliverUndoTask import dev.usbharu.hideout.core.external.job.DeliverUndoTaskDef import dev.usbharu.owl.consumer.AbstractTaskRunner diff --git a/hideout-worker/src/main/kotlin/dev/usbharu/hideout/worker/ReceiveFollowTaskRunner.kt b/hideout-worker/src/main/kotlin/dev/usbharu/hideout/worker/ReceiveFollowTaskRunner.kt index 458f4f4c..7839bc01 100644 --- a/hideout-worker/src/main/kotlin/dev/usbharu/hideout/worker/ReceiveFollowTaskRunner.kt +++ b/hideout-worker/src/main/kotlin/dev/usbharu/hideout/worker/ReceiveFollowTaskRunner.kt @@ -19,10 +19,8 @@ package dev.usbharu.hideout.worker import dev.usbharu.hideout.activitypub.service.objects.user.APUserService import dev.usbharu.hideout.application.external.Transaction import dev.usbharu.hideout.core.domain.exception.resource.UserNotFoundException -import dev.usbharu.hideout.core.domain.model.actor.ActorRepository import dev.usbharu.hideout.core.external.job.ReceiveFollowTask import dev.usbharu.hideout.core.external.job.ReceiveFollowTaskDef -import dev.usbharu.hideout.core.service.relationship.RelationshipService import dev.usbharu.owl.consumer.AbstractTaskRunner import dev.usbharu.owl.consumer.TaskRequest import dev.usbharu.owl.consumer.TaskResult diff --git a/hideout-worker/src/main/kotlin/dev/usbharu/hideout/worker/UpdateActorWorker.kt b/hideout-worker/src/main/kotlin/dev/usbharu/hideout/worker/UpdateActorWorker.kt index f25b8dce..9faec4c6 100644 --- a/hideout-worker/src/main/kotlin/dev/usbharu/hideout/worker/UpdateActorWorker.kt +++ b/hideout-worker/src/main/kotlin/dev/usbharu/hideout/worker/UpdateActorWorker.kt @@ -20,7 +20,6 @@ import dev.usbharu.hideout.activitypub.service.objects.user.APUserService import dev.usbharu.hideout.application.external.Transaction import dev.usbharu.hideout.core.external.job.UpdateActorTask import dev.usbharu.hideout.core.external.job.UpdateActorTaskDef -import dev.usbharu.hideout.core.service.post.PostService import dev.usbharu.owl.consumer.AbstractTaskRunner import dev.usbharu.owl.consumer.TaskRequest import dev.usbharu.owl.consumer.TaskResult