Merge branch 'develop' into feature/default-hashtag-timeline
This commit is contained in:
commit
1b66da505e
401 changed files with 11621 additions and 7382 deletions
|
|
@ -40,7 +40,7 @@ export const paramDef = {
|
|||
startsAt: { type: 'integer' },
|
||||
dayOfWeek: { type: 'integer' },
|
||||
},
|
||||
required: ['id', 'memo', 'url', 'imageUrl', 'place', 'priority', 'ratio', 'expiresAt', 'startsAt', 'dayOfWeek'],
|
||||
required: ['id'],
|
||||
} as const;
|
||||
|
||||
@Injectable()
|
||||
|
|
@ -63,8 +63,8 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||
ratio: ps.ratio,
|
||||
memo: ps.memo,
|
||||
imageUrl: ps.imageUrl,
|
||||
expiresAt: new Date(ps.expiresAt),
|
||||
startsAt: new Date(ps.startsAt),
|
||||
expiresAt: ps.expiresAt ? new Date(ps.expiresAt) : undefined,
|
||||
startsAt: ps.startsAt ? new Date(ps.startsAt) : undefined,
|
||||
dayOfWeek: ps.dayOfWeek,
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -61,7 +61,7 @@ export const meta = {
|
|||
name: {
|
||||
type: 'string',
|
||||
optional: false, nullable: false,
|
||||
example: 'lenna.jpg',
|
||||
example: '192.jpg',
|
||||
},
|
||||
type: {
|
||||
type: 'string',
|
||||
|
|
|
|||
|
|
@ -6,7 +6,6 @@
|
|||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||
import type { RolesRepository } from '@/models/_.js';
|
||||
import { GlobalEventService } from '@/core/GlobalEventService.js';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import { ApiError } from '@/server/api/error.js';
|
||||
import { RoleService } from '@/core/RoleService.js';
|
||||
|
|
@ -50,19 +49,6 @@ export const paramDef = {
|
|||
},
|
||||
required: [
|
||||
'roleId',
|
||||
'name',
|
||||
'description',
|
||||
'color',
|
||||
'iconUrl',
|
||||
'target',
|
||||
'condFormula',
|
||||
'isPublic',
|
||||
'isModerator',
|
||||
'isAdministrator',
|
||||
'asBadge',
|
||||
'canEditMembersByModerator',
|
||||
'displayOrder',
|
||||
'policies',
|
||||
],
|
||||
} as const;
|
||||
|
||||
|
|
|
|||
|
|
@ -93,7 +93,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||
const currentAntennasCount = await this.antennasRepository.countBy({
|
||||
userId: me.id,
|
||||
});
|
||||
if (currentAntennasCount > (await this.roleService.getUserPolicies(me.id)).antennaLimit) {
|
||||
if (currentAntennasCount >= (await this.roleService.getUserPolicies(me.id)).antennaLimit) {
|
||||
throw new ApiError(meta.errors.tooManyAntennas);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||
import { ClipEntityService } from '@/core/entities/ClipEntityService.js';
|
||||
import { ClipService } from '@/core/ClipService.js';
|
||||
|
|
@ -41,7 +41,7 @@ export const paramDef = {
|
|||
isPublic: { type: 'boolean' },
|
||||
description: { type: 'string', nullable: true, minLength: 1, maxLength: 2048 },
|
||||
},
|
||||
required: ['clipId', 'name'],
|
||||
required: ['clipId'],
|
||||
} as const;
|
||||
|
||||
@Injectable()
|
||||
|
|
|
|||
|
|
@ -95,15 +95,14 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||
|
||||
// Check if the circular reference will occur
|
||||
const checkCircle = async (folderId: string): Promise<boolean> => {
|
||||
// Fetch folder
|
||||
const folder2 = await this.driveFoldersRepository.findOneBy({
|
||||
const folder2 = await this.driveFoldersRepository.findOneByOrFail({
|
||||
id: folderId,
|
||||
});
|
||||
|
||||
if (folder2!.id === folder!.id) {
|
||||
if (folder2.id === folder.id) {
|
||||
return true;
|
||||
} else if (folder2!.parentId) {
|
||||
return await checkCircle(folder2!.parentId);
|
||||
} else if (folder2.parentId) {
|
||||
return await checkCircle(folder2.parentId);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,7 +12,6 @@ import type { MiDriveFile } from '@/models/DriveFile.js';
|
|||
import { IdService } from '@/core/IdService.js';
|
||||
import { GalleryPostEntityService } from '@/core/entities/GalleryPostEntityService.js';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import { isNotNull } from '@/misc/is-not-null.js';
|
||||
|
||||
export const meta = {
|
||||
tags: ['gallery'],
|
||||
|
|
@ -70,7 +69,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||
id: fileId,
|
||||
userId: me.id,
|
||||
}),
|
||||
))).filter(isNotNull);
|
||||
))).filter(x => x != null);
|
||||
|
||||
if (files.length === 0) {
|
||||
throw new Error();
|
||||
|
|
|
|||
|
|
@ -10,7 +10,6 @@ import type { DriveFilesRepository, GalleryPostsRepository } from '@/models/_.js
|
|||
import type { MiDriveFile } from '@/models/DriveFile.js';
|
||||
import { GalleryPostEntityService } from '@/core/entities/GalleryPostEntityService.js';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import { isNotNull } from '@/misc/is-not-null.js';
|
||||
|
||||
export const meta = {
|
||||
tags: ['gallery'],
|
||||
|
|
@ -48,7 +47,7 @@ export const paramDef = {
|
|||
} },
|
||||
isSensitive: { type: 'boolean', default: false },
|
||||
},
|
||||
required: ['postId', 'title', 'fileIds'],
|
||||
required: ['postId'],
|
||||
} as const;
|
||||
|
||||
@Injectable()
|
||||
|
|
@ -63,15 +62,19 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||
private galleryPostEntityService: GalleryPostEntityService,
|
||||
) {
|
||||
super(meta, paramDef, async (ps, me) => {
|
||||
const files = (await Promise.all(ps.fileIds.map(fileId =>
|
||||
this.driveFilesRepository.findOneBy({
|
||||
id: fileId,
|
||||
userId: me.id,
|
||||
}),
|
||||
))).filter(isNotNull);
|
||||
let files: Array<MiDriveFile> | undefined;
|
||||
|
||||
if (files.length === 0) {
|
||||
throw new Error();
|
||||
if (ps.fileIds) {
|
||||
files = (await Promise.all(ps.fileIds.map(fileId =>
|
||||
this.driveFilesRepository.findOneBy({
|
||||
id: fileId,
|
||||
userId: me.id,
|
||||
}),
|
||||
))).filter(x => x != null);
|
||||
|
||||
if (files.length === 0) {
|
||||
throw new Error();
|
||||
}
|
||||
}
|
||||
|
||||
await this.galleryPostsRepository.update({
|
||||
|
|
@ -82,7 +85,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||
title: ps.title,
|
||||
description: ps.description,
|
||||
isSensitive: ps.isSensitive,
|
||||
fileIds: files.map(file => file.id),
|
||||
fileIds: files ? files.map(file => file.id) : undefined,
|
||||
});
|
||||
|
||||
const post = await this.galleryPostsRepository.findOneByOrFail({ id: ps.postId });
|
||||
|
|
|
|||
|
|
@ -78,7 +78,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
|
|||
if (file.size === 0) throw new ApiError(meta.errors.emptyFile);
|
||||
const antennas: (_Antenna & { userListAccts: string[] | null })[] = JSON.parse(await this.downloadService.downloadTextFile(file.url));
|
||||
const currentAntennasCount = await this.antennasRepository.countBy({ userId: me.id });
|
||||
if (currentAntennasCount + antennas.length > (await this.roleService.getUserPolicies(me.id)).antennaLimit) {
|
||||
if (currentAntennasCount + antennas.length >= (await this.roleService.getUserPolicies(me.id)).antennaLimit) {
|
||||
throw new ApiError(meta.errors.tooManyAntennas);
|
||||
}
|
||||
this.queueService.createImportAntennasJob(me, antennas);
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ import { UserFollowingService } from '@/core/UserFollowingService.js';
|
|||
import { AccountUpdateService } from '@/core/AccountUpdateService.js';
|
||||
import { HashtagService } from '@/core/HashtagService.js';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import { RoleService } from '@/core/RoleService.js';
|
||||
import { RolePolicies, RoleService } from '@/core/RoleService.js';
|
||||
import { CacheService } from '@/core/CacheService.js';
|
||||
import { RemoteUserResolveService } from '@/core/RemoteUserResolveService.js';
|
||||
import { DriveFileEntityService } from '@/core/entities/DriveFileEntityService.js';
|
||||
|
|
@ -256,8 +256,16 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||
const profileUpdates = {} as Partial<MiUserProfile>;
|
||||
|
||||
const profile = await this.userProfilesRepository.findOneByOrFail({ userId: user.id });
|
||||
let policies: RolePolicies | null = null;
|
||||
|
||||
if (ps.name !== undefined) updates.name = ps.name;
|
||||
if (ps.name !== undefined) {
|
||||
if (ps.name === null) {
|
||||
updates.name = null;
|
||||
} else {
|
||||
const trimmedName = ps.name.trim();
|
||||
updates.name = trimmedName === '' ? null : trimmedName;
|
||||
}
|
||||
}
|
||||
if (ps.description !== undefined) profileUpdates.description = ps.description;
|
||||
if (ps.lang !== undefined) profileUpdates.lang = ps.lang;
|
||||
if (ps.location !== undefined) profileUpdates.location = ps.location;
|
||||
|
|
@ -289,14 +297,16 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||
}
|
||||
|
||||
if (ps.mutedWords !== undefined) {
|
||||
checkMuteWordCount(ps.mutedWords, (await this.roleService.getUserPolicies(user.id)).wordMuteLimit);
|
||||
policies ??= await this.roleService.getUserPolicies(user.id);
|
||||
checkMuteWordCount(ps.mutedWords, policies.wordMuteLimit);
|
||||
validateMuteWordRegex(ps.mutedWords);
|
||||
|
||||
profileUpdates.mutedWords = ps.mutedWords;
|
||||
profileUpdates.enableWordMute = ps.mutedWords.length > 0;
|
||||
}
|
||||
if (ps.hardMutedWords !== undefined) {
|
||||
checkMuteWordCount(ps.hardMutedWords, (await this.roleService.getUserPolicies(user.id)).wordMuteLimit);
|
||||
policies ??= await this.roleService.getUserPolicies(user.id);
|
||||
checkMuteWordCount(ps.hardMutedWords, policies.wordMuteLimit);
|
||||
validateMuteWordRegex(ps.hardMutedWords);
|
||||
profileUpdates.hardMutedWords = ps.hardMutedWords;
|
||||
}
|
||||
|
|
@ -315,13 +325,17 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||
if (typeof ps.injectFeaturedNote === 'boolean') profileUpdates.injectFeaturedNote = ps.injectFeaturedNote;
|
||||
if (typeof ps.receiveAnnouncementEmail === 'boolean') profileUpdates.receiveAnnouncementEmail = ps.receiveAnnouncementEmail;
|
||||
if (typeof ps.alwaysMarkNsfw === 'boolean') {
|
||||
if ((await roleService.getUserPolicies(user.id)).alwaysMarkNsfw) throw new ApiError(meta.errors.restrictedByRole);
|
||||
policies ??= await this.roleService.getUserPolicies(user.id);
|
||||
if (policies.alwaysMarkNsfw) throw new ApiError(meta.errors.restrictedByRole);
|
||||
profileUpdates.alwaysMarkNsfw = ps.alwaysMarkNsfw;
|
||||
}
|
||||
if (typeof ps.autoSensitive === 'boolean') profileUpdates.autoSensitive = ps.autoSensitive;
|
||||
if (ps.emailNotificationTypes !== undefined) profileUpdates.emailNotificationTypes = ps.emailNotificationTypes;
|
||||
|
||||
if (ps.avatarId) {
|
||||
policies ??= await this.roleService.getUserPolicies(user.id);
|
||||
if (!policies.canUpdateBioMedia) throw new ApiError(meta.errors.restrictedByRole);
|
||||
|
||||
const avatar = await this.driveFilesRepository.findOneBy({ id: ps.avatarId });
|
||||
|
||||
if (avatar == null || avatar.userId !== user.id) throw new ApiError(meta.errors.noSuchAvatar);
|
||||
|
|
@ -337,6 +351,9 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||
}
|
||||
|
||||
if (ps.bannerId) {
|
||||
policies ??= await this.roleService.getUserPolicies(user.id);
|
||||
if (!policies.canUpdateBioMedia) throw new ApiError(meta.errors.restrictedByRole);
|
||||
|
||||
const banner = await this.driveFilesRepository.findOneBy({ id: ps.bannerId });
|
||||
|
||||
if (banner == null || banner.userId !== user.id) throw new ApiError(meta.errors.noSuchBanner);
|
||||
|
|
@ -352,14 +369,15 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||
}
|
||||
|
||||
if (ps.avatarDecorations) {
|
||||
policies ??= await this.roleService.getUserPolicies(user.id);
|
||||
const decorations = await this.avatarDecorationService.getAll(true);
|
||||
const [myRoles, myPolicies] = await Promise.all([this.roleService.getUserRoles(user.id), this.roleService.getUserPolicies(user.id)]);
|
||||
const myRoles = await this.roleService.getUserRoles(user.id);
|
||||
const allRoles = await this.roleService.getRoles();
|
||||
const decorationIds = decorations
|
||||
.filter(d => d.roleIdsThatCanBeUsedThisDecoration.filter(roleId => allRoles.some(r => r.id === roleId)).length === 0 || myRoles.some(r => d.roleIdsThatCanBeUsedThisDecoration.includes(r.id)))
|
||||
.map(d => d.id);
|
||||
|
||||
if (ps.avatarDecorations.length > myPolicies.avatarDecorationLimit) throw new ApiError(meta.errors.restrictedByRole);
|
||||
if (ps.avatarDecorations.length > policies.avatarDecorationLimit) throw new ApiError(meta.errors.restrictedByRole);
|
||||
|
||||
updates.avatarDecorations = ps.avatarDecorations.filter(d => decorationIds.includes(d.id)).map(d => ({
|
||||
id: d.id,
|
||||
|
|
|
|||
|
|
@ -85,7 +85,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||
const currentWebhooksCount = await this.webhooksRepository.countBy({
|
||||
userId: me.id,
|
||||
});
|
||||
if (currentWebhooksCount > (await this.roleService.getUserPolicies(me.id)).webhookLimit) {
|
||||
if (currentWebhooksCount >= (await this.roleService.getUserPolicies(me.id)).webhookLimit) {
|
||||
throw new ApiError(meta.errors.tooManyWebhooks);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -34,13 +34,13 @@ export const paramDef = {
|
|||
webhookId: { type: 'string', format: 'misskey:id' },
|
||||
name: { type: 'string', minLength: 1, maxLength: 100 },
|
||||
url: { type: 'string', minLength: 1, maxLength: 1024 },
|
||||
secret: { type: 'string', maxLength: 1024, default: '' },
|
||||
secret: { type: 'string', nullable: true, maxLength: 1024 },
|
||||
on: { type: 'array', items: {
|
||||
type: 'string', enum: webhookEventTypes,
|
||||
} },
|
||||
active: { type: 'boolean' },
|
||||
},
|
||||
required: ['webhookId', 'name', 'url', 'on', 'active'],
|
||||
required: ['webhookId'],
|
||||
} as const;
|
||||
|
||||
// TODO: ロジックをサービスに切り出す
|
||||
|
|
@ -66,7 +66,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||
await this.webhooksRepository.update(webhook.id, {
|
||||
name: ps.name,
|
||||
url: ps.url,
|
||||
secret: ps.secret,
|
||||
secret: ps.secret === null ? '' : ps.secret,
|
||||
on: ps.on,
|
||||
active: ps.active,
|
||||
});
|
||||
|
|
|
|||
|
|
@ -141,6 +141,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||
timelineConfig = [
|
||||
`homeTimeline:${me.id}`,
|
||||
'localTimeline',
|
||||
`localTimelineWithReplyTo:${me.id}`,
|
||||
];
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -36,6 +36,12 @@ export const meta = {
|
|||
code: 'YOU_HAVE_BEEN_BLOCKED',
|
||||
id: '20ef5475-9f38-4e4c-bd33-de6d979498ec',
|
||||
},
|
||||
|
||||
cannotReactToRenote: {
|
||||
message: 'You cannot react to Renote.',
|
||||
code: 'CANNOT_REACT_TO_RENOTE',
|
||||
id: 'eaccdc08-ddef-43fe-908f-d108faad57f5',
|
||||
},
|
||||
},
|
||||
} as const;
|
||||
|
||||
|
|
@ -62,6 +68,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||
await this.reactionService.create(me, note, ps.reaction).catch(err => {
|
||||
if (err.id === '51c42bb4-931a-456b-bff7-e5a8a70dd298') throw new ApiError(meta.errors.alreadyReacted);
|
||||
if (err.id === 'e70412a4-7197-4726-8e74-f3e0deb92aa7') throw new ApiError(meta.errors.youHaveBeenBlocked);
|
||||
if (err.id === '12c35529-3c79-4327-b1cc-e2cf63a71925') throw new ApiError(meta.errors.cannotReactToRenote);
|
||||
throw err;
|
||||
});
|
||||
return;
|
||||
|
|
|
|||
|
|
@ -70,7 +70,7 @@ export const paramDef = {
|
|||
alignCenter: { type: 'boolean' },
|
||||
hideTitleWhenPinned: { type: 'boolean' },
|
||||
},
|
||||
required: ['pageId', 'title', 'name', 'content', 'variables', 'script'],
|
||||
required: ['pageId'],
|
||||
} as const;
|
||||
|
||||
@Injectable()
|
||||
|
|
@ -91,9 +91,8 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||
throw new ApiError(meta.errors.accessDenied);
|
||||
}
|
||||
|
||||
let eyeCatchingImage = null;
|
||||
if (ps.eyeCatchingImageId != null) {
|
||||
eyeCatchingImage = await this.driveFilesRepository.findOneBy({
|
||||
const eyeCatchingImage = await this.driveFilesRepository.findOneBy({
|
||||
id: ps.eyeCatchingImageId,
|
||||
userId: me.id,
|
||||
});
|
||||
|
|
@ -116,23 +115,15 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||
await this.pagesRepository.update(page.id, {
|
||||
updatedAt: new Date(),
|
||||
title: ps.title,
|
||||
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
|
||||
name: ps.name === undefined ? page.name : ps.name,
|
||||
name: ps.name,
|
||||
summary: ps.summary === undefined ? page.summary : ps.summary,
|
||||
content: ps.content,
|
||||
variables: ps.variables,
|
||||
script: ps.script,
|
||||
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
|
||||
alignCenter: ps.alignCenter === undefined ? page.alignCenter : ps.alignCenter,
|
||||
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
|
||||
hideTitleWhenPinned: ps.hideTitleWhenPinned === undefined ? page.hideTitleWhenPinned : ps.hideTitleWhenPinned,
|
||||
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
|
||||
font: ps.font === undefined ? page.font : ps.font,
|
||||
eyeCatchingImageId: ps.eyeCatchingImageId === null
|
||||
? null
|
||||
: ps.eyeCatchingImageId === undefined
|
||||
? page.eyeCatchingImageId
|
||||
: eyeCatchingImage!.id,
|
||||
alignCenter: ps.alignCenter,
|
||||
hideTitleWhenPinned: ps.hideTitleWhenPinned,
|
||||
font: ps.font,
|
||||
eyeCatchingImageId: ps.eyeCatchingImageId,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,7 +12,6 @@ import { Endpoint } from '@/server/api/endpoint-base.js';
|
|||
import { MetaService } from '@/core/MetaService.js';
|
||||
import { UserEntityService } from '@/core/entities/UserEntityService.js';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import { isNotNull } from '@/misc/is-not-null.js';
|
||||
|
||||
export const meta = {
|
||||
tags: ['users'],
|
||||
|
|
@ -53,7 +52,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||
host: acct.host ?? IsNull(),
|
||||
})));
|
||||
|
||||
return await this.userEntityService.packMany(users.filter(isNotNull), me, { schema: 'UserDetailed' });
|
||||
return await this.userEntityService.packMany(users.filter(x => x != null), me, { schema: 'UserDetailed' });
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,12 +6,11 @@
|
|||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import ms from 'ms';
|
||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||
import { IdService } from '@/core/IdService.js';
|
||||
import type { RenoteMutingsRepository } from '@/models/_.js';
|
||||
import type { MiRenoteMuting } from '@/models/RenoteMuting.js';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import { GetterService } from '@/server/api/GetterService.js';
|
||||
import { ApiError } from '../../error.js';
|
||||
import { UserRenoteMutingService } from "@/core/UserRenoteMutingService.js";
|
||||
import type { RenoteMutingsRepository } from '@/models/_.js';
|
||||
|
||||
export const meta = {
|
||||
tags: ['account'],
|
||||
|
|
@ -62,7 +61,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||
private renoteMutingsRepository: RenoteMutingsRepository,
|
||||
|
||||
private getterService: GetterService,
|
||||
private idService: IdService,
|
||||
private userRenoteMutingService: UserRenoteMutingService,
|
||||
) {
|
||||
super(meta, paramDef, async (ps, me) => {
|
||||
const muter = me;
|
||||
|
|
@ -79,21 +78,19 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||
});
|
||||
|
||||
// Check if already muting
|
||||
const exist = await this.renoteMutingsRepository.findOneBy({
|
||||
muterId: muter.id,
|
||||
muteeId: mutee.id,
|
||||
const exist = await this.renoteMutingsRepository.exists({
|
||||
where: {
|
||||
muterId: muter.id,
|
||||
muteeId: mutee.id,
|
||||
},
|
||||
});
|
||||
|
||||
if (exist != null) {
|
||||
if (exist === true) {
|
||||
throw new ApiError(meta.errors.alreadyMuting);
|
||||
}
|
||||
|
||||
// Create mute
|
||||
await this.renoteMutingsRepository.insert({
|
||||
id: this.idService.gen(),
|
||||
muterId: muter.id,
|
||||
muteeId: mutee.id,
|
||||
} as MiRenoteMuting);
|
||||
await this.userRenoteMutingService.mute(muter, mutee);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,10 +5,11 @@
|
|||
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||
import type { RenoteMutingsRepository } from '@/models/_.js';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import { GetterService } from '@/server/api/GetterService.js';
|
||||
import { ApiError } from '../../error.js';
|
||||
import { UserRenoteMutingService } from "@/core/UserRenoteMutingService.js";
|
||||
import type { RenoteMutingsRepository } from '@/models/_.js';
|
||||
|
||||
export const meta = {
|
||||
tags: ['account'],
|
||||
|
|
@ -53,6 +54,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||
private renoteMutingsRepository: RenoteMutingsRepository,
|
||||
|
||||
private getterService: GetterService,
|
||||
private userRenoteMutingService: UserRenoteMutingService,
|
||||
) {
|
||||
super(meta, paramDef, async (ps, me) => {
|
||||
const muter = me;
|
||||
|
|
@ -79,9 +81,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||
}
|
||||
|
||||
// Delete mute
|
||||
await this.renoteMutingsRepository.delete({
|
||||
id: exist.id,
|
||||
});
|
||||
await this.userRenoteMutingService.unmute([exist]);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -100,7 +100,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||
const currentCount = await this.userListsRepository.countBy({
|
||||
userId: me.id,
|
||||
});
|
||||
if (currentCount > (await this.roleService.getUserPolicies(me.id)).userListLimit) {
|
||||
if (currentCount >= (await this.roleService.getUserPolicies(me.id)).userListLimit) {
|
||||
throw new ApiError(meta.errors.tooManyUserLists);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -61,7 +61,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||
const currentCount = await this.userListsRepository.countBy({
|
||||
userId: me.id,
|
||||
});
|
||||
if (currentCount > (await this.roleService.getUserPolicies(me.id)).userListLimit) {
|
||||
if (currentCount >= (await this.roleService.getUserPolicies(me.id)).userListLimit) {
|
||||
throw new ApiError(meta.errors.tooManyUserLists);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ import { DI } from '@/di-symbols.js';
|
|||
import { CacheService } from '@/core/CacheService.js';
|
||||
import { UserEntityService } from '@/core/entities/UserEntityService.js';
|
||||
import { RoleService } from '@/core/RoleService.js';
|
||||
import { isUserRelated } from '@/misc/is-user-related.js';
|
||||
import { ApiError } from '../../error.js';
|
||||
|
||||
export const meta = {
|
||||
|
|
@ -74,6 +75,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||
private roleService: RoleService,
|
||||
) {
|
||||
super(meta, paramDef, async (ps, me) => {
|
||||
const userIdsWhoBlockingMe = me ? await this.cacheService.userBlockedCache.fetch(me.id) : new Set<string>();
|
||||
const iAmModerator = me ? await this.roleService.isModerator(me) : false; // Moderators can see reactions of all users
|
||||
if (!iAmModerator) {
|
||||
const user = await this.cacheService.findUserById(ps.userId);
|
||||
|
|
@ -85,8 +87,15 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||
if ((me == null || me.id !== ps.userId) && !profile.publicReactions) {
|
||||
throw new ApiError(meta.errors.reactionsNotPublic);
|
||||
}
|
||||
|
||||
// early return if me is blocked by requesting user
|
||||
if (userIdsWhoBlockingMe.has(ps.userId)) {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
const userIdsWhoMeMuting = me ? await this.cacheService.userMutingsCache.fetch(me.id) : new Set<string>();
|
||||
|
||||
const query = this.queryService.makePaginationQuery(this.noteReactionsRepository.createQueryBuilder('reaction'),
|
||||
ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate)
|
||||
.andWhere('reaction.userId = :userId', { userId: ps.userId })
|
||||
|
|
@ -94,9 +103,15 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||
|
||||
this.queryService.generateVisibilityQuery(query, me);
|
||||
|
||||
const reactions = await query
|
||||
const reactions = (await query
|
||||
.limit(ps.limit)
|
||||
.getMany();
|
||||
.getMany()).filter(reaction => {
|
||||
if (reaction.note?.userId === ps.userId) return true; // we can see reactions to note of requesting user
|
||||
if (me && isUserRelated(reaction.note, userIdsWhoBlockingMe)) return false;
|
||||
if (me && isUserRelated(reaction.note, userIdsWhoMeMuting)) return false;
|
||||
|
||||
return true;
|
||||
});
|
||||
|
||||
return await this.noteReactionEntityService.packMany(reactions, me, { withNote: true });
|
||||
});
|
||||
|
|
|
|||
|
|
@ -3,15 +3,9 @@
|
|||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import { Brackets } from 'typeorm';
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import type { UsersRepository, FollowingsRepository } from '@/models/_.js';
|
||||
import type { Config } from '@/config.js';
|
||||
import type { MiUser } from '@/models/User.js';
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||
import { UserEntityService } from '@/core/entities/UserEntityService.js';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import { sqlLikeEscape } from '@/misc/sql-like-escape.js';
|
||||
import { UserSearchService } from '@/core/UserSearchService.js';
|
||||
|
||||
export const meta = {
|
||||
tags: ['users'],
|
||||
|
|
@ -49,89 +43,16 @@ export const paramDef = {
|
|||
@Injectable()
|
||||
export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
|
||||
constructor(
|
||||
@Inject(DI.config)
|
||||
private config: Config,
|
||||
|
||||
@Inject(DI.usersRepository)
|
||||
private usersRepository: UsersRepository,
|
||||
|
||||
@Inject(DI.followingsRepository)
|
||||
private followingsRepository: FollowingsRepository,
|
||||
|
||||
private userEntityService: UserEntityService,
|
||||
private userSearchService: UserSearchService,
|
||||
) {
|
||||
super(meta, paramDef, async (ps, me) => {
|
||||
const setUsernameAndHostQuery = (query = this.usersRepository.createQueryBuilder('user')) => {
|
||||
if (ps.username) {
|
||||
query.andWhere('user.usernameLower LIKE :username', { username: sqlLikeEscape(ps.username.toLowerCase()) + '%' });
|
||||
}
|
||||
|
||||
if (ps.host) {
|
||||
if (ps.host === this.config.hostname || ps.host === '.') {
|
||||
query.andWhere('user.host IS NULL');
|
||||
} else {
|
||||
query.andWhere('user.host LIKE :host', {
|
||||
host: sqlLikeEscape(ps.host.toLowerCase()) + '%',
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return query;
|
||||
};
|
||||
|
||||
const activeThreshold = new Date(Date.now() - (1000 * 60 * 60 * 24 * 30)); // 30日
|
||||
|
||||
let users: MiUser[] = [];
|
||||
|
||||
if (me) {
|
||||
const followingQuery = this.followingsRepository.createQueryBuilder('following')
|
||||
.select('following.followeeId')
|
||||
.where('following.followerId = :followerId', { followerId: me.id });
|
||||
|
||||
const query = setUsernameAndHostQuery()
|
||||
.andWhere(`user.id IN (${ followingQuery.getQuery() })`)
|
||||
.andWhere('user.id != :meId', { meId: me.id })
|
||||
.andWhere('user.isSuspended = FALSE')
|
||||
.andWhere(new Brackets(qb => {
|
||||
qb
|
||||
.where('user.updatedAt IS NULL')
|
||||
.orWhere('user.updatedAt > :activeThreshold', { activeThreshold: activeThreshold });
|
||||
}));
|
||||
|
||||
query.setParameters(followingQuery.getParameters());
|
||||
|
||||
users = await query
|
||||
.orderBy('user.usernameLower', 'ASC')
|
||||
.limit(ps.limit)
|
||||
.getMany();
|
||||
|
||||
if (users.length < ps.limit) {
|
||||
const otherQuery = setUsernameAndHostQuery()
|
||||
.andWhere(`user.id NOT IN (${ followingQuery.getQuery() })`)
|
||||
.andWhere('user.isSuspended = FALSE')
|
||||
.andWhere('user.updatedAt IS NOT NULL');
|
||||
|
||||
otherQuery.setParameters(followingQuery.getParameters());
|
||||
|
||||
const otherUsers = await otherQuery
|
||||
.orderBy('user.updatedAt', 'DESC')
|
||||
.limit(ps.limit - users.length)
|
||||
.getMany();
|
||||
|
||||
users = users.concat(otherUsers);
|
||||
}
|
||||
} else {
|
||||
const query = setUsernameAndHostQuery()
|
||||
.andWhere('user.isSuspended = FALSE')
|
||||
.andWhere('user.updatedAt IS NOT NULL');
|
||||
|
||||
users = await query
|
||||
.orderBy('user.updatedAt', 'DESC')
|
||||
.limit(ps.limit - users.length)
|
||||
.getMany();
|
||||
}
|
||||
|
||||
return await this.userEntityService.packMany(users, me, { schema: ps.detail ? 'UserDetailed' : 'UserLite' });
|
||||
super(meta, paramDef, (ps, me) => {
|
||||
return this.userSearchService.search({
|
||||
username: ps.username,
|
||||
host: ps.host,
|
||||
}, {
|
||||
limit: ps.limit,
|
||||
detail: ps.detail,
|
||||
}, me);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue