Merge branch 'develop' into mkusername-empty

This commit is contained in:
Kagami Sascha Rosylight
2023-02-19 09:26:27 +01:00
25 changed files with 183 additions and 160 deletions

View File

@@ -61,7 +61,7 @@ export class CustomEmojiService {
await this.db.queryResultCache!.remove(['meta_emojis']);
this.globalEventService.publishBroadcastStream('emojiAdded', {
emoji: await this.emojiEntityService.pack(emoji.id),
emoji: await this.emojiEntityService.packDetailed(emoji.id),
});
}

View File

@@ -5,44 +5,59 @@ import type { Packed } from '@/misc/schema.js';
import type { } from '@/models/entities/Blocking.js';
import type { Emoji } from '@/models/entities/Emoji.js';
import { bindThis } from '@/decorators.js';
import { UserEntityService } from './UserEntityService.js';
@Injectable()
export class EmojiEntityService {
constructor(
@Inject(DI.emojisRepository)
private emojisRepository: EmojisRepository,
private userEntityService: UserEntityService,
) {
}
@bindThis
public async pack(
public async packSimple(
src: Emoji['id'] | Emoji,
opts: { omitHost?: boolean; omitId?: boolean; withUrl?: boolean; } = { omitHost: true, omitId: true, withUrl: true },
): Promise<Packed<'Emoji'>> {
opts = { omitHost: true, omitId: true, withUrl: true, ...opts };
): Promise<Packed<'EmojiSimple'>> {
const emoji = typeof src === 'object' ? src : await this.emojisRepository.findOneByOrFail({ id: src });
return {
id: opts.omitId ? undefined : emoji.id,
aliases: emoji.aliases,
name: emoji.name,
category: emoji.category,
host: opts.omitHost ? undefined : emoji.host,
// || emoji.originalUrl してるのは後方互換性のためpublicUrlはstringなので??はだめ)
url: opts.withUrl ? (emoji.publicUrl || emoji.originalUrl) : undefined,
url: emoji.publicUrl || emoji.originalUrl,
};
}
@bindThis
public packMany(
public packSimpleMany(
emojis: any[],
opts: { omitHost?: boolean; omitId?: boolean; withUrl?: boolean; } = {},
) {
return Promise.all(emojis.map(x => this.pack(x, opts)));
return Promise.all(emojis.map(x => this.packSimple(x)));
}
@bindThis
public async packDetailed(
src: Emoji['id'] | Emoji,
): Promise<Packed<'EmojiDetailed'>> {
const emoji = typeof src === 'object' ? src : await this.emojisRepository.findOneByOrFail({ id: src });
return {
id: emoji.id,
aliases: emoji.aliases,
name: emoji.name,
category: emoji.category,
host: emoji.host,
// || emoji.originalUrl してるのは後方互換性のためpublicUrlはstringなので??はだめ)
url: emoji.publicUrl || emoji.originalUrl,
};
}
@bindThis
public packDetailedMany(
emojis: any[],
) {
return Promise.all(emojis.map(x => this.packDetailed(x)));
}
}

View File

@@ -26,7 +26,7 @@ import { packedClipSchema } from '@/models/schema/clip.js';
import { packedFederationInstanceSchema } from '@/models/schema/federation-instance.js';
import { packedQueueCountSchema } from '@/models/schema/queue.js';
import { packedGalleryPostSchema } from '@/models/schema/gallery-post.js';
import { packedEmojiSchema } from '@/models/schema/emoji.js';
import { packedEmojiDetailedSchema, packedEmojiSimpleSchema } from '@/models/schema/emoji.js';
import { packedFlashSchema } from '@/models/schema/flash.js';
export const refs = {
@@ -57,7 +57,8 @@ export const refs = {
Clip: packedClipSchema,
FederationInstance: packedFederationInstanceSchema,
GalleryPost: packedGalleryPostSchema,
Emoji: packedEmojiSchema,
EmojiSimple: packedEmojiSimpleSchema,
EmojiDetailed: packedEmojiDetailedSchema,
Flash: packedFlashSchema,
};

View File

@@ -1,11 +1,37 @@
export const packedEmojiSchema = {
export const packedEmojiSimpleSchema = {
type: 'object',
properties: {
aliases: {
type: 'array',
optional: false, nullable: false,
items: {
type: 'string',
optional: false, nullable: false,
format: 'id',
},
},
name: {
type: 'string',
optional: false, nullable: false,
},
category: {
type: 'string',
optional: false, nullable: true,
},
url: {
type: 'string',
optional: false, nullable: false,
},
},
} as const;
export const packedEmojiDetailedSchema = {
type: 'object',
properties: {
id: {
type: 'string',
optional: true, nullable: false,
optional: false, nullable: false,
format: 'id',
example: 'xxxxxxxxxx',
},
aliases: {
type: 'array',
@@ -26,12 +52,12 @@ export const packedEmojiSchema = {
},
host: {
type: 'string',
optional: true, nullable: true,
optional: false, nullable: true,
description: 'The local host is represented with `null`.',
},
url: {
type: 'string',
optional: true, nullable: false,
optional: false, nullable: false,
},
},
} as const;

View File

@@ -219,8 +219,8 @@ export class ApiCallService implements OnApplicationShutdown {
const limit = Object.assign({}, ep.meta.limit);
if (!limit.key) {
limit.key = ep.name;
if (limit.key == null) {
(limit as any).key = ep.name;
}
// TODO: 毎リクエスト計算するのもあれだしキャッシュしたい

View File

@@ -1,4 +1,5 @@
import type { Schema } from '@/misc/schema.js';
import { RolePolicies } from '@/core/RoleService.js';
import * as ep___admin_meta from './endpoints/admin/meta.js';
import * as ep___admin_abuseUserReports from './endpoints/admin/abuse-user-reports.js';
@@ -659,7 +660,7 @@ export interface IEndpointMeta {
*/
readonly requireAdmin?: boolean;
readonly requireRolePolicy?: string;
readonly requireRolePolicy?: keyof RolePolicies;
/**
* エンドポイントのリミテーションに関するやつ

View File

@@ -56,7 +56,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
await this.db.queryResultCache!.remove(['meta_emojis']);
this.globalEventService.publishBroadcastStream('emojiUpdated', {
emojis: await this.emojiEntityService.packMany(ps.ids),
emojis: await this.emojiEntityService.packDetailedMany(ps.ids),
});
});
}

View File

@@ -92,7 +92,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
await this.db.queryResultCache!.remove(['meta_emojis']);
this.globalEventService.publishBroadcastStream('emojiAdded', {
emoji: await this.emojiEntityService.pack(copied.id),
emoji: await this.emojiEntityService.packDetailed(copied.id),
});
return {

View File

@@ -54,7 +54,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
}
this.globalEventService.publishBroadcastStream('emojiDeleted', {
emojis: await this.emojiEntityService.packMany(emojis),
emojis: await this.emojiEntityService.packDetailedMany(emojis),
});
});
}

View File

@@ -4,9 +4,9 @@ import { Endpoint } from '@/server/api/endpoint-base.js';
import type { EmojisRepository } from '@/models/index.js';
import { DI } from '@/di-symbols.js';
import { ModerationLogService } from '@/core/ModerationLogService.js';
import { ApiError } from '../../../error.js';
import { EmojiEntityService } from '@/core/entities/EmojiEntityService.js';
import { GlobalEventService } from '@/core/GlobalEventService.js';
import { ApiError } from '../../../error.js';
export const meta = {
tags: ['admin'],
@@ -57,7 +57,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
await this.db.queryResultCache!.remove(['meta_emojis']);
this.globalEventService.publishBroadcastStream('emojiDeleted', {
emojis: [await this.emojiEntityService.pack(emoji)],
emojis: [await this.emojiEntityService.packDetailed(emoji)],
});
this.moderationLogService.insertModerationLog(me, 'deleteEmoji', {

View File

@@ -101,7 +101,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
.take(ps.limit)
.getMany();
return this.emojiEntityService.packMany(emojis, { omitHost: false, omitId: false, withUrl: false });
return this.emojiEntityService.packDetailedMany(emojis);
});
}
}

View File

@@ -98,7 +98,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
emojis = await q.take(ps.limit).getMany();
}
return this.emojiEntityService.packMany(emojis, { omitHost: false, omitId: false, withUrl: false });
return this.emojiEntityService.packDetailedMany(emojis);
});
}
}

View File

@@ -56,7 +56,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
await this.db.queryResultCache!.remove(['meta_emojis']);
this.globalEventService.publishBroadcastStream('emojiUpdated', {
emojis: await this.emojiEntityService.packMany(ps.ids),
emojis: await this.emojiEntityService.packDetailedMany(ps.ids),
});
});
}

View File

@@ -52,7 +52,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
await this.db.queryResultCache!.remove(['meta_emojis']);
this.globalEventService.publishBroadcastStream('emojiUpdated', {
emojis: await this.emojiEntityService.packMany(ps.ids),
emojis: await this.emojiEntityService.packDetailedMany(ps.ids),
});
});
}

View File

@@ -54,7 +54,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
await this.db.queryResultCache!.remove(['meta_emojis']);
this.globalEventService.publishBroadcastStream('emojiUpdated', {
emojis: await this.emojiEntityService.packMany(ps.ids),
emojis: await this.emojiEntityService.packDetailedMany(ps.ids),
});
});
}

View File

@@ -3,9 +3,9 @@ import { DataSource } from 'typeorm';
import { Endpoint } from '@/server/api/endpoint-base.js';
import type { EmojisRepository } from '@/models/index.js';
import { DI } from '@/di-symbols.js';
import { ApiError } from '../../../error.js';
import { EmojiEntityService } from '@/core/entities/EmojiEntityService.js';
import { GlobalEventService } from '@/core/GlobalEventService.js';
import { ApiError } from '../../../error.js';
export const meta = {
tags: ['admin'],
@@ -68,7 +68,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
await this.db.queryResultCache!.remove(['meta_emojis']);
const updated = await this.emojiEntityService.pack(emoji.id);
const updated = await this.emojiEntityService.packDetailed(emoji.id);
if (emoji.name === ps.name) {
this.globalEventService.publishBroadcastStream('emojiUpdated', {
@@ -76,7 +76,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
});
} else {
this.globalEventService.publishBroadcastStream('emojiDeleted', {
emojis: [await this.emojiEntityService.pack(emoji)],
emojis: [await this.emojiEntityService.packDetailed(emoji)],
});
this.globalEventService.publishBroadcastStream('emojiAdded', {

View File

@@ -54,86 +54,22 @@ export const meta = {
},
mascotImageUrl: {
type: 'string',
optional: false, nullable: false,
optional: false, nullable: true,
default: '/assets/ai.png',
},
bannerUrl: {
type: 'string',
optional: false, nullable: false,
optional: false, nullable: true,
},
errorImageUrl: {
type: 'string',
optional: false, nullable: false,
optional: false, nullable: true,
default: 'https://xn--931a.moe/aiart/yubitun.png',
},
iconUrl: {
type: 'string',
optional: false, nullable: true,
},
maxNoteTextLength: {
type: 'number',
optional: false, nullable: false,
},
emojis: {
type: 'array',
optional: false, nullable: false,
items: {
type: 'object',
optional: false, nullable: false,
properties: {
id: {
type: 'string',
optional: false, nullable: false,
format: 'id',
},
aliases: {
type: 'array',
optional: false, nullable: false,
items: {
type: 'string',
optional: false, nullable: false,
},
},
category: {
type: 'string',
optional: false, nullable: true,
},
host: {
type: 'string',
optional: false, nullable: true,
},
url: {
type: 'string',
optional: false, nullable: false,
format: 'url',
},
},
},
},
ads: {
type: 'array',
optional: false, nullable: false,
items: {
type: 'object',
optional: false, nullable: false,
properties: {
place: {
type: 'string',
optional: false, nullable: false,
},
url: {
type: 'string',
optional: false, nullable: false,
format: 'url',
},
imageUrl: {
type: 'string',
optional: false, nullable: false,
format: 'url',
},
},
},
},
enableEmail: {
type: 'boolean',
optional: false, nullable: false,
@@ -146,10 +82,6 @@ export const meta = {
type: 'boolean',
optional: false, nullable: false,
},
proxyAccountName: {
type: 'string',
optional: false, nullable: true,
},
userStarForReactionFallback: {
type: 'boolean',
optional: true, nullable: false,
@@ -228,7 +160,7 @@ export const meta = {
optional: true, nullable: true,
},
smtpPort: {
type: 'string',
type: 'number',
optional: true, nullable: true,
},
smtpUser: {
@@ -299,6 +231,10 @@ export const meta = {
type: 'boolean',
optional: true, nullable: false,
},
policies: {
type: 'object',
optional: false, nullable: false,
},
},
},
} as const;
@@ -349,7 +285,6 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
iconUrl: instance.iconUrl,
backgroundImageUrl: instance.backgroundImageUrl,
logoImageUrl: instance.logoImageUrl,
maxNoteTextLength: MAX_NOTE_TEXT_LENGTH, // 後方互換性のため
defaultLightTheme: instance.defaultLightTheme,
defaultDarkTheme: instance.defaultDarkTheme,
enableEmail: instance.enableEmail,

View File

@@ -82,11 +82,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
});
return {
emojis: await this.emojiEntityService.packMany(emojis, {
omitId: true,
omitHost: true,
withUrl: true,
}),
emojis: await this.emojiEntityService.packSimpleMany(emojis),
};
});
}

View File

@@ -295,7 +295,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
iconUrl: instance.iconUrl,
backgroundImageUrl: instance.backgroundImageUrl,
logoImageUrl: instance.logoImageUrl,
maxNoteTextLength: MAX_NOTE_TEXT_LENGTH, // 後方互換性のため
maxNoteTextLength: MAX_NOTE_TEXT_LENGTH,
defaultLightTheme: instance.defaultLightTheme,
defaultDarkTheme: instance.defaultDarkTheme,
ads: ads.map(ad => ({

View File

@@ -42,10 +42,10 @@ export interface InternalStreamTypes {
export interface BroadcastTypes {
emojiAdded: {
emoji: Packed<'Emoji'>;
emoji: Packed<'EmojiDetailed'>;
};
emojiUpdated: {
emojis: Packed<'Emoji'>[];
emojis: Packed<'EmojiDetailed'>[];
};
emojiDeleted: {
emojis: {

View File

@@ -1,5 +1,16 @@
import Resolver from '../../src/activitypub/resolver.js';
import { IObject } from '../../src/activitypub/type.js';
import type { Config } from '@/config.js';
import type { ApDbResolverService } from '@/core/activitypub/ApDbResolverService.js';
import type { ApRendererService } from '@/core/activitypub/ApRendererService.js';
import type { ApRequestService } from '@/core/activitypub/ApRequestService.js';
import { Resolver } from '@/core/activitypub/ApResolverService.js';
import type { IObject } from '@/core/activitypub/type.js';
import type { HttpRequestService } from '@/core/HttpRequestService.js';
import type { InstanceActorService } from '@/core/InstanceActorService.js';
import type { LoggerService } from '@/core/LoggerService.js';
import type { MetaService } from '@/core/MetaService.js';
import type { UtilityService } from '@/core/UtilityService.js';
import { bindThis } from '@/decorators.js';
import type { NoteReactionsRepository, NotesRepository, PollsRepository, UsersRepository } from '@/models/index.js';
type MockResponse = {
type: string;
@@ -8,6 +19,25 @@ type MockResponse = {
export class MockResolver extends Resolver {
private _rs = new Map<string, MockResponse>();
constructor(loggerService: LoggerService) {
super(
{} as Config,
{} as UsersRepository,
{} as NotesRepository,
{} as PollsRepository,
{} as NoteReactionsRepository,
{} as UtilityService,
{} as InstanceActorService,
{} as MetaService,
{} as ApRequestService,
{} as HttpRequestService,
{} as ApRendererService,
{} as ApDbResolverService,
loggerService,
);
}
public async _register(uri: string, content: string | Record<string, any>, type = 'application/activity+json') {
this._rs.set(uri, {
type,

View File

@@ -2,8 +2,39 @@ process.env.NODE_ENV = 'test';
import * as assert from 'assert';
import rndstr from 'rndstr';
import { Test } from '@nestjs/testing';
import { jest } from '@jest/globals';
import { ApNoteService } from '@/core/activitypub/models/ApNoteService.js';
import { ApPersonService } from '@/core/activitypub/models/ApPersonService.js';
import { GlobalModule } from '@/GlobalModule.js';
import { CoreModule } from '@/core/CoreModule.js';
import { FederatedInstanceService } from '@/core/FederatedInstanceService.js';
import { LoggerService } from '@/core/LoggerService.js';
import { MockResolver } from '../misc/mock-resolver.js';
describe('ActivityPub', () => {
let noteService: ApNoteService;
let personService: ApPersonService;
let resolver: MockResolver;
beforeEach(async () => {
const app = await Test.createTestingModule({
imports: [GlobalModule, CoreModule],
}).compile();
await app.init();
app.enableShutdownHooks();
noteService = app.get<ApNoteService>(ApNoteService);
personService = app.get<ApPersonService>(ApPersonService);
resolver = new MockResolver(await app.resolve<LoggerService>(LoggerService));
// Prevent ApPersonService from fetching instance, as it causes Jest import-after-test error
const federatedInstanceService = app.get<FederatedInstanceService>(FederatedInstanceService);
jest.spyOn(federatedInstanceService, 'fetch').mockImplementation(() => new Promise(() => {}));
});
describe('Parse minimum object', () => {
const host = 'https://host1.test';
const preferredUsername = `${rndstr('A-Z', 4)}${rndstr('a-z', 4)}`;
@@ -28,13 +59,9 @@ describe('ActivityPub', () => {
};
test('Minimum Actor', async () => {
const { MockResolver } = await import('../misc/mock-resolver.js');
const { createPerson } = await import('../../src/activitypub/models/person.js');
const resolver = new MockResolver();
resolver._register(actor.id, actor);
const user = await createPerson(actor.id, resolver);
const user = await personService.createPerson(actor.id, resolver);
assert.deepStrictEqual(user.uri, actor.id);
assert.deepStrictEqual(user.username, actor.preferredUsername);
@@ -42,14 +69,10 @@ describe('ActivityPub', () => {
});
test('Minimum Note', async () => {
const { MockResolver } = await import('../misc/mock-resolver.js');
const { createNote } = await import('../../src/activitypub/models/note.js');
const resolver = new MockResolver();
resolver._register(actor.id, actor);
resolver._register(post.id, post);
const note = await createNote(post.id, resolver, true);
const note = await noteService.createNote(post.id, resolver, true);
assert.deepStrictEqual(note?.uri, post.id);
assert.deepStrictEqual(note.visibility, 'public');
@@ -75,13 +98,9 @@ describe('ActivityPub', () => {
};
test('Actor', async () => {
const { MockResolver } = await import('../misc/mock-resolver.js');
const { createPerson } = await import('../../src/activitypub/models/person.js');
const resolver = new MockResolver();
resolver._register(actor.id, actor);
const user = await createPerson(actor.id, resolver);
const user = await personService.createPerson(actor.id, resolver);
assert.deepStrictEqual(user.name, actor.name.substr(0, 128));
});