summaryrefslogtreecommitdiff
path: root/packages/backend/src/server/api/endpoints/users
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/users
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/users')
-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
8 files changed, 183 insertions, 79 deletions
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);