summaryrefslogtreecommitdiff
path: root/packages/backend/src/server/api/endpoints
diff options
context:
space:
mode:
authormisskey-release-bot[bot] <157398866+misskey-release-bot[bot]@users.noreply.github.com>2025-05-31 12:37:06 +0000
committerGitHub <noreply@github.com>2025-05-31 12:37:06 +0000
commit92b9a5218db145e9bb831cefd36c309de86083b5 (patch)
tree2ebad71633f9bbacabbc193254223146f1662aee /packages/backend/src/server/api/endpoints
parentMerge pull request #15933 from misskey-dev/develop (diff)
parentRelease: 2025.5.1 (diff)
downloadmisskey-92b9a5218db145e9bb831cefd36c309de86083b5.tar.gz
misskey-92b9a5218db145e9bb831cefd36c309de86083b5.tar.bz2
misskey-92b9a5218db145e9bb831cefd36c309de86083b5.zip
Merge pull request #16005 from misskey-dev/develop
Release: 2025.5.1
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/admin/invite/create.ts2
-rw-r--r--packages/backend/src/server/api/endpoints/admin/meta.ts31
-rw-r--r--packages/backend/src/server/api/endpoints/admin/queue/jobs.ts12
-rw-r--r--packages/backend/src/server/api/endpoints/admin/queue/queue-stats.ts113
-rw-r--r--packages/backend/src/server/api/endpoints/admin/queue/queues.ts42
-rw-r--r--packages/backend/src/server/api/endpoints/admin/queue/show-job.ts7
-rw-r--r--packages/backend/src/server/api/endpoints/admin/update-meta.ts33
-rw-r--r--packages/backend/src/server/api/endpoints/antennas/notes.ts5
-rw-r--r--packages/backend/src/server/api/endpoints/channels/timeline.ts7
-rw-r--r--packages/backend/src/server/api/endpoints/clips/notes.ts2
-rw-r--r--packages/backend/src/server/api/endpoints/drive/files/create.ts7
-rw-r--r--packages/backend/src/server/api/endpoints/drive/files/move-bulk.ts41
-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/children.ts7
-rw-r--r--packages/backend/src/server/api/endpoints/notes/global-timeline.ts7
-rw-r--r--packages/backend/src/server/api/endpoints/notes/hybrid-timeline.ts5
-rw-r--r--packages/backend/src/server/api/endpoints/notes/local-timeline.ts5
-rw-r--r--packages/backend/src/server/api/endpoints/notes/mentions.ts5
-rw-r--r--packages/backend/src/server/api/endpoints/notes/renotes.ts5
-rw-r--r--packages/backend/src/server/api/endpoints/notes/replies.ts5
-rw-r--r--packages/backend/src/server/api/endpoints/notes/search-by-tag.ts80
-rw-r--r--packages/backend/src/server/api/endpoints/notes/show-partial-bulk.ts66
-rw-r--r--packages/backend/src/server/api/endpoints/notes/show.ts15
-rw-r--r--packages/backend/src/server/api/endpoints/notes/timeline.ts5
-rw-r--r--packages/backend/src/server/api/endpoints/notes/user-list-timeline.ts5
-rw-r--r--packages/backend/src/server/api/endpoints/pages/show.ts27
-rw-r--r--packages/backend/src/server/api/endpoints/roles/notes.ts5
-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/notes.ts10
-rw-r--r--packages/backend/src/server/api/endpoints/users/recommendation.ts1
-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.ts80
38 files changed, 717 insertions, 255 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/admin/invite/create.ts b/packages/backend/src/server/api/endpoints/admin/invite/create.ts
index 5ecae3161a..e52b177e2b 100644
--- a/packages/backend/src/server/api/endpoints/admin/invite/create.ts
+++ b/packages/backend/src/server/api/endpoints/admin/invite/create.ts
@@ -68,6 +68,8 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
for (let i = 0; i < ps.count; i++) {
ticketsPromises.push(this.registrationTicketsRepository.insertOne({
id: this.idService.gen(),
+ createdBy: me,
+ createdById: me.id,
expiresAt: ps.expiresAt ? new Date(ps.expiresAt) : null,
code: generateInviteCode(),
}));
diff --git a/packages/backend/src/server/api/endpoints/admin/meta.ts b/packages/backend/src/server/api/endpoints/admin/meta.ts
index 4a106e7175..924163afbb 100644
--- a/packages/backend/src/server/api/endpoints/admin/meta.ts
+++ b/packages/backend/src/server/api/endpoints/admin/meta.ts
@@ -495,6 +495,10 @@ export const meta = {
type: 'boolean',
optional: false, nullable: false,
},
+ urlPreviewAllowRedirect: {
+ type: 'boolean',
+ optional: false, nullable: false,
+ },
urlPreviewTimeout: {
type: 'number',
optional: false, nullable: false,
@@ -546,6 +550,27 @@ export const meta = {
},
},
},
+ singleUserMode: {
+ type: 'boolean',
+ optional: false, nullable: false,
+ },
+ ugcVisibilityForVisitor: {
+ type: 'string',
+ enum: ['all', 'local', 'none'],
+ optional: false, nullable: false,
+ },
+ proxyRemoteFiles: {
+ type: 'boolean',
+ optional: false, nullable: false,
+ },
+ signToActivityPubGet: {
+ type: 'boolean',
+ optional: false, nullable: false,
+ },
+ allowExternalApRedirect: {
+ type: 'boolean',
+ optional: false, nullable: false,
+ },
},
},
} as const;
@@ -683,6 +708,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
notesPerOneAd: instance.notesPerOneAd,
summalyProxy: instance.urlPreviewSummaryProxyUrl,
urlPreviewEnabled: instance.urlPreviewEnabled,
+ urlPreviewAllowRedirect: instance.urlPreviewAllowRedirect,
urlPreviewTimeout: instance.urlPreviewTimeout,
urlPreviewMaximumContentLength: instance.urlPreviewMaximumContentLength,
urlPreviewRequireContentLength: instance.urlPreviewRequireContentLength,
@@ -691,6 +717,11 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
federation: instance.federation,
federationHosts: instance.federationHosts,
deliverSuspendedSoftware: instance.deliverSuspendedSoftware,
+ singleUserMode: instance.singleUserMode,
+ ugcVisibilityForVisitor: instance.ugcVisibilityForVisitor,
+ proxyRemoteFiles: instance.proxyRemoteFiles,
+ signToActivityPubGet: instance.signToActivityPubGet,
+ allowExternalApRedirect: instance.allowExternalApRedirect,
};
});
}
diff --git a/packages/backend/src/server/api/endpoints/admin/queue/jobs.ts b/packages/backend/src/server/api/endpoints/admin/queue/jobs.ts
index 79731c9786..a68e95bf3f 100644
--- a/packages/backend/src/server/api/endpoints/admin/queue/jobs.ts
+++ b/packages/backend/src/server/api/endpoints/admin/queue/jobs.ts
@@ -5,7 +5,6 @@
import { Injectable } from '@nestjs/common';
import { Endpoint } from '@/server/api/endpoint-base.js';
-import { ModerationLogService } from '@/core/ModerationLogService.js';
import { QUEUE_TYPES, QueueService } from '@/core/QueueService.js';
export const meta = {
@@ -14,13 +13,22 @@ export const meta = {
requireCredential: true,
requireModerator: true,
kind: 'read:admin:queue',
+
+ res: {
+ type: 'array',
+ optional: false, nullable: false,
+ items: {
+ optional: false, nullable: false,
+ ref: 'QueueJob',
+ },
+ },
} as const;
export const paramDef = {
type: 'object',
properties: {
queue: { type: 'string', enum: QUEUE_TYPES },
- state: { type: 'array', items: { type: 'string', enum: ['active', 'wait', 'delayed', 'completed', 'failed'] } },
+ state: { type: 'array', items: { type: 'string', enum: ['active', 'wait', 'delayed', 'completed', 'failed', 'paused'] } },
search: { type: 'string' },
},
required: ['queue', 'state'],
diff --git a/packages/backend/src/server/api/endpoints/admin/queue/queue-stats.ts b/packages/backend/src/server/api/endpoints/admin/queue/queue-stats.ts
index 10ce48332a..0098160165 100644
--- a/packages/backend/src/server/api/endpoints/admin/queue/queue-stats.ts
+++ b/packages/backend/src/server/api/endpoints/admin/queue/queue-stats.ts
@@ -5,7 +5,6 @@
import { Injectable } from '@nestjs/common';
import { Endpoint } from '@/server/api/endpoint-base.js';
-import { ModerationLogService } from '@/core/ModerationLogService.js';
import { QUEUE_TYPES, QueueService } from '@/core/QueueService.js';
export const meta = {
@@ -14,6 +13,118 @@ export const meta = {
requireCredential: true,
requireModerator: true,
kind: 'read:admin:queue',
+
+ res: {
+ type: 'object',
+ optional: false, nullable: false,
+ properties: {
+ name: {
+ type: 'string',
+ optional: false, nullable: false,
+ enum: QUEUE_TYPES,
+ },
+ qualifiedName: {
+ type: 'string',
+ optional: false, nullable: false,
+ },
+ counts: {
+ type: 'object',
+ optional: false, nullable: false,
+ additionalProperties: {
+ type: 'number',
+ },
+ },
+ isPaused: {
+ type: 'boolean',
+ optional: false, nullable: false,
+ },
+ metrics: {
+ type: 'object',
+ optional: false, nullable: false,
+ properties: {
+ completed: {
+ optional: false, nullable: false,
+ ref: 'QueueMetrics',
+ },
+ failed: {
+ optional: false, nullable: false,
+ ref: 'QueueMetrics',
+ },
+ },
+ },
+ db: {
+ type: 'object',
+ optional: false, nullable: false,
+ properties: {
+ version: {
+ type: 'string',
+ optional: false, nullable: false,
+ },
+ mode: {
+ type: 'string',
+ optional: false, nullable: false,
+ enum: ['cluster', 'standalone', 'sentinel'],
+ },
+ runId: {
+ type: 'string',
+ optional: false, nullable: false,
+ },
+ processId: {
+ type: 'string',
+ optional: false, nullable: false,
+ },
+ port: {
+ type: 'number',
+ optional: false, nullable: false,
+ },
+ os: {
+ type: 'string',
+ optional: false, nullable: false,
+ },
+ uptime: {
+ type: 'number',
+ optional: false, nullable: false,
+ },
+ memory: {
+ type: 'object',
+ optional: false, nullable: false,
+ properties: {
+ total: {
+ type: 'number',
+ optional: false, nullable: false,
+ },
+ used: {
+ type: 'number',
+ optional: false, nullable: false,
+ },
+ fragmentationRatio: {
+ type: 'number',
+ optional: false, nullable: false,
+ },
+ peak: {
+ type: 'number',
+ optional: false, nullable: false,
+ },
+ },
+ },
+ clients: {
+ type: 'object',
+ optional: false, nullable: false,
+ properties: {
+ blocked: {
+ type: 'number',
+ optional: false, nullable: false,
+ },
+ connected: {
+ type: 'number',
+ optional: false, nullable: false,
+ },
+ },
+ },
+ },
+ }
+ },
+ },
} as const;
export const paramDef = {
diff --git a/packages/backend/src/server/api/endpoints/admin/queue/queues.ts b/packages/backend/src/server/api/endpoints/admin/queue/queues.ts
index 3a38275f60..8d27e38c84 100644
--- a/packages/backend/src/server/api/endpoints/admin/queue/queues.ts
+++ b/packages/backend/src/server/api/endpoints/admin/queue/queues.ts
@@ -5,7 +5,6 @@
import { Injectable } from '@nestjs/common';
import { Endpoint } from '@/server/api/endpoint-base.js';
-import { ModerationLogService } from '@/core/ModerationLogService.js';
import { QUEUE_TYPES, QueueService } from '@/core/QueueService.js';
export const meta = {
@@ -14,6 +13,47 @@ export const meta = {
requireCredential: true,
requireModerator: true,
kind: 'read:admin:queue',
+
+ res: {
+ type: 'array',
+ optional: false, nullable: false,
+ items: {
+ type: 'object',
+ optional: false, nullable: false,
+ properties: {
+ name: {
+ type: 'string',
+ optional: false, nullable: false,
+ enum: QUEUE_TYPES,
+ },
+ counts: {
+ type: 'object',
+ optional: false, nullable: false,
+ additionalProperties: {
+ type: 'number',
+ },
+ },
+ isPaused: {
+ type: 'boolean',
+ optional: false, nullable: false,
+ },
+ metrics: {
+ type: 'object',
+ optional: false, nullable: false,
+ properties: {
+ completed: {
+ optional: false, nullable: false,
+ ref: 'QueueMetrics',
+ },
+ failed: {
+ optional: false, nullable: false,
+ ref: 'QueueMetrics',
+ },
+ },
+ },
+ },
+ },
+ },
} as const;
export const paramDef = {
diff --git a/packages/backend/src/server/api/endpoints/admin/queue/show-job.ts b/packages/backend/src/server/api/endpoints/admin/queue/show-job.ts
index 63747b5540..1735c22674 100644
--- a/packages/backend/src/server/api/endpoints/admin/queue/show-job.ts
+++ b/packages/backend/src/server/api/endpoints/admin/queue/show-job.ts
@@ -5,7 +5,6 @@
import { Injectable } from '@nestjs/common';
import { Endpoint } from '@/server/api/endpoint-base.js';
-import { ModerationLogService } from '@/core/ModerationLogService.js';
import { QUEUE_TYPES, QueueService } from '@/core/QueueService.js';
export const meta = {
@@ -14,6 +13,11 @@ export const meta = {
requireCredential: true,
requireModerator: true,
kind: 'read:admin:queue',
+
+ res: {
+ optional: false, nullable: false,
+ ref: 'QueueJob',
+ },
} as const;
export const paramDef = {
@@ -28,7 +32,6 @@ export const paramDef = {
@Injectable()
export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
constructor(
- private moderationLogService: ModerationLogService,
private queueService: QueueService,
) {
super(meta, paramDef, async (ps, me) => {
diff --git a/packages/backend/src/server/api/endpoints/admin/update-meta.ts b/packages/backend/src/server/api/endpoints/admin/update-meta.ts
index 31eeaa5e38..578aa2b662 100644
--- a/packages/backend/src/server/api/endpoints/admin/update-meta.ts
+++ b/packages/backend/src/server/api/endpoints/admin/update-meta.ts
@@ -170,6 +170,7 @@ export const paramDef = {
description: '[Deprecated] Use "urlPreviewSummaryProxyUrl" instead.',
},
urlPreviewEnabled: { type: 'boolean' },
+ urlPreviewAllowRedirect: { type: 'boolean' },
urlPreviewTimeout: { type: 'integer' },
urlPreviewMaximumContentLength: { type: 'integer' },
urlPreviewRequireContentLength: { type: 'boolean' },
@@ -196,6 +197,14 @@ export const paramDef = {
required: ['software', 'versionRange'],
},
},
+ singleUserMode: { type: 'boolean' },
+ ugcVisibilityForVisitor: {
+ type: 'string',
+ enum: ['all', 'local', 'none'],
+ },
+ proxyRemoteFiles: { type: 'boolean' },
+ signToActivityPubGet: { type: 'boolean' },
+ allowExternalApRedirect: { type: 'boolean' },
},
required: [],
} as const;
@@ -656,6 +665,10 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
set.urlPreviewEnabled = ps.urlPreviewEnabled;
}
+ if (ps.urlPreviewAllowRedirect !== undefined) {
+ set.urlPreviewAllowRedirect = ps.urlPreviewAllowRedirect;
+ }
+
if (ps.urlPreviewTimeout !== undefined) {
set.urlPreviewTimeout = ps.urlPreviewTimeout;
}
@@ -690,6 +703,26 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
set.federationHosts = ps.federationHosts.filter(Boolean).map(x => x.toLowerCase());
}
+ if (ps.singleUserMode !== undefined) {
+ set.singleUserMode = ps.singleUserMode;
+ }
+
+ if (ps.ugcVisibilityForVisitor !== undefined) {
+ set.ugcVisibilityForVisitor = ps.ugcVisibilityForVisitor;
+ }
+
+ if (ps.proxyRemoteFiles !== undefined) {
+ set.proxyRemoteFiles = ps.proxyRemoteFiles;
+ }
+
+ if (ps.signToActivityPubGet !== undefined) {
+ set.signToActivityPubGet = ps.signToActivityPubGet;
+ }
+
+ if (ps.allowExternalApRedirect !== undefined) {
+ set.allowExternalApRedirect = ps.allowExternalApRedirect;
+ }
+
const before = await this.metaService.fetch(true);
await this.metaService.update(set);
diff --git a/packages/backend/src/server/api/endpoints/antennas/notes.ts b/packages/backend/src/server/api/endpoints/antennas/notes.ts
index f37cdc6658..b2d9cea03c 100644
--- a/packages/backend/src/server/api/endpoints/antennas/notes.ts
+++ b/packages/backend/src/server/api/endpoints/antennas/notes.ts
@@ -111,11 +111,8 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
// NOTE: センシティブ除外の設定はこのエンドポイントでは無視する。
// https://github.com/misskey-dev/misskey/pull/15346#discussion_r1929950255
- this.queryService.generateBlockedHostQueryForNote(query);
- this.queryService.generateSuspendedUserQueryForNote(query);
this.queryService.generateVisibilityQuery(query, me);
- this.queryService.generateMutedUserQueryForNotes(query, me);
- this.queryService.generateBlockedUserQueryForNotes(query, me);
+ this.queryService.generateBaseNoteFilteringQuery(query, me);
const notes = await query.getMany();
if (sinceId != null && untilId == null) {
diff --git a/packages/backend/src/server/api/endpoints/channels/timeline.ts b/packages/backend/src/server/api/endpoints/channels/timeline.ts
index 2401ab8208..46b050d4b4 100644
--- a/packages/backend/src/server/api/endpoints/channels/timeline.ts
+++ b/packages/backend/src/server/api/endpoints/channels/timeline.ts
@@ -121,12 +121,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
.leftJoinAndSelect('renote.user', 'renoteUser')
.leftJoinAndSelect('note.channel', 'channel');
- this.queryService.generateBlockedHostQueryForNote(query);
- this.queryService.generateSuspendedUserQueryForNote(query);
- if (me) {
- this.queryService.generateMutedUserQueryForNotes(query, me);
- this.queryService.generateBlockedUserQueryForNotes(query, me);
- }
+ this.queryService.generateBaseNoteFilteringQuery(query, me);
//#endregion
return await query.limit(ps.limit).getMany();
diff --git a/packages/backend/src/server/api/endpoints/clips/notes.ts b/packages/backend/src/server/api/endpoints/clips/notes.ts
index 33f32d1d8a..4869ffd402 100644
--- a/packages/backend/src/server/api/endpoints/clips/notes.ts
+++ b/packages/backend/src/server/api/endpoints/clips/notes.ts
@@ -91,6 +91,8 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
if (me) {
this.queryService.generateMutedUserQueryForNotes(query, me);
this.queryService.generateBlockedUserQueryForNotes(query, me);
+ this.queryService.generateMutedUserQueryForNotes(query, me, { noteColumn: 'renote' });
+ this.queryService.generateBlockedUserQueryForNotes(query, me, { noteColumn: 'renote' });
}
const notes = await query
diff --git a/packages/backend/src/server/api/endpoints/drive/files/create.ts b/packages/backend/src/server/api/endpoints/drive/files/create.ts
index 11c255a361..7d5c0ccd4d 100644
--- a/packages/backend/src/server/api/endpoints/drive/files/create.ts
+++ b/packages/backend/src/server/api/endpoints/drive/files/create.ts
@@ -63,6 +63,12 @@ export const meta = {
id: 'b9d8c348-33f0-4673-b9a9-5d4da058977a',
httpStatusCode: 413,
},
+
+ unallowedFileType: {
+ message: 'Cannot upload the file because it is an unallowed file type.',
+ code: 'UNALLOWED_FILE_TYPE',
+ id: '4becd248-7f2c-48c4-a9f0-75edc4f9a1ea',
+ },
},
} as const;
@@ -123,6 +129,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
if (err.id === '282f77bf-5816-4f72-9264-aa14d8261a21') throw new ApiError(meta.errors.inappropriate);
if (err.id === 'c6244ed2-a39a-4e1c-bf93-f0fbd7764fa6') throw new ApiError(meta.errors.noFreeSpace);
if (err.id === 'f9e4e5f3-4df4-40b5-b400-f236945f7073') throw new ApiError(meta.errors.maxFileSizeExceeded);
+ if (err.id === 'bd71c601-f9b0-4808-9137-a330647ced9b') throw new ApiError(meta.errors.unallowedFileType);
}
throw new ApiError();
} finally {
diff --git a/packages/backend/src/server/api/endpoints/drive/files/move-bulk.ts b/packages/backend/src/server/api/endpoints/drive/files/move-bulk.ts
new file mode 100644
index 0000000000..c8500895eb
--- /dev/null
+++ b/packages/backend/src/server/api/endpoints/drive/files/move-bulk.ts
@@ -0,0 +1,41 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and misskey-project
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+import { Inject, Injectable } from '@nestjs/common';
+import { Endpoint } from '@/server/api/endpoint-base.js';
+import { DI } from '@/di-symbols.js';
+import { DriveService } from '@/core/DriveService.js';
+import { ApiError } from '../../../error.js';
+
+export const meta = {
+ tags: ['drive'],
+
+ requireCredential: true,
+
+ kind: 'write:drive',
+
+ errors: {
+ },
+} as const;
+
+export const paramDef = {
+ type: 'object',
+ properties: {
+ fileIds: { type: 'array', uniqueItems: true, minItems: 1, maxItems: 100, items: { type: 'string', format: 'misskey:id' } },
+ folderId: { type: 'string', format: 'misskey:id', nullable: true },
+ },
+ required: ['fileIds'],
+} as const;
+
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
+ constructor(
+ private driveService: DriveService,
+ ) {
+ super(meta, paramDef, async (ps, me) => {
+ await this.driveService.moveFiles(ps.fileIds, ps.folderId ?? null, me.id);
+ });
+ }
+}
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/children.ts b/packages/backend/src/server/api/endpoints/notes/children.ts
index 712a86eb13..d457ad1220 100644
--- a/packages/backend/src/server/api/endpoints/notes/children.ts
+++ b/packages/backend/src/server/api/endpoints/notes/children.ts
@@ -70,12 +70,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
.leftJoinAndSelect('renote.user', 'renoteUser');
this.queryService.generateVisibilityQuery(query, me);
- this.queryService.generateBlockedHostQueryForNote(query);
- this.queryService.generateSuspendedUserQueryForNote(query);
- if (me) {
- this.queryService.generateMutedUserQueryForNotes(query, me);
- this.queryService.generateBlockedUserQueryForNotes(query, me);
- }
+ this.queryService.generateBaseNoteFilteringQuery(query, me);
const notes = await query.limit(ps.limit).getMany();
diff --git a/packages/backend/src/server/api/endpoints/notes/global-timeline.ts b/packages/backend/src/server/api/endpoints/notes/global-timeline.ts
index 8d38bb1c65..1c73edf08e 100644
--- a/packages/backend/src/server/api/endpoints/notes/global-timeline.ts
+++ b/packages/backend/src/server/api/endpoints/notes/global-timeline.ts
@@ -78,11 +78,8 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
.leftJoinAndSelect('reply.user', 'replyUser')
.leftJoinAndSelect('renote.user', 'renoteUser');
- if (me) {
- this.queryService.generateMutedUserQueryForNotes(query, me);
- this.queryService.generateBlockedUserQueryForNotes(query, me);
- this.queryService.generateMutedUserRenotesQueryForNotes(query, me);
- }
+ this.queryService.generateBaseNoteFilteringQuery(query, me);
+ if (me) this.queryService.generateMutedUserRenotesQueryForNotes(query, me);
if (ps.withFiles) {
query.andWhere('note.fileIds != \'{}\'');
diff --git a/packages/backend/src/server/api/endpoints/notes/hybrid-timeline.ts b/packages/backend/src/server/api/endpoints/notes/hybrid-timeline.ts
index 6a3ee817e4..2c8459525a 100644
--- a/packages/backend/src/server/api/endpoints/notes/hybrid-timeline.ts
+++ b/packages/backend/src/server/api/endpoints/notes/hybrid-timeline.ts
@@ -243,10 +243,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
}
this.queryService.generateVisibilityQuery(query, me);
- this.queryService.generateBlockedHostQueryForNote(query);
- this.queryService.generateSuspendedUserQueryForNote(query);
- this.queryService.generateMutedUserQueryForNotes(query, me);
- this.queryService.generateBlockedUserQueryForNotes(query, me);
+ this.queryService.generateBaseNoteFilteringQuery(query, me);
this.queryService.generateMutedUserRenotesQueryForNotes(query, me);
if (ps.includeMyRenotes === false) {
diff --git a/packages/backend/src/server/api/endpoints/notes/local-timeline.ts b/packages/backend/src/server/api/endpoints/notes/local-timeline.ts
index d1dc22f233..ee61ab43da 100644
--- a/packages/backend/src/server/api/endpoints/notes/local-timeline.ts
+++ b/packages/backend/src/server/api/endpoints/notes/local-timeline.ts
@@ -156,10 +156,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
.leftJoinAndSelect('renote.user', 'renoteUser');
this.queryService.generateVisibilityQuery(query, me);
- this.queryService.generateBlockedHostQueryForNote(query);
- this.queryService.generateSuspendedUserQueryForNote(query);
- if (me) this.queryService.generateMutedUserQueryForNotes(query, me);
- if (me) this.queryService.generateBlockedUserQueryForNotes(query, me);
+ this.queryService.generateBaseNoteFilteringQuery(query, me);
if (me) this.queryService.generateMutedUserRenotesQueryForNotes(query, me);
if (ps.withFiles) {
diff --git a/packages/backend/src/server/api/endpoints/notes/mentions.ts b/packages/backend/src/server/api/endpoints/notes/mentions.ts
index c3722b1b5a..7ffc349db5 100644
--- a/packages/backend/src/server/api/endpoints/notes/mentions.ts
+++ b/packages/backend/src/server/api/endpoints/notes/mentions.ts
@@ -72,11 +72,8 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
.leftJoinAndSelect('renote.user', 'renoteUser');
this.queryService.generateVisibilityQuery(query, me);
- this.queryService.generateBlockedHostQueryForNote(query);
- this.queryService.generateSuspendedUserQueryForNote(query);
- this.queryService.generateMutedUserQueryForNotes(query, me);
+ this.queryService.generateBaseNoteFilteringQuery(query, me);
this.queryService.generateMutedNoteThreadQuery(query, me);
- this.queryService.generateBlockedUserQueryForNotes(query, me);
if (ps.visibility) {
query.andWhere('note.visibility = :visibility', { visibility: ps.visibility });
diff --git a/packages/backend/src/server/api/endpoints/notes/renotes.ts b/packages/backend/src/server/api/endpoints/notes/renotes.ts
index ce2435b8eb..fa2306c1bf 100644
--- a/packages/backend/src/server/api/endpoints/notes/renotes.ts
+++ b/packages/backend/src/server/api/endpoints/notes/renotes.ts
@@ -72,10 +72,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
.leftJoinAndSelect('renote.user', 'renoteUser');
this.queryService.generateVisibilityQuery(query, me);
- this.queryService.generateBlockedHostQueryForNote(query);
- this.queryService.generateSuspendedUserQueryForNote(query);
- if (me) this.queryService.generateMutedUserQueryForNotes(query, me);
- if (me) this.queryService.generateBlockedUserQueryForNotes(query, me);
+ this.queryService.generateBaseNoteFilteringQuery(query, me);
const renotes = await query.limit(ps.limit).getMany();
diff --git a/packages/backend/src/server/api/endpoints/notes/replies.ts b/packages/backend/src/server/api/endpoints/notes/replies.ts
index f491cc38ab..9626947480 100644
--- a/packages/backend/src/server/api/endpoints/notes/replies.ts
+++ b/packages/backend/src/server/api/endpoints/notes/replies.ts
@@ -56,10 +56,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
.leftJoinAndSelect('renote.user', 'renoteUser');
this.queryService.generateVisibilityQuery(query, me);
- this.queryService.generateBlockedHostQueryForNote(query);
- this.queryService.generateSuspendedUserQueryForNote(query);
- if (me) this.queryService.generateMutedUserQueryForNotes(query, me);
- if (me) this.queryService.generateBlockedUserQueryForNotes(query, me);
+ this.queryService.generateBaseNoteFilteringQuery(query, me);
const timeline = await query.limit(ps.limit).getMany();
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..51255f0bbf 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;
@@ -81,18 +96,15 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
.leftJoinAndSelect('renote.user', 'renoteUser');
this.queryService.generateVisibilityQuery(query, me);
- this.queryService.generateBlockedHostQueryForNote(query);
- this.queryService.generateSuspendedUserQueryForNote(query);
- if (me) this.queryService.generateMutedUserQueryForNotes(query, me);
- if (me) this.queryService.generateBlockedUserQueryForNotes(query, me);
+ this.queryService.generateBaseNoteFilteringQuery(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/notes/show-partial-bulk.ts b/packages/backend/src/server/api/endpoints/notes/show-partial-bulk.ts
new file mode 100644
index 0000000000..e102bc1d4a
--- /dev/null
+++ b/packages/backend/src/server/api/endpoints/notes/show-partial-bulk.ts
@@ -0,0 +1,66 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and misskey-project
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+import { Injectable } from '@nestjs/common';
+import { Endpoint } from '@/server/api/endpoint-base.js';
+import { NoteEntityService } from '@/core/entities/NoteEntityService.js';
+import { GetterService } from '@/server/api/GetterService.js';
+import { ApiError } from '../../error.js';
+
+export const meta = {
+ tags: ['notes'],
+
+ requireCredential: false,
+
+ res: {
+ type: 'array',
+ optional: false, nullable: false,
+ items: {
+ type: 'object',
+ properties: {
+ id: {
+ type: 'string',
+ optional: false, nullable: false,
+ },
+ reactions: {
+ type: 'object',
+ optional: false, nullable: false,
+ additionalProperties: {
+ type: 'number',
+ },
+ },
+ reactionEmojis: {
+ type: 'object',
+ optional: false, nullable: false,
+ additionalProperties: {
+ type: 'string',
+ },
+ },
+ },
+ },
+ },
+
+ errors: {
+ },
+} as const;
+
+export const paramDef = {
+ type: 'object',
+ properties: {
+ noteIds: { type: 'array', items: { type: 'string', format: 'misskey:id' }, maxItems: 100, minItems: 1 },
+ },
+ required: ['noteIds'],
+} as const;
+
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
+ constructor(
+ private noteEntityService: NoteEntityService,
+ ) {
+ super(meta, paramDef, async (ps, me) => {
+ return await this.noteEntityService.fetchDiffs(ps.noteIds);
+ });
+ }
+}
diff --git a/packages/backend/src/server/api/endpoints/notes/show.ts b/packages/backend/src/server/api/endpoints/notes/show.ts
index 11839bce36..b93c73b0c5 100644
--- a/packages/backend/src/server/api/endpoints/notes/show.ts
+++ b/packages/backend/src/server/api/endpoints/notes/show.ts
@@ -3,10 +3,12 @@
* SPDX-License-Identifier: AGPL-3.0-only
*/
-import { Injectable } from '@nestjs/common';
+import { Inject, Injectable } from '@nestjs/common';
import { Endpoint } from '@/server/api/endpoint-base.js';
import { NoteEntityService } from '@/core/entities/NoteEntityService.js';
import { GetterService } from '@/server/api/GetterService.js';
+import { DI } from '@/di-symbols.js';
+import { MiMeta } from '@/models/Meta.js';
import { ApiError } from '../../error.js';
export const meta = {
@@ -46,6 +48,9 @@ export const paramDef = {
@Injectable()
export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
constructor(
+ @Inject(DI.meta)
+ private serverSettings: MiMeta,
+
private noteEntityService: NoteEntityService,
private getterService: GetterService,
) {
@@ -59,6 +64,14 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
throw new ApiError(meta.errors.signinRequired);
}
+ if (this.serverSettings.ugcVisibilityForVisitor === 'none' && me == null) {
+ throw new ApiError(meta.errors.signinRequired);
+ }
+
+ if (this.serverSettings.ugcVisibilityForVisitor === 'local' && note.userHost != null && me == null) {
+ throw new ApiError(meta.errors.signinRequired);
+ }
+
return await this.noteEntityService.pack(note, me, {
detail: true,
});
diff --git a/packages/backend/src/server/api/endpoints/notes/timeline.ts b/packages/backend/src/server/api/endpoints/notes/timeline.ts
index e6d6a1b629..c76cca1518 100644
--- a/packages/backend/src/server/api/endpoints/notes/timeline.ts
+++ b/packages/backend/src/server/api/endpoints/notes/timeline.ts
@@ -199,10 +199,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
}));
this.queryService.generateVisibilityQuery(query, me);
- this.queryService.generateBlockedHostQueryForNote(query);
- this.queryService.generateSuspendedUserQueryForNote(query);
- this.queryService.generateMutedUserQueryForNotes(query, me);
- this.queryService.generateBlockedUserQueryForNotes(query, me);
+ this.queryService.generateBaseNoteFilteringQuery(query, me);
this.queryService.generateMutedUserRenotesQueryForNotes(query, me);
if (ps.includeMyRenotes === false) {
diff --git a/packages/backend/src/server/api/endpoints/notes/user-list-timeline.ts b/packages/backend/src/server/api/endpoints/notes/user-list-timeline.ts
index ec7c4b0f97..614cd9204d 100644
--- a/packages/backend/src/server/api/endpoints/notes/user-list-timeline.ts
+++ b/packages/backend/src/server/api/endpoints/notes/user-list-timeline.ts
@@ -184,10 +184,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
}));
this.queryService.generateVisibilityQuery(query, me);
- this.queryService.generateBlockedHostQueryForNote(query);
- this.queryService.generateSuspendedUserQueryForNote(query);
- this.queryService.generateMutedUserQueryForNotes(query, me);
- this.queryService.generateBlockedUserQueryForNotes(query, me);
+ this.queryService.generateBaseNoteFilteringQuery(query, me);
this.queryService.generateMutedUserRenotesQueryForNotes(query, me);
if (ps.includeMyRenotes === false) {
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/roles/notes.ts b/packages/backend/src/server/api/endpoints/roles/notes.ts
index 16b0783a01..e8a760e9f8 100644
--- a/packages/backend/src/server/api/endpoints/roles/notes.ts
+++ b/packages/backend/src/server/api/endpoints/roles/notes.ts
@@ -102,10 +102,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
.leftJoinAndSelect('renote.user', 'renoteUser');
this.queryService.generateVisibilityQuery(query, me);
- this.queryService.generateBlockedHostQueryForNote(query);
- this.queryService.generateSuspendedUserQueryForNote(query);
- this.queryService.generateMutedUserQueryForNotes(query, me);
- this.queryService.generateBlockedUserQueryForNotes(query, me);
+ this.queryService.generateBaseNoteFilteringQuery(query, me);
const notes = await query.getMany();
notes.sort((a, b) => a.id > b.id ? -1 : 1);
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/notes.ts b/packages/backend/src/server/api/endpoints/users/notes.ts
index 0c64df569d..5832790a61 100644
--- a/packages/backend/src/server/api/endpoints/users/notes.ts
+++ b/packages/backend/src/server/api/endpoints/users/notes.ts
@@ -186,12 +186,10 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
}
this.queryService.generateVisibilityQuery(query, me);
- this.queryService.generateBlockedHostQueryForNote(query, true);
- this.queryService.generateSuspendedUserQueryForNote(query, true);
- if (me) {
- this.queryService.generateMutedUserQueryForNotes(query, me, { id: ps.userId });
- this.queryService.generateBlockedUserQueryForNotes(query, me);
- }
+ this.queryService.generateBaseNoteFilteringQuery(query, me, {
+ excludeAuthor: true,
+ excludeUserFromMute: ps.userId,
+ });
if (ps.withFiles) {
query.andWhere('note.fileIds != \'{}\'');
diff --git a/packages/backend/src/server/api/endpoints/users/recommendation.ts b/packages/backend/src/server/api/endpoints/users/recommendation.ts
index 5b1c6b514b..769a72d7a1 100644
--- a/packages/backend/src/server/api/endpoints/users/recommendation.ts
+++ b/packages/backend/src/server/api/endpoints/users/recommendation.ts
@@ -64,6 +64,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
this.queryService.generateMutedUserQueryForUsers(query, me);
this.queryService.generateBlockQueryForUsers(query, me);
this.queryService.generateBlockedUserQueryForNotes(query, me);
+ this.queryService.generateBlockedUserQueryForNotes(query, me, { noteColumn: 'renote' });
const followingQuery = this.followingsRepository.createQueryBuilder('following')
.select('following.followeeId')
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 062326e28d..d57db42e6d 100644
--- a/packages/backend/src/server/api/endpoints/users/show.ts
+++ b/packages/backend/src/server/api/endpoints/users/show.ts
@@ -5,7 +5,7 @@
import { In, IsNull } from 'typeorm';
import { Inject, Injectable } from '@nestjs/common';
-import type { UsersRepository } from '@/models/_.js';
+import type { MiMeta, UsersRepository } from '@/models/_.js';
import type { MiUser } from '@/models/User.js';
import { Endpoint } from '@/server/api/endpoint-base.js';
import { UserEntityService } from '@/core/entities/UserEntityService.js';
@@ -59,29 +59,53 @@ 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;
@Injectable()
export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
constructor(
+ @Inject(DI.meta)
+ private serverSettings: MiMeta,
+
@Inject(DI.usersRepository)
private usersRepository: UsersRepository,
@@ -92,12 +116,18 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
private apiLoggerService: ApiLoggerService,
) {
super(meta, paramDef, async (ps, me, _1, _2, _3, ip) => {
+ if (this.serverSettings.ugcVisibilityForVisitor === 'none' && me == null) {
+ throw new ApiError(meta.errors.noSuchUser);
+ }
+
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 [];
}
@@ -122,13 +152,17 @@ 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);
+ }
+
user = await this.remoteUserResolveService.resolveUser(ps.username, ps.host).catch(err => {
this.apiLoggerService.logger.warn(`failed to resolve remote user: ${err}`);
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() };
@@ -139,6 +173,10 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
throw new ApiError(meta.errors.noSuchUser);
}
+ if (this.serverSettings.ugcVisibilityForVisitor === 'local' && user.host != null && me == null) {
+ throw new ApiError(meta.errors.noSuchUser);
+ }
+
if (user.host == null) {
if (me == null && ip != null) {
this.perUserPvChart.commitByVisitor(user, ip);