add role name

This commit is contained in:
samunohito 2024-02-15 13:20:22 +09:00
parent 089682c08d
commit 4bbf0457fa
5 changed files with 132 additions and 32 deletions

View file

@ -4,7 +4,7 @@
*/
import { Inject, Injectable, OnApplicationShutdown } from '@nestjs/common';
import { In, IsNull } from 'typeorm';
import { Brackets, In, IsNull, SelectQueryBuilder, WhereExpressionBuilder } from 'typeorm';
import * as Redis from 'ioredis';
import { DI } from '@/di-symbols.js';
import { IdService } from '@/core/IdService.js';
@ -40,6 +40,7 @@ export const fetchEmojisSortKeys = [
'license',
'isSensitive',
'localOnly',
'roleIdsThatCanBeUsedThisEmojiAsReaction',
] as const;
export type FetchEmojisSortKeys = typeof fetchEmojisSortKeys[number];
export type FetchEmojisParams = {
@ -57,14 +58,15 @@ export type FetchEmojisParams = {
isSensitive?: boolean;
localOnly?: boolean;
hostType?: FetchEmojisHostTypes;
roleIds?: string[];
},
sinceId?: string;
untilId?: string;
limit?: number;
page?: number;
sort?: {
key : FetchEmojisSortKeys;
direction : 'ASC' | 'DESC';
key: FetchEmojisSortKeys;
direction: 'ASC' | 'DESC';
}[]
}
@ -76,10 +78,8 @@ export class CustomEmojiService implements OnApplicationShutdown {
constructor(
@Inject(DI.redis)
private redisClient: Redis.Redis,
@Inject(DI.emojisRepository)
private emojisRepository: EmojisRepository,
private utilityService: UtilityService,
private idService: IdService,
private emojiEntityService: EmojiEntityService,
@ -441,6 +441,19 @@ export class CustomEmojiService implements OnApplicationShutdown {
@bindThis
public async fetchEmojis(params?: FetchEmojisParams) {
function multipleWordsToQuery(
query: string,
builder: SelectQueryBuilder<MiEmoji>,
action: (qb: WhereExpressionBuilder, word: string) => void,
) {
const words = query.split(/\s/);
builder.andWhere(new Brackets((qb => {
for (const word of words) {
action(qb, word);
}
})));
}
const builder = this.emojisRepository.createQueryBuilder('emoji');
if (params?.query) {
const q = params.query;
@ -453,41 +466,64 @@ export class CustomEmojiService implements OnApplicationShutdown {
builder.andWhere('emoji.updatedAt <= :updateAtTo', { updateAtTo: q.updatedAtTo });
}
if (q.name) {
builder.andWhere('emoji.name LIKE :name', { name: `%${q.name}%` });
multipleWordsToQuery(q.name, builder, (qb, word) => {
qb.orWhere('emoji.name LIKE :name', { name: `%${word}%` });
});
}
if (q.hostType === 'local') {
builder.andWhere('emoji.host IS NULL');
} else {
if (q.host) {
// noIndexScan
builder.andWhere('emoji.host LIKE :host', { host: `%${q.host}%` });
} else {
builder.andWhere('emoji.host IS NOT NULL');
switch (true) {
case q.hostType === 'local': {
builder.andWhere('emoji.host IS NULL');
break;
}
case q.hostType === 'remote': {
if (q.host) {
// noIndexScan
multipleWordsToQuery(q.host, builder, (qb, word) => {
qb.orWhere('emoji.host LIKE :host', { host: `%${word}%` });
});
} else {
builder.andWhere('emoji.host IS NOT NULL');
}
break;
}
}
if (q.uri) {
// noIndexScan
builder.andWhere('emoji.uri LIKE :uri', { url: `%${q.uri}%` });
multipleWordsToQuery(q.uri, builder, (qb, word) => {
qb.orWhere('emoji.uri LIKE :uri', { uri: `%${word}%` });
});
}
if (q.publicUrl) {
// noIndexScan
builder.andWhere('emoji.publicUrl LIKE :publicUrl', { publicUrl: `%${q.publicUrl}%` });
multipleWordsToQuery(q.publicUrl, builder, (qb, word) => {
qb.orWhere('emoji.publicUrl LIKE :publicUrl', { publicUrl: `%${word}%` });
});
}
if (q.type) {
// noIndexScan
builder.andWhere('emoji.type LIKE :type', { type: `%${q.type}%` });
multipleWordsToQuery(q.type, builder, (qb, word) => {
qb.orWhere('emoji.type LIKE :type', { type: `%${word}%` });
});
}
if (q.aliases) {
// noIndexScan
builder.andWhere('emoji.aliases ANY(:aliases)', { aliases: q.aliases });
multipleWordsToQuery(q.aliases, builder, (qb, word) => {
qb.orWhere('emoji.aliases LIKE :aliases', { aliases: `%${word}%` });
});
}
if (q.category) {
// noIndexScan
builder.andWhere('emoji.category LIKE :category', { category: `%${q.category}%` });
multipleWordsToQuery(q.category, builder, (qb, word) => {
qb.orWhere('emoji.category LIKE :category', { category: `%${word}%` });
});
}
if (q.license) {
// noIndexScan
builder.andWhere('emoji.license LIKE :license', { license: `%${q.license}%` });
multipleWordsToQuery(q.license, builder, (qb, word) => {
qb.orWhere('emoji.license LIKE :license', { license: `%${word}%` });
});
}
if (q.isSensitive != null) {
// noIndexScan

View file

@ -4,10 +4,10 @@
*/
import { Inject, Injectable } from '@nestjs/common';
import { In } from 'typeorm';
import { DI } from '@/di-symbols.js';
import type { EmojisRepository } from '@/models/_.js';
import type { EmojisRepository, MiRole, RolesRepository } from '@/models/_.js';
import type { Packed } from '@/misc/json-schema.js';
import type { } from '@/models/Blocking.js';
import type { MiEmoji } from '@/models/Emoji.js';
import { bindThis } from '@/decorators.js';
@ -16,6 +16,8 @@ export class EmojiEntityService {
constructor(
@Inject(DI.emojisRepository)
private emojisRepository: EmojisRepository,
@Inject(DI.rolesRepository)
private rolesRepository: RolesRepository,
) {
}
@ -75,9 +77,28 @@ export class EmojiEntityService {
@bindThis
public async packDetailedAdmin(
src: MiEmoji['id'] | MiEmoji,
hint?: {
roles?: Map<MiRole['id'], MiRole>
},
): Promise<Packed<'EmojiDetailedAdmin'>> {
const emoji = typeof src === 'object' ? src : await this.emojisRepository.findOneByOrFail({ id: src });
const roles = Array.of<MiRole>();
if (emoji.roleIdsThatCanBeUsedThisEmojiAsReaction.length > 0) {
if (hint?.roles) {
const hintRoles = hint.roles;
roles.push(
...emoji.roleIdsThatCanBeUsedThisEmojiAsReaction
.filter(x => hintRoles.has(x))
.map(x => hintRoles.get(x)!),
);
} else {
roles.push(
...await this.rolesRepository.findBy({ id: In(emoji.roleIdsThatCanBeUsedThisEmojiAsReaction) }),
);
}
}
return {
id: emoji.id,
updatedAt: emoji.updatedAt?.toISOString() ?? null,
@ -92,15 +113,39 @@ export class EmojiEntityService {
license: emoji.license,
localOnly: emoji.localOnly,
isSensitive: emoji.isSensitive,
roleIdsThatCanBeUsedThisEmojiAsReaction: emoji.roleIdsThatCanBeUsedThisEmojiAsReaction,
roleIdsThatCanBeUsedThisEmojiAsReaction: roles.map(it => ({ id: it.id, name: it.name })),
};
}
@bindThis
public packDetailedAdminMany(
emojis: any[],
public async packDetailedAdminMany(
emojis: MiEmoji['id'][] | MiEmoji[],
hint?: {
roles?: Map<MiRole['id'], MiRole>
},
): Promise<Packed<'EmojiDetailedAdmin'>[]> {
return Promise.all(emojis.map(x => this.packDetailedAdmin(x)));
// IDのみの要素をピックアップし、DBからレコードを取り出して他の値を補完する
const emojiEntities = emojis.filter(x => typeof x === 'object') as MiEmoji[];
const emojiIdOnlyList = emojis.filter(x => typeof x === 'string') as string[];
if (emojiIdOnlyList.length > 0) {
emojiEntities.push(...await this.emojisRepository.findBy({ id: In(emojiIdOnlyList) }));
}
// 特定ロール専用の絵文字である場合、そのロール情報をあらかじめまとめて取得しておくpack側で都度取得も出来るが負荷が高いので
let hintRoles: Map<MiRole['id'], MiRole>;
if (hint?.roles) {
hintRoles = hint.roles;
} else {
const roles = Array.of<MiRole>();
const roleIds = [...new Set(emojiEntities.flatMap(x => x.roleIdsThatCanBeUsedThisEmojiAsReaction))];
if (roleIds.length > 0) {
roles.push(...await this.rolesRepository.findBy({ id: In(roleIds) }));
}
hintRoles = new Map(roles.map(x => [x.id, x]));
}
return Promise.all(emojis.map(x => this.packDetailedAdmin(x, { roles: hintRoles })));
}
}

View file

@ -170,11 +170,19 @@ export const packedEmojiDetailedAdminSchema = {
},
roleIdsThatCanBeUsedThisEmojiAsReaction: {
type: 'array',
optional: false, nullable: false,
items: {
type: 'string',
optional: false, nullable: false,
format: 'id',
type: 'object',
properties: {
id: {
type: 'string',
format: 'misskey:id',
optional: false, nullable: false,
},
name: {
type: 'string',
optional: false, nullable: false,
},
},
},
},
},

View file

@ -53,6 +53,10 @@ export const paramDef = {
isSensitive: { type: 'boolean' },
localOnly: { type: 'boolean' },
hostType: { type: 'string', enum: ['local', 'remote', 'all'], default: 'all' },
roleIds: {
type: 'array',
items: { type: 'string', format: 'misskey:id' },
},
},
},
sinceId: { type: 'string', format: 'misskey:id' },
@ -79,6 +83,7 @@ export const paramDef = {
'license',
'isSensitive',
'localOnly',
'roleIdsThatCanBeUsedThisEmojiAsReaction',
],
default: 'id',
},
@ -119,6 +124,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
isSensitive: ps.query.isSensitive,
localOnly: ps.query.localOnly,
hostType: ps.query.hostType,
roleIds: ps.query.roleIds,
};
}

View file

@ -4462,7 +4462,11 @@ export type components = {
license: string | null;
localOnly: boolean;
isSensitive: boolean;
roleIdsThatCanBeUsedThisEmojiAsReaction: string[];
roleIdsThatCanBeUsedThisEmojiAsReaction: {
/** Format: misskey:id */
id: string;
name: string;
}[];
};
Flash: {
/**
@ -6963,6 +6967,7 @@ export type operations = {
* @enum {string}
*/
hostType?: 'local' | 'remote' | 'all';
roleIds?: string[];
}) | null;
/** Format: misskey:id */
sinceId?: string;
@ -6976,7 +6981,7 @@ export type operations = {
* @default id
* @enum {string}
*/
key: 'id' | 'updatedAt' | 'name' | 'host' | 'uri' | 'publicUrl' | 'type' | 'aliases' | 'category' | 'license' | 'isSensitive' | 'localOnly';
key: 'id' | 'updatedAt' | 'name' | 'host' | 'uri' | 'publicUrl' | 'type' | 'aliases' | 'category' | 'license' | 'isSensitive' | 'localOnly' | 'roleIdsThatCanBeUsedThisEmojiAsReaction';
/**
* @default DESC
* @enum {string}