summaryrefslogtreecommitdiff
path: root/packages/backend/src/server/api/endpoints
diff options
context:
space:
mode:
authorzyoshoka <107108195+zyoshoka@users.noreply.github.com>2025-05-27 08:57:09 +0900
committerGitHub <noreply@github.com>2025-05-27 08:57:09 +0900
commitd27075c5f536a86a6c81cb3c3cec92b302663e2e (patch)
treec9ab17782c4bc77da1f534c8731bbad5a0000849 /packages/backend/src/server/api/endpoints
parentfix(backend): add response schema for `notes/show-partial-bulk` endpoint (#16... (diff)
downloadmisskey-d27075c5f536a86a6c81cb3c3cec92b302663e2e.tar.gz
misskey-d27075c5f536a86a6c81cb3c3cec92b302663e2e.tar.bz2
misskey-d27075c5f536a86a6c81cb3c3cec92b302663e2e.zip
fix(backend): correct invalid schema format specifying only `required` for `anyOf` (#16089)
* fix(backend): correct invalid schema format specifying only `required` for `anyOf` * refactor(backend): make types derived from `allOf` or `anyOf` more strong
Diffstat (limited to 'packages/backend/src/server/api/endpoints')
-rw-r--r--packages/backend/src/server/api/endpoints/admin/drive/show-file.ts35
-rw-r--r--packages/backend/src/server/api/endpoints/admin/emoji/update.ts67
-rw-r--r--packages/backend/src/server/api/endpoints/drive/files/show.ts41
-rw-r--r--packages/backend/src/server/api/endpoints/i/revoke-token.ts23
-rw-r--r--packages/backend/src/server/api/endpoints/notes/search-by-tag.ts75
-rw-r--r--packages/backend/src/server/api/endpoints/pages/show.ts27
-rw-r--r--packages/backend/src/server/api/endpoints/users/followers.ts51
-rw-r--r--packages/backend/src/server/api/endpoints/users/following.ts54
-rw-r--r--packages/backend/src/server/api/endpoints/users/relation.ts2
-rw-r--r--packages/backend/src/server/api/endpoints/users/search-by-username-and-host.ts41
-rw-r--r--packages/backend/src/server/api/endpoints/users/show.test.ts23
-rw-r--r--packages/backend/src/server/api/endpoints/users/show.ts63
12 files changed, 318 insertions, 184 deletions
diff --git a/packages/backend/src/server/api/endpoints/admin/drive/show-file.ts b/packages/backend/src/server/api/endpoints/admin/drive/show-file.ts
index a7136d8c8c..b84a5c73f9 100644
--- a/packages/backend/src/server/api/endpoints/admin/drive/show-file.ts
+++ b/packages/backend/src/server/api/endpoints/admin/drive/show-file.ts
@@ -162,14 +162,21 @@ export const meta = {
} as const;
export const paramDef = {
- type: 'object',
- properties: {
- fileId: { type: 'string', format: 'misskey:id' },
- url: { type: 'string' },
- },
anyOf: [
- { required: ['fileId'] },
- { required: ['url'] },
+ {
+ type: 'object',
+ properties: {
+ fileId: { type: 'string', format: 'misskey:id' },
+ },
+ required: ['fileId'],
+ },
+ {
+ type: 'object',
+ properties: {
+ url: { type: 'string' },
+ },
+ required: ['url'],
+ },
],
} as const;
@@ -186,15 +193,11 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
private idService: IdService,
) {
super(meta, paramDef, async (ps, me) => {
- const file = ps.fileId ? await this.driveFilesRepository.findOneBy({ id: ps.fileId }) : await this.driveFilesRepository.findOne({
- where: [{
- url: ps.url,
- }, {
- thumbnailUrl: ps.url,
- }, {
- webpublicUrl: ps.url,
- }],
- });
+ const file = await this.driveFilesRepository.findOneBy(
+ 'fileId' in ps
+ ? { id: ps.fileId }
+ : [{ url: ps.url }, { thumbnailUrl: ps.url }, { webpublicUrl: ps.url }],
+ );
if (file == null) {
throw new ApiError(meta.errors.noSuchFile);
diff --git a/packages/backend/src/server/api/endpoints/admin/emoji/update.ts b/packages/backend/src/server/api/endpoints/admin/emoji/update.ts
index 6834a6d213..7bde10af46 100644
--- a/packages/backend/src/server/api/endpoints/admin/emoji/update.ts
+++ b/packages/backend/src/server/api/endpoints/admin/emoji/update.ts
@@ -37,29 +37,45 @@ export const meta = {
} as const;
export const paramDef = {
- type: 'object',
- properties: {
- id: { type: 'string', format: 'misskey:id' },
- name: { type: 'string', pattern: '^[a-zA-Z0-9_]+$' },
- fileId: { type: 'string', format: 'misskey:id' },
- category: {
- type: 'string',
- nullable: true,
- description: 'Use `null` to reset the category.',
+ allOf: [
+ {
+ anyOf: [
+ {
+ type: 'object',
+ properties: {
+ id: { type: 'string', format: 'misskey:id' },
+ },
+ required: ['id'],
+ },
+ {
+ type: 'object',
+ properties: {
+ name: { type: 'string', pattern: '^[a-zA-Z0-9_]+$' },
+ },
+ required: ['name'],
+ },
+ ],
+ },
+ {
+ type: 'object',
+ properties: {
+ fileId: { type: 'string', format: 'misskey:id' },
+ category: {
+ type: 'string',
+ nullable: true,
+ description: 'Use `null` to reset the category.',
+ },
+ aliases: { type: 'array', items: {
+ type: 'string',
+ } },
+ license: { type: 'string', nullable: true },
+ isSensitive: { type: 'boolean' },
+ localOnly: { type: 'boolean' },
+ roleIdsThatCanBeUsedThisEmojiAsReaction: { type: 'array', items: {
+ type: 'string',
+ } },
+ },
},
- aliases: { type: 'array', items: {
- type: 'string',
- } },
- license: { type: 'string', nullable: true },
- isSensitive: { type: 'boolean' },
- localOnly: { type: 'boolean' },
- roleIdsThatCanBeUsedThisEmojiAsReaction: { type: 'array', items: {
- type: 'string',
- } },
- },
- anyOf: [
- { required: ['id'] },
- { required: ['name'] },
],
} as const;
@@ -78,10 +94,9 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
if (driveFile == null) throw new ApiError(meta.errors.noSuchFile);
}
- // JSON schemeのanyOfの型変換がうまくいっていないらしい
- const required = { id: ps.id, name: ps.name } as
- | { id: MiEmoji['id']; name?: string }
- | { id?: MiEmoji['id']; name: string };
+ const required = 'id' in ps
+ ? { id: ps.id, name: 'name' in ps ? ps.name as string : undefined }
+ : { name: ps.name };
const error = await this.customEmojiService.update({
...required,
diff --git a/packages/backend/src/server/api/endpoints/drive/files/show.ts b/packages/backend/src/server/api/endpoints/drive/files/show.ts
index e8f4539d61..9a2e2c73e8 100644
--- a/packages/backend/src/server/api/endpoints/drive/files/show.ts
+++ b/packages/backend/src/server/api/endpoints/drive/files/show.ts
@@ -43,14 +43,21 @@ export const meta = {
} as const;
export const paramDef = {
- type: 'object',
- properties: {
- fileId: { type: 'string', format: 'misskey:id' },
- url: { type: 'string' },
- },
anyOf: [
- { required: ['fileId'] },
- { required: ['url'] },
+ {
+ type: 'object',
+ properties: {
+ fileId: { type: 'string', format: 'misskey:id' },
+ },
+ required: ['fileId'],
+ },
+ {
+ type: 'object',
+ properties: {
+ url: { type: 'string' },
+ },
+ required: ['url'],
+ },
],
} as const;
@@ -64,21 +71,11 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
private roleService: RoleService,
) {
super(meta, paramDef, async (ps, me) => {
- let file: MiDriveFile | null = null;
-
- if (ps.fileId) {
- file = await this.driveFilesRepository.findOneBy({ id: ps.fileId });
- } else if (ps.url) {
- file = await this.driveFilesRepository.findOne({
- where: [{
- url: ps.url,
- }, {
- webpublicUrl: ps.url,
- }, {
- thumbnailUrl: ps.url,
- }],
- });
- }
+ const file = await this.driveFilesRepository.findOneBy(
+ 'fileId' in ps
+ ? { id: ps.fileId }
+ : [{ url: ps.url }, { webpublicUrl: ps.url }, { thumbnailUrl: ps.url }],
+ );
if (file == null) {
throw new ApiError(meta.errors.noSuchFile);
diff --git a/packages/backend/src/server/api/endpoints/i/revoke-token.ts b/packages/backend/src/server/api/endpoints/i/revoke-token.ts
index c05ee93c6f..08f5e3a7a1 100644
--- a/packages/backend/src/server/api/endpoints/i/revoke-token.ts
+++ b/packages/backend/src/server/api/endpoints/i/revoke-token.ts
@@ -15,14 +15,21 @@ export const meta = {
} as const;
export const paramDef = {
- type: 'object',
- properties: {
- tokenId: { type: 'string', format: 'misskey:id' },
- token: { type: 'string', nullable: true },
- },
anyOf: [
- { required: ['tokenId'] },
- { required: ['token'] },
+ {
+ type: 'object',
+ properties: {
+ tokenId: { type: 'string', format: 'misskey:id' },
+ },
+ required: ['tokenId'],
+ },
+ {
+ type: 'object',
+ properties: {
+ token: { type: 'string', nullable: true },
+ },
+ required: ['token'],
+ },
],
} as const;
@@ -33,7 +40,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
private accessTokensRepository: AccessTokensRepository,
) {
super(meta, paramDef, async (ps, me) => {
- if (ps.tokenId) {
+ if ('tokenId' in ps) {
const tokenExist = await this.accessTokensRepository.exists({ where: { id: ps.tokenId } });
if (tokenExist) {
diff --git a/packages/backend/src/server/api/endpoints/notes/search-by-tag.ts b/packages/backend/src/server/api/endpoints/notes/search-by-tag.ts
index d0781bd8dd..3e41b33d03 100644
--- a/packages/backend/src/server/api/endpoints/notes/search-by-tag.ts
+++ b/packages/backend/src/server/api/endpoints/notes/search-by-tag.ts
@@ -28,38 +28,53 @@ export const meta = {
} as const;
export const paramDef = {
- type: 'object',
- properties: {
- reply: { type: 'boolean', nullable: true, default: null },
- renote: { type: 'boolean', nullable: true, default: null },
- withFiles: {
- type: 'boolean',
- default: false,
- description: 'Only show notes that have attached files.',
+ allOf: [
+ {
+ anyOf: [
+ {
+ type: 'object',
+ properties: {
+ tag: { type: 'string', minLength: 1 },
+ },
+ required: ['tag'],
+ },
+ {
+ type: 'object',
+ properties: {
+ query: {
+ type: 'array',
+ description: 'The outer arrays are chained with OR, the inner arrays are chained with AND.',
+ items: {
+ type: 'array',
+ items: {
+ type: 'string',
+ minLength: 1,
+ },
+ minItems: 1,
+ },
+ minItems: 1,
+ },
+ },
+ required: ['query'],
+ },
+ ],
},
- poll: { type: 'boolean', nullable: true, default: null },
- sinceId: { type: 'string', format: 'misskey:id' },
- untilId: { type: 'string', format: 'misskey:id' },
- limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 },
-
- tag: { type: 'string', minLength: 1 },
- query: {
- type: 'array',
- description: 'The outer arrays are chained with OR, the inner arrays are chained with AND.',
- items: {
- type: 'array',
- items: {
- type: 'string',
- minLength: 1,
+ {
+ type: 'object',
+ properties: {
+ reply: { type: 'boolean', nullable: true, default: null },
+ renote: { type: 'boolean', nullable: true, default: null },
+ withFiles: {
+ type: 'boolean',
+ default: false,
+ description: 'Only show notes that have attached files.',
},
- minItems: 1,
+ poll: { type: 'boolean', nullable: true, default: null },
+ sinceId: { type: 'string', format: 'misskey:id' },
+ untilId: { type: 'string', format: 'misskey:id' },
+ limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 },
},
- minItems: 1,
},
- },
- anyOf: [
- { required: ['tag'] },
- { required: ['query'] },
],
} as const;
@@ -87,12 +102,12 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
if (me) this.queryService.generateBlockedUserQueryForNotes(query, me);
try {
- if (ps.tag) {
+ if ('tag' in ps) {
if (!safeForSql(normalizeForSearch(ps.tag))) throw new Error('Injection');
query.andWhere(':tag <@ note.tags', { tag: [normalizeForSearch(ps.tag)] });
} else {
query.andWhere(new Brackets(qb => {
- for (const tags of ps.query!) {
+ for (const tags of ps.query) {
qb.orWhere(new Brackets(qb => {
for (const tag of tags) {
if (!safeForSql(normalizeForSearch(tag))) throw new Error('Injection');
diff --git a/packages/backend/src/server/api/endpoints/pages/show.ts b/packages/backend/src/server/api/endpoints/pages/show.ts
index e08b832a3f..8427bab2d5 100644
--- a/packages/backend/src/server/api/endpoints/pages/show.ts
+++ b/packages/backend/src/server/api/endpoints/pages/show.ts
@@ -33,15 +33,22 @@ export const meta = {
} as const;
export const paramDef = {
- type: 'object',
- properties: {
- pageId: { type: 'string', format: 'misskey:id' },
- name: { type: 'string' },
- username: { type: 'string' },
- },
anyOf: [
- { required: ['pageId'] },
- { required: ['name', 'username'] },
+ {
+ type: 'object',
+ properties: {
+ pageId: { type: 'string', format: 'misskey:id' },
+ },
+ required: ['pageId'],
+ },
+ {
+ type: 'object',
+ properties: {
+ name: { type: 'string' },
+ username: { type: 'string' },
+ },
+ required: ['name', 'username'],
+ },
],
} as const;
@@ -59,9 +66,9 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
super(meta, paramDef, async (ps, me) => {
let page: MiPage | null = null;
- if (ps.pageId) {
+ if ('pageId' in ps) {
page = await this.pagesRepository.findOneBy({ id: ps.pageId });
- } else if (ps.name && ps.username) {
+ } else {
const author = await this.usersRepository.findOneBy({
host: IsNull(),
usernameLower: ps.username.toLowerCase(),
diff --git a/packages/backend/src/server/api/endpoints/users/followers.ts b/packages/backend/src/server/api/endpoints/users/followers.ts
index a8b4319a61..bb8d4c49e9 100644
--- a/packages/backend/src/server/api/endpoints/users/followers.ts
+++ b/packages/backend/src/server/api/endpoints/users/followers.ts
@@ -47,23 +47,38 @@ export const meta = {
} as const;
export const paramDef = {
- type: 'object',
- properties: {
- sinceId: { type: 'string', format: 'misskey:id' },
- untilId: { type: 'string', format: 'misskey:id' },
- limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 },
-
- userId: { type: 'string', format: 'misskey:id' },
- username: { type: 'string' },
- host: {
- type: 'string',
- nullable: true,
- description: 'The local host is represented with `null`.',
+ allOf: [
+ {
+ anyOf: [
+ {
+ type: 'object',
+ properties: {
+ userId: { type: 'string', format: 'misskey:id' },
+ },
+ required: ['userId'],
+ },
+ {
+ type: 'object',
+ properties: {
+ username: { type: 'string' },
+ host: {
+ type: 'string',
+ nullable: true,
+ description: 'The local host is represented with `null`.',
+ },
+ },
+ required: ['username', 'host'],
+ },
+ ],
+ },
+ {
+ type: 'object',
+ properties: {
+ sinceId: { type: 'string', format: 'misskey:id' },
+ untilId: { type: 'string', format: 'misskey:id' },
+ limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 },
+ },
},
- },
- anyOf: [
- { required: ['userId'] },
- { required: ['username', 'host'] },
],
} as const;
@@ -85,9 +100,9 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
private roleService: RoleService,
) {
super(meta, paramDef, async (ps, me) => {
- const user = await this.usersRepository.findOneBy(ps.userId != null
+ const user = await this.usersRepository.findOneBy('userId' in ps
? { id: ps.userId }
- : { usernameLower: ps.username!.toLowerCase(), host: this.utilityService.toPunyNullable(ps.host) ?? IsNull() });
+ : { usernameLower: ps.username.toLowerCase(), host: this.utilityService.toPunyNullable(ps.host) ?? IsNull() });
if (user == null) {
throw new ApiError(meta.errors.noSuchUser);
diff --git a/packages/backend/src/server/api/endpoints/users/following.ts b/packages/backend/src/server/api/endpoints/users/following.ts
index feda5bb353..1fc87151b2 100644
--- a/packages/backend/src/server/api/endpoints/users/following.ts
+++ b/packages/backend/src/server/api/endpoints/users/following.ts
@@ -54,25 +54,39 @@ export const meta = {
} as const;
export const paramDef = {
- type: 'object',
- properties: {
- sinceId: { type: 'string', format: 'misskey:id' },
- untilId: { type: 'string', format: 'misskey:id' },
- limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 },
-
- userId: { type: 'string', format: 'misskey:id' },
- username: { type: 'string' },
- host: {
- type: 'string',
- nullable: true,
- description: 'The local host is represented with `null`.',
+ allOf: [
+ {
+ anyOf: [
+ {
+ type: 'object',
+ properties: {
+ userId: { type: 'string', format: 'misskey:id' },
+ },
+ required: ['userId'],
+ },
+ {
+ type: 'object',
+ properties: {
+ username: { type: 'string' },
+ host: {
+ type: 'string',
+ nullable: true,
+ description: 'The local host is represented with `null`.',
+ },
+ },
+ required: ['username', 'host'],
+ },
+ ],
+ },
+ {
+ type: 'object',
+ properties: {
+ sinceId: { type: 'string', format: 'misskey:id' },
+ untilId: { type: 'string', format: 'misskey:id' },
+ limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 },
+ birthday: { ...birthdaySchema, nullable: true },
+ },
},
-
- birthday: { ...birthdaySchema, nullable: true },
- },
- anyOf: [
- { required: ['userId'] },
- { required: ['username', 'host'] },
],
} as const;
@@ -94,9 +108,9 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
private roleService: RoleService,
) {
super(meta, paramDef, async (ps, me) => {
- const user = await this.usersRepository.findOneBy(ps.userId != null
+ const user = await this.usersRepository.findOneBy('userId' in ps
? { id: ps.userId }
- : { usernameLower: ps.username!.toLowerCase(), host: this.utilityService.toPunyNullable(ps.host) ?? IsNull() });
+ : { usernameLower: ps.username.toLowerCase(), host: this.utilityService.toPunyNullable(ps.host) ?? IsNull() });
if (user == null) {
throw new ApiError(meta.errors.noSuchUser);
diff --git a/packages/backend/src/server/api/endpoints/users/relation.ts b/packages/backend/src/server/api/endpoints/users/relation.ts
index 1d75437b81..f146095cf1 100644
--- a/packages/backend/src/server/api/endpoints/users/relation.ts
+++ b/packages/backend/src/server/api/endpoints/users/relation.ts
@@ -114,7 +114,7 @@ export const paramDef = {
type: 'object',
properties: {
userId: {
- anyOf: [
+ oneOf: [
{ type: 'string', format: 'misskey:id' },
{
type: 'array',
diff --git a/packages/backend/src/server/api/endpoints/users/search-by-username-and-host.ts b/packages/backend/src/server/api/endpoints/users/search-by-username-and-host.ts
index 134f1a8e87..d1d6354d53 100644
--- a/packages/backend/src/server/api/endpoints/users/search-by-username-and-host.ts
+++ b/packages/backend/src/server/api/endpoints/users/search-by-username-and-host.ts
@@ -26,17 +26,32 @@ export const meta = {
} as const;
export const paramDef = {
- type: 'object',
- properties: {
- limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 },
- detail: { type: 'boolean', default: true },
-
- username: { type: 'string', nullable: true },
- host: { type: 'string', nullable: true },
- },
- anyOf: [
- { required: ['username'] },
- { required: ['host'] },
+ allOf: [
+ {
+ anyOf: [
+ {
+ type: 'object',
+ properties: {
+ username: { type: 'string', nullable: true },
+ },
+ required: ['username'],
+ },
+ {
+ type: 'object',
+ properties: {
+ host: { type: 'string', nullable: true },
+ },
+ required: ['host'],
+ },
+ ],
+ },
+ {
+ type: 'object',
+ properties: {
+ limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 },
+ detail: { type: 'boolean', default: true },
+ },
+ },
],
} as const;
@@ -47,8 +62,8 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
) {
super(meta, paramDef, (ps, me) => {
return this.userSearchService.searchByUsernameAndHost({
- username: ps.username,
- host: ps.host,
+ username: 'username' in ps ? ps.username : undefined,
+ host: 'host' in ps ? ps.host : undefined,
}, {
limit: ps.limit,
detail: ps.detail,
diff --git a/packages/backend/src/server/api/endpoints/users/show.test.ts b/packages/backend/src/server/api/endpoints/users/show.test.ts
new file mode 100644
index 0000000000..068ffd8bc9
--- /dev/null
+++ b/packages/backend/src/server/api/endpoints/users/show.test.ts
@@ -0,0 +1,23 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and misskey-project
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+process.env.NODE_ENV = 'test';
+
+import { getValidator } from '../../../../../test/prelude/get-api-validator.js';
+import { paramDef } from './show.js';
+
+const VALID = true;
+const INVALID = false;
+
+describe('api:users/show', () => {
+ describe('validation', () => {
+ const v = getValidator(paramDef);
+
+ test('Reject empty', () => expect(v({})).toBe(INVALID));
+ test('Reject host only', () => expect(v({ host: 'misskey.test' })).toBe(INVALID));
+ test('Accept userId only', () => expect(v({ userId: '1' })).toBe(VALID));
+ test('Accept username and host', () => expect(v({ username: 'alice', host: 'misskey.test' })).toBe(VALID));
+ });
+});
diff --git a/packages/backend/src/server/api/endpoints/users/show.ts b/packages/backend/src/server/api/endpoints/users/show.ts
index 431869d47f..d57db42e6d 100644
--- a/packages/backend/src/server/api/endpoints/users/show.ts
+++ b/packages/backend/src/server/api/endpoints/users/show.ts
@@ -59,23 +59,44 @@ export const meta = {
} as const;
export const paramDef = {
- type: 'object',
- properties: {
- userId: { type: 'string', format: 'misskey:id' },
- userIds: { type: 'array', uniqueItems: true, items: {
- type: 'string', format: 'misskey:id',
- } },
- username: { type: 'string' },
- host: {
- type: 'string',
- nullable: true,
- description: 'The local host is represented with `null`.',
+ allOf: [
+ {
+ anyOf: [
+ {
+ type: 'object',
+ properties: {
+ userId: { type: 'string', format: 'misskey:id' },
+ },
+ required: ['userId'],
+ },
+ {
+ type: 'object',
+ properties: {
+ userIds: { type: 'array', uniqueItems: true, items: {
+ type: 'string', format: 'misskey:id',
+ } },
+ },
+ required: ['userIds'],
+ },
+ {
+ type: 'object',
+ properties: {
+ username: { type: 'string' },
+ },
+ required: ['username'],
+ },
+ ],
+ },
+ {
+ type: 'object',
+ properties: {
+ host: {
+ type: 'string',
+ nullable: true,
+ description: 'The local host is represented with `null`.',
+ },
+ },
},
- },
- anyOf: [
- { required: ['userId'] },
- { required: ['userIds'] },
- { required: ['username'] },
],
} as const;
@@ -102,9 +123,11 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
let user;
const isModerator = await this.roleService.isModerator(me);
- ps.username = ps.username?.trim();
+ if ('username' in ps) {
+ ps.username = ps.username.trim();
+ }
- if (ps.userIds) {
+ if ('userIds' in ps) {
if (ps.userIds.length === 0) {
return [];
}
@@ -129,7 +152,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
return _users.map(u => _userMap.get(u.id)!);
} else {
// Lookup user
- if (typeof ps.host === 'string' && typeof ps.username === 'string') {
+ if (typeof ps.host === 'string' && 'username' in ps) {
if (this.serverSettings.ugcVisibilityForVisitor === 'local' && me == null) {
throw new ApiError(meta.errors.noSuchUser);
}
@@ -139,7 +162,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
throw new ApiError(meta.errors.failedToResolveRemoteUser);
});
} else {
- const q: FindOptionsWhere<MiUser> = ps.userId != null
+ const q: FindOptionsWhere<MiUser> = 'userId' in ps
? { id: ps.userId }
: { usernameLower: ps.username!.toLowerCase(), host: IsNull() };