This commit is contained in:
parent
e6112c092b
commit
e48590f53e
|
@ -6,7 +6,7 @@
|
||||||
import { Inject, Injectable } from '@nestjs/common';
|
import { Inject, Injectable } from '@nestjs/common';
|
||||||
import { DI } from '@/di-symbols.js';
|
import { DI } from '@/di-symbols.js';
|
||||||
import type { UsersRepository } from '@/models/_.js';
|
import type { UsersRepository } from '@/models/_.js';
|
||||||
import type { MiUser } from '@/models/User.js';
|
import type { MiLocalUser, MiUser } from '@/models/User.js';
|
||||||
import { ApRendererService } from '@/core/activitypub/ApRendererService.js';
|
import { ApRendererService } from '@/core/activitypub/ApRendererService.js';
|
||||||
import { RelayService } from '@/core/RelayService.js';
|
import { RelayService } from '@/core/RelayService.js';
|
||||||
import { ApDeliverManagerService } from '@/core/activitypub/ApDeliverManagerService.js';
|
import { ApDeliverManagerService } from '@/core/activitypub/ApDeliverManagerService.js';
|
||||||
|
@ -26,16 +26,43 @@ export class AccountUpdateService {
|
||||||
) {
|
) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async createUpdatePersonActivity(user: MiLocalUser) {
|
||||||
|
return this.apRendererService.addContext(
|
||||||
|
this.apRendererService.renderUpdate(
|
||||||
|
await this.apRendererService.renderPerson(user), user
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
public async publishToFollowers(userId: MiUser['id']) {
|
public async publishToFollowers(userId: MiUser['id']) {
|
||||||
const user = await this.usersRepository.findOneBy({ id: userId });
|
const user = await this.usersRepository.findOneBy({ id: userId });
|
||||||
if (user == null) throw new Error('user not found');
|
if (user == null) throw new Error('user not found');
|
||||||
|
|
||||||
// フォロワーがリモートユーザーかつ投稿者がローカルユーザーならUpdateを配信
|
// 投稿者がローカルユーザーならUpdateを配信
|
||||||
if (this.userEntityService.isLocalUser(user)) {
|
if (this.userEntityService.isLocalUser(user)) {
|
||||||
const content = this.apRendererService.addContext(this.apRendererService.renderUpdate(await this.apRendererService.renderPerson(user), user));
|
const content = await this.createUpdatePersonActivity(user);
|
||||||
this.apDeliverManagerService.deliverToFollowers(user, content);
|
this.apDeliverManagerService.deliverToFollowers(user, content);
|
||||||
this.relayService.deliverToRelays(user, content);
|
this.relayService.deliverToRelays(user, content);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@bindThis
|
||||||
|
async publishToFollowersAndSharedInboxAndRelays(userId: MiUser['id']) {
|
||||||
|
const user = await this.usersRepository.findOneBy({ id: userId });
|
||||||
|
if (user == null) throw new Error('user not found');
|
||||||
|
|
||||||
|
// 投稿者がローカルユーザーならUpdateを配信
|
||||||
|
if (this.userEntityService.isLocalUser(user)) {
|
||||||
|
const content = await this.createUpdatePersonActivity(user);
|
||||||
|
const manager = this.apDeliverManagerService.createDeliverManager(user, content);
|
||||||
|
manager.addAllKnowingSharedInboxRecipe();
|
||||||
|
manager.addFollowersRecipe();
|
||||||
|
|
||||||
|
await Promise.allSettled([
|
||||||
|
manager.execute(),
|
||||||
|
this.relayService.deliverToRelays(user, content),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,14 +4,12 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Inject, Injectable } from '@nestjs/common';
|
import { Inject, Injectable } from '@nestjs/common';
|
||||||
import { Not, IsNull } from 'typeorm';
|
|
||||||
import type { FollowingsRepository, FollowRequestsRepository, UsersRepository } from '@/models/_.js';
|
import type { FollowingsRepository, FollowRequestsRepository, UsersRepository } from '@/models/_.js';
|
||||||
import type { MiUser } from '@/models/User.js';
|
import type { MiUser } from '@/models/User.js';
|
||||||
import { GlobalEventService } from '@/core/GlobalEventService.js';
|
import { GlobalEventService } from '@/core/GlobalEventService.js';
|
||||||
import { DI } from '@/di-symbols.js';
|
import { DI } from '@/di-symbols.js';
|
||||||
import { UserEntityService } from '@/core/entities/UserEntityService.js';
|
import { UserEntityService } from '@/core/entities/UserEntityService.js';
|
||||||
import { bindThis } from '@/decorators.js';
|
import { bindThis } from '@/decorators.js';
|
||||||
import { ApDeliverManagerService } from './activitypub/ApDeliverManagerService.js';
|
|
||||||
import { ModerationLogService } from '@/core/ModerationLogService.js';
|
import { ModerationLogService } from '@/core/ModerationLogService.js';
|
||||||
import { AccountUpdateService } from '@/core/AccountUpdateService.js';
|
import { AccountUpdateService } from '@/core/AccountUpdateService.js';
|
||||||
|
|
||||||
|
@ -29,9 +27,7 @@ export class UserSuspendService {
|
||||||
|
|
||||||
private userEntityService: UserEntityService,
|
private userEntityService: UserEntityService,
|
||||||
private globalEventService: GlobalEventService,
|
private globalEventService: GlobalEventService,
|
||||||
private accountUpadateService: AccountUpdateService,
|
private accountUpdateService: AccountUpdateService,
|
||||||
private apRendererService: ApRendererService,
|
|
||||||
private apDeliverManagerService: ApDeliverManagerService,
|
|
||||||
private moderationLogService: ModerationLogService,
|
private moderationLogService: ModerationLogService,
|
||||||
) {
|
) {
|
||||||
}
|
}
|
||||||
|
@ -84,12 +80,7 @@ export class UserSuspendService {
|
||||||
});
|
});
|
||||||
|
|
||||||
if (this.userEntityService.isLocalUser(user)) {
|
if (this.userEntityService.isLocalUser(user)) {
|
||||||
this.accountUpadateService.publishToFollowers(user.id);
|
this.accountUpdateService.publishToFollowersAndSharedInboxAndRelays(user.id);
|
||||||
const content = this.apRendererService.addContext(this.apRendererService.renderDelete(this.userEntityService.genLocalUserUri(user.id), user));
|
|
||||||
const manager = this.apDeliverManagerService.createDeliverManager(user, content);
|
|
||||||
manager.addAllKnowingSharedInboxRecipe();
|
|
||||||
manager.addFollowersRecipe();
|
|
||||||
manager.execute();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -98,12 +89,7 @@ export class UserSuspendService {
|
||||||
this.globalEventService.publishInternalEvent('userChangeSuspendedState', { id: user.id, isSuspended: false });
|
this.globalEventService.publishInternalEvent('userChangeSuspendedState', { id: user.id, isSuspended: false });
|
||||||
|
|
||||||
if (this.userEntityService.isLocalUser(user)) {
|
if (this.userEntityService.isLocalUser(user)) {
|
||||||
this.accountUpadateService.publishToFollowers(user.id);
|
this.accountUpdateService.publishToFollowersAndSharedInboxAndRelays(user.id);
|
||||||
const content = this.apRendererService.addContext(this.apRendererService.renderUndo(this.apRendererService.renderDelete(this.userEntityService.genLocalUserUri(user.id), user), user));
|
|
||||||
const manager = this.apDeliverManagerService.createDeliverManager(user, content);
|
|
||||||
manager.addAllKnowingSharedInboxRecipe();
|
|
||||||
manager.addFollowersRecipe();
|
|
||||||
manager.execute();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -26,6 +26,10 @@ import { ApRendererService } from '@/core/activitypub/ApRendererService.js';
|
||||||
import { ModerationLogService } from '@/core/ModerationLogService.js';
|
import { ModerationLogService } from '@/core/ModerationLogService.js';
|
||||||
import { secureRndstr } from '@/misc/secure-rndstr.js';
|
import { secureRndstr } from '@/misc/secure-rndstr.js';
|
||||||
import { randomString } from '../utils.js';
|
import { randomString } from '../utils.js';
|
||||||
|
import { AccountUpdateService } from '@/core/AccountUpdateService.js';
|
||||||
|
import { ApDeliverManagerService } from '@/core/activitypub/ApDeliverManagerService.js';
|
||||||
|
import { RelayService } from '@/core/RelayService.js';
|
||||||
|
import { ApLoggerService } from '@/core/activitypub/ApLoggerService.js';
|
||||||
|
|
||||||
function genHost() {
|
function genHost() {
|
||||||
return randomString() + '.example.com';
|
return randomString() + '.example.com';
|
||||||
|
@ -42,6 +46,9 @@ describe('UserSuspendService', () => {
|
||||||
let globalEventService: jest.Mocked<GlobalEventService>;
|
let globalEventService: jest.Mocked<GlobalEventService>;
|
||||||
let apRendererService: jest.Mocked<ApRendererService>;
|
let apRendererService: jest.Mocked<ApRendererService>;
|
||||||
let moderationLogService: jest.Mocked<ModerationLogService>;
|
let moderationLogService: jest.Mocked<ModerationLogService>;
|
||||||
|
let accountUpdateService: jest.Mocked<AccountUpdateService>;
|
||||||
|
let apDeliverManagerService: jest.Mocked<ApDeliverManagerService>;
|
||||||
|
let relayService: jest.Mocked<RelayService>;
|
||||||
|
|
||||||
async function createUser(data: Partial<MiUser> = {}): Promise<MiUser> {
|
async function createUser(data: Partial<MiUser> = {}): Promise<MiUser> {
|
||||||
const user = {
|
const user = {
|
||||||
|
@ -84,6 +91,8 @@ describe('UserSuspendService', () => {
|
||||||
imports: [GlobalModule],
|
imports: [GlobalModule],
|
||||||
providers: [
|
providers: [
|
||||||
UserSuspendService,
|
UserSuspendService,
|
||||||
|
AccountUpdateService,
|
||||||
|
ApDeliverManagerService,
|
||||||
{
|
{
|
||||||
provide: UserEntityService,
|
provide: UserEntityService,
|
||||||
useFactory: () => ({
|
useFactory: () => ({
|
||||||
|
@ -94,6 +103,7 @@ describe('UserSuspendService', () => {
|
||||||
{
|
{
|
||||||
provide: QueueService,
|
provide: QueueService,
|
||||||
useFactory: () => ({
|
useFactory: () => ({
|
||||||
|
deliverMany: jest.fn(),
|
||||||
deliver: jest.fn(),
|
deliver: jest.fn(),
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
|
@ -103,20 +113,38 @@ describe('UserSuspendService', () => {
|
||||||
publishInternalEvent: jest.fn(),
|
publishInternalEvent: jest.fn(),
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
{
|
|
||||||
provide: ApRendererService,
|
|
||||||
useFactory: () => ({
|
|
||||||
addContext: jest.fn(),
|
|
||||||
renderDelete: jest.fn(),
|
|
||||||
renderUndo: jest.fn(),
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
provide: ModerationLogService,
|
provide: ModerationLogService,
|
||||||
useFactory: () => ({
|
useFactory: () => ({
|
||||||
log: jest.fn(),
|
log: jest.fn(),
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
provide: RelayService,
|
||||||
|
useFactory: () => ({
|
||||||
|
deliverToRelays: jest.fn(),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
provide: ApRendererService,
|
||||||
|
useFactory: () => ({
|
||||||
|
renderDelete: jest.fn(),
|
||||||
|
renderUndo: jest.fn(),
|
||||||
|
renderPerson: jest.fn(),
|
||||||
|
renderUpdate: jest.fn(),
|
||||||
|
addContext: jest.fn(),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
provide: ApLoggerService,
|
||||||
|
useFactory: () => ({
|
||||||
|
logger: {
|
||||||
|
createSubLogger: jest.fn().mockReturnValue({
|
||||||
|
info: jest.fn(), error: jest.fn(), warn: jest.fn(), debug: jest.fn(),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
},
|
||||||
],
|
],
|
||||||
}).compile();
|
}).compile();
|
||||||
|
|
||||||
|
@ -131,6 +159,9 @@ describe('UserSuspendService', () => {
|
||||||
globalEventService = app.get<GlobalEventService>(GlobalEventService) as jest.Mocked<GlobalEventService>;
|
globalEventService = app.get<GlobalEventService>(GlobalEventService) as jest.Mocked<GlobalEventService>;
|
||||||
apRendererService = app.get<ApRendererService>(ApRendererService) as jest.Mocked<ApRendererService>;
|
apRendererService = app.get<ApRendererService>(ApRendererService) as jest.Mocked<ApRendererService>;
|
||||||
moderationLogService = app.get<ModerationLogService>(ModerationLogService) as jest.Mocked<ModerationLogService>;
|
moderationLogService = app.get<ModerationLogService>(ModerationLogService) as jest.Mocked<ModerationLogService>;
|
||||||
|
accountUpdateService = app.get<AccountUpdateService>(AccountUpdateService) as jest.Mocked<AccountUpdateService>;
|
||||||
|
apDeliverManagerService = app.get<ApDeliverManagerService>(ApDeliverManagerService) as jest.Mocked<ApDeliverManagerService>;
|
||||||
|
relayService = app.get<RelayService>(RelayService) as jest.Mocked<RelayService>;
|
||||||
|
|
||||||
// Reset mocks
|
// Reset mocks
|
||||||
jest.clearAllMocks();
|
jest.clearAllMocks();
|
||||||
|
@ -311,42 +342,46 @@ describe('UserSuspendService', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('ActivityPub delivery', () => {
|
describe('ActivityPub delivery', () => {
|
||||||
test('should deliver Delete activity on suspend of local user', async () => {
|
test('should deliver Update Person activity on suspend of local user', async () => {
|
||||||
const localUser = await createUser({ host: null });
|
const localUser = await createUser({ host: null });
|
||||||
const moderator = await createUser();
|
const moderator = await createUser();
|
||||||
|
|
||||||
userEntityService.isLocalUser.mockReturnValue(true);
|
userEntityService.isLocalUser.mockReturnValue(true);
|
||||||
userEntityService.genLocalUserUri.mockReturnValue(`https://example.com/users/${localUser.id}`);
|
userEntityService.genLocalUserUri.mockReturnValue(`https://example.com/users/${localUser.id}`);
|
||||||
apRendererService.renderDelete.mockReturnValue({ type: 'Delete' } as any);
|
apRendererService.renderUpdate.mockReturnValue({ type: 'Update' } as any);
|
||||||
apRendererService.addContext.mockReturnValue({ '@context': '...', type: 'Delete' } as any);
|
apRendererService.renderPerson.mockReturnValue({ type: 'Person' } as any);
|
||||||
|
apRendererService.addContext.mockReturnValue({ '@context': '...', type: 'Update' } as any);
|
||||||
|
|
||||||
await userSuspendService.suspend(localUser, moderator);
|
await userSuspendService.suspend(localUser, moderator);
|
||||||
await setTimeout(250);
|
await setTimeout(250);
|
||||||
|
|
||||||
// ActivityPub配信が呼ばれているかチェック
|
// ActivityPub配信が呼ばれているかチェック
|
||||||
expect(userEntityService.isLocalUser).toHaveBeenCalledWith(localUser);
|
expect(userEntityService.isLocalUser).toHaveBeenCalledWith(localUser);
|
||||||
expect(apRendererService.renderDelete).toHaveBeenCalled();
|
expect(apRendererService.renderUpdate).toHaveBeenCalled();
|
||||||
|
expect(apRendererService.renderPerson).toHaveBeenCalled();
|
||||||
expect(apRendererService.addContext).toHaveBeenCalled();
|
expect(apRendererService.addContext).toHaveBeenCalled();
|
||||||
|
expect(queueService.deliverMany).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should deliver Undo Delete activity on unsuspend of local user', async () => {
|
test('should deliver Update Person activity on unsuspend of local user', async () => {
|
||||||
const localUser = await createUser({ host: null, isSuspended: true });
|
const localUser = await createUser({ host: null, isSuspended: true });
|
||||||
const moderator = await createUser();
|
const moderator = await createUser();
|
||||||
|
|
||||||
userEntityService.isLocalUser.mockReturnValue(true);
|
userEntityService.isLocalUser.mockReturnValue(true);
|
||||||
userEntityService.genLocalUserUri.mockReturnValue(`https://example.com/users/${localUser.id}`);
|
userEntityService.genLocalUserUri.mockReturnValue(`https://example.com/users/${localUser.id}`);
|
||||||
apRendererService.renderDelete.mockReturnValue({ type: 'Delete' } as any);
|
apRendererService.renderUpdate.mockReturnValue({ type: 'Update' } as any);
|
||||||
apRendererService.renderUndo.mockReturnValue({ type: 'Undo' } as any);
|
apRendererService.renderPerson.mockReturnValue({ type: 'Person' } as any);
|
||||||
apRendererService.addContext.mockReturnValue({ '@context': '...', type: 'Undo' } as any);
|
apRendererService.addContext.mockReturnValue({ '@context': '...', type: 'Update' } as any);
|
||||||
|
|
||||||
await userSuspendService.unsuspend(localUser, moderator);
|
await userSuspendService.suspend(localUser, moderator);
|
||||||
await setTimeout(250);
|
await setTimeout(250);
|
||||||
|
|
||||||
// ActivityPub配信が呼ばれているかチェック
|
// ActivityPub配信が呼ばれているかチェック
|
||||||
expect(userEntityService.isLocalUser).toHaveBeenCalledWith(localUser);
|
expect(userEntityService.isLocalUser).toHaveBeenCalledWith(localUser);
|
||||||
expect(apRendererService.renderDelete).toHaveBeenCalled();
|
expect(apRendererService.renderUpdate).toHaveBeenCalled();
|
||||||
expect(apRendererService.renderUndo).toHaveBeenCalled();
|
expect(apRendererService.renderPerson).toHaveBeenCalled();
|
||||||
expect(apRendererService.addContext).toHaveBeenCalled();
|
expect(apRendererService.addContext).toHaveBeenCalled();
|
||||||
|
expect(queueService.deliverMany).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should not deliver any activity on suspend of remote user', async () => {
|
test('should not deliver any activity on suspend of remote user', async () => {
|
||||||
|
|
Loading…
Reference in New Issue