diff options
| author | Namekuji <11836635+nmkj-io@users.noreply.github.com> | 2023-04-08 01:16:26 -0400 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2023-04-08 14:16:26 +0900 |
| commit | 25ebb73756017174c3197376b7485e5e7fcb5fd1 (patch) | |
| tree | 9f313b388dd38db54c66c66554b10255450832ba /packages/backend/src/core/AccountMoveService.ts | |
| parent | enhance(backend): Redisにチャンネル投稿がない場合はDBから持... (diff) | |
| download | sharkey-25ebb73756017174c3197376b7485e5e7fcb5fd1.tar.gz sharkey-25ebb73756017174c3197376b7485e5e7fcb5fd1.tar.bz2 sharkey-25ebb73756017174c3197376b7485e5e7fcb5fd1.zip | |
feat: account migration (#10507)
* add Move activity
* add endpoint to move from local to remote
* follow move activity coming to inbox
* fix move endpoint
* add known-as endpoint to create account alias
* add migration page
* add route to migration page
* add move and known-as endpoints
* fix dependnecies error
* fix new endpoints
* fix move activity id
* fix refollow
* add movedToUri and alsoKnownAs to api
* fix moveToUri indicator
* fix missing context
* add chengelog
* rename MkMoved to MkAccountMoved
* add missing semicolon
* fix targetUri
* fix followings query
* remove redundant null check
Diffstat (limited to 'packages/backend/src/core/AccountMoveService.ts')
| -rw-r--r-- | packages/backend/src/core/AccountMoveService.ts | 114 |
1 files changed, 114 insertions, 0 deletions
diff --git a/packages/backend/src/core/AccountMoveService.ts b/packages/backend/src/core/AccountMoveService.ts new file mode 100644 index 0000000000..6a7727c57a --- /dev/null +++ b/packages/backend/src/core/AccountMoveService.ts @@ -0,0 +1,114 @@ +import { Inject, Injectable } from '@nestjs/common'; +import { IsNull } from 'typeorm'; + +import { bindThis } from '@/decorators.js'; +import { DI } from '@/di-symbols.js'; +import type { LocalUser } from '@/models/entities/User.js'; +import { User } from '@/models/entities/User.js'; +import type { FollowingsRepository, UsersRepository } from '@/models/index.js'; + +import { GlobalEventService } from '@/core/GlobalEventService.js'; +import { UserFollowingService } from '@/core/UserFollowingService.js'; +import { ApDeliverManagerService } from '@/core/activitypub/ApDeliverManagerService.js'; +import { ApRendererService } from '@/core/activitypub/ApRendererService.js'; +import { UserEntityService } from '@/core/entities/UserEntityService.js'; +import { AccountUpdateService } from '@/core/AccountUpdateService.js'; +import { RelayService } from '@/core/RelayService.js'; + +@Injectable() +export class AccountMoveService { + constructor( + @Inject(DI.usersRepository) + private usersRepository: UsersRepository, + + @Inject(DI.followingsRepository) + private followingsRepository: FollowingsRepository, + + private userEntityService: UserEntityService, + private apRendererService: ApRendererService, + private apDeliverManagerService: ApDeliverManagerService, + private globalEventService: GlobalEventService, + private userFollowingService: UserFollowingService, + private accountUpdateService: AccountUpdateService, + private relayService: RelayService, + ) { + } + + /** + * Move a local account to a remote account. + * + * After delivering Move activity, its local followers unfollow the old account and then follow the new one. + */ + @bindThis + public async moveToRemote(src: LocalUser, dst: User): Promise<unknown> { + // Make sure that the destination is a remote account. + if (this.userEntityService.isLocalUser(dst)) throw new Error('move destiantion is not remote'); + if (!dst.uri) throw new Error('destination uri is empty'); + + // add movedToUri to indicate that the user has moved + const update = {} as Partial<User>; + update.alsoKnownAs = src.alsoKnownAs?.concat([dst.uri]) ?? [dst.uri]; + update.movedToUri = dst.uri; + await this.usersRepository.update(src.id, update); + + const srcPerson = await this.apRendererService.renderPerson(src); + const updateAct = this.apRendererService.addContext(this.apRendererService.renderUpdate(srcPerson, src)); + await this.apDeliverManagerService.deliverToFollowers(src, updateAct); + this.relayService.deliverToRelays(src, updateAct); + + // Deliver Move activity to the followers of the old account + const moveAct = this.apRendererService.addContext(this.apRendererService.renderMove(src, dst)); + await this.apDeliverManagerService.deliverToFollowers(src, moveAct); + + // Publish meUpdated event + const iObj = await this.userEntityService.pack<true, true>(src.id, src, { detail: true, includeSecrets: true }); + this.globalEventService.publishMainStream(src.id, 'meUpdated', iObj); + + // follow the new account and unfollow the old one + const followings = await this.followingsRepository.find({ + relations: { + follower: true, + }, + where: { + followeeId: src.id, + followerHost: IsNull(), // follower is local + } + }); + followings.forEach(async (following) => { + if (!following.follower) return; + try { + await this.userFollowingService.follow(following.follower, dst); + await this.userFollowingService.unfollow(following.follower, src); + } catch { + /* empty */ + } + }); + + return iObj; + } + + /** + * Create an alias of an old remote account. + * + * The user's new profile will be published to the followers. + */ + @bindThis + public async createAlias(me: LocalUser, updates: Partial<User>): Promise<unknown> { + await this.usersRepository.update(me.id, updates); + + // Publish meUpdated event + const iObj = await this.userEntityService.pack<true, true>(me.id, me, { + detail: true, + includeSecrets: true, + }); + this.globalEventService.publishMainStream(me.id, 'meUpdated', iObj); + + if (me.isLocked === false) { + await this.userFollowingService.acceptAllFollowRequests(me); + } + + this.accountUpdateService.publishToFollowers(me.id); + + return iObj; + } +} |