Merge upstream

This commit is contained in:
무라쿠모 2024-08-11 13:06:02 +09:00
commit ad42eccfa4
No known key found for this signature in database
GPG key ID: 139D6573F92DA9F7
24 changed files with 529 additions and 20 deletions

View file

@ -32,6 +32,7 @@ import * as ep___admin_avatarDecorations_update from './endpoints/admin/avatar-d
import * as ep___admin_unsetUserAvatar from './endpoints/admin/unset-user-avatar.js';
import * as ep___admin_unsetUserBanner from './endpoints/admin/unset-user-banner.js';
import * as ep___admin_disposeCache from './endpoints/admin/dispose-cache.js';
import * as ep___admin_unsetUserMutualLink from './endpoints/admin/unset-user-mutual-link.js';
import * as ep___admin_drive_cleanRemoteFiles from './endpoints/admin/drive/clean-remote-files.js';
import * as ep___admin_drive_cleanup from './endpoints/admin/drive/cleanup.js';
import * as ep___admin_drive_deleteAllFilesOfAUser from './endpoints/admin/drive/delete-all-files-of-a-user.js';
@ -423,6 +424,7 @@ const $admin_avatarDecorations_update: Provider = { provide: 'ep:admin/avatar-de
const $admin_unsetUserAvatar: Provider = { provide: 'ep:admin/unset-user-avatar', useClass: ep___admin_unsetUserAvatar.default };
const $admin_unsetUserBanner: Provider = { provide: 'ep:admin/unset-user-banner', useClass: ep___admin_unsetUserBanner.default };
const $admin_disposeCache: Provider = { provide: 'ep:admin/dispose-cache', useClass: ep___admin_disposeCache.default };
const $admin_unsetUserMutualLink: Provider = { provide: 'ep:admin/unset-user-mutual-link', useClass: ep___admin_unsetUserMutualLink.default };
const $admin_drive_cleanRemoteFiles: Provider = { provide: 'ep:admin/drive/clean-remote-files', useClass: ep___admin_drive_cleanRemoteFiles.default };
const $admin_drive_cleanup: Provider = { provide: 'ep:admin/drive/cleanup', useClass: ep___admin_drive_cleanup.default };
const $admin_drive_deleteAllFilesOfAUser: Provider = { provide: 'ep:admin/drive/delete-all-files-of-a-user', useClass: ep___admin_drive_deleteAllFilesOfAUser.default };
@ -818,6 +820,7 @@ const $reversi_verify: Provider = { provide: 'ep:reversi/verify', useClass: ep__
$admin_unsetUserAvatar,
$admin_unsetUserBanner,
$admin_disposeCache,
$admin_unsetUserMutualLink,
$admin_drive_cleanRemoteFiles,
$admin_drive_cleanup,
$admin_drive_deleteAllFilesOfAUser,
@ -1207,6 +1210,7 @@ const $reversi_verify: Provider = { provide: 'ep:reversi/verify', useClass: ep__
$admin_unsetUserAvatar,
$admin_unsetUserBanner,
$admin_disposeCache,
$admin_unsetUserMutualLink,
$admin_drive_cleanRemoteFiles,
$admin_drive_cleanup,
$admin_drive_deleteAllFilesOfAUser,

View file

@ -18,6 +18,17 @@ const ajv = new Ajv({
});
ajv.addFormat('misskey:id', /^[a-zA-Z0-9]+$/);
ajv.addFormat('url', {
type: 'string',
validate: (url: string) => {
try {
new URL(url);
return true;
} catch (e) {
return false;
}
},
});
export type Response = Record<string, any> | void;

View file

@ -32,6 +32,7 @@ import * as ep___admin_avatarDecorations_update from './endpoints/admin/avatar-d
import * as ep___admin_unsetUserAvatar from './endpoints/admin/unset-user-avatar.js';
import * as ep___admin_unsetUserBanner from './endpoints/admin/unset-user-banner.js';
import * as ep___admin_disposeCache from './endpoints/admin/dispose-cache.js';
import * as ep___admin_unsetUserMutualLink from './endpoints/admin/unset-user-mutual-link.js';
import * as ep___admin_drive_cleanRemoteFiles from './endpoints/admin/drive/clean-remote-files.js';
import * as ep___admin_drive_cleanup from './endpoints/admin/drive/cleanup.js';
import * as ep___admin_drive_deleteAllFilesOfAUser from './endpoints/admin/drive/delete-all-files-of-a-user.js';
@ -421,6 +422,7 @@ const eps = [
['admin/unset-user-avatar', ep___admin_unsetUserAvatar],
['admin/unset-user-banner', ep___admin_unsetUserBanner],
['admin/dispose-cache', ep___admin_disposeCache],
['admin/unset-user-mutual-link', ep___admin_unsetUserMutualLink],
['admin/drive/clean-remote-files', ep___admin_drive_cleanRemoteFiles],
['admin/drive/cleanup', ep___admin_drive_cleanup],
['admin/drive/delete-all-files-of-a-user', ep___admin_drive_deleteAllFilesOfAUser],

View file

@ -0,0 +1,56 @@
import { Inject, Injectable } from '@nestjs/common';
import type {
UsersRepository,
UserProfilesRepository,
} from '@/models/_.js';
import { Endpoint } from '@/server/api/endpoint-base.js';
import { DI } from '@/di-symbols.js';
import { ModerationLogService } from '@/core/ModerationLogService.js';
export const meta = {
tags: ['admin'],
requireCredential: true,
requireModerator: true,
kind: 'write:admin:unset-user-mutual-link',
} as const;
export const paramDef = {
type: 'object',
properties: {
userId: { type: 'string', format: 'misskey:id' },
},
required: ['userId'],
} as const;
// eslint-disable-next-line import/no-default-export
@Injectable()
export default class extends Endpoint<typeof meta, typeof paramDef> {
constructor(
@Inject(DI.usersRepository)
private usersRepository: UsersRepository,
@Inject(DI.userProfilesRepository)
private userProfilesRepository: UserProfilesRepository,
private moderationLogService: ModerationLogService,
) {
super(meta, paramDef, async (ps, me) => {
const user = await this.usersRepository.findOneBy({ id: ps.userId });
const userProfile = await this.userProfilesRepository.findOneBy({ userId: ps.userId });
if (user == null || userProfile == null) {
throw new Error('user not found');
}
await this.userProfilesRepository.update(user.id, {
mutualLinkSections: [],
});
this.moderationLogService.log(me, 'unsetUserMutualLink', {
userId: user.id,
userUsername: user.username,
userMutualLinkSections: userProfile.mutualLinkSections,
});
});
}
}

View file

@ -56,6 +56,12 @@ export const meta = {
id: '539f3a45-f215-4f81-a9a8-31293640207f',
},
noSuchFile: {
message: 'No such file.',
code: 'NO_SUCH_FILE',
id: 'e0f0d3c7-e704-4314-a0b5-04286d69a65c',
},
noSuchBanner: {
message: 'No such banner file.',
code: 'NO_SUCH_BANNER',
@ -68,6 +74,12 @@ export const meta = {
id: 'f419f9f8-2f4d-46b1-9fb4-49d3a2fd7191',
},
fileNotAnImage: {
message: 'The specified file is not an image.',
code: 'FILE_NOT_AN_IMAGE',
id: '2851568b-5ad1-4031-bf0d-5320afebf3a9',
},
bannerNotAnImage: {
message: 'The file specified as a banner is not an image.',
code: 'BANNER_NOT_AN_IMAGE',
@ -178,8 +190,8 @@ export const paramDef = {
mutedWords: { type: 'array', items: {
oneOf: [
{ type: 'array', items: { type: 'string' } },
{ type: 'string' }
]
{ type: 'string' },
],
} },
mutedInstances: { type: 'array', items: {
type: 'string',
@ -213,6 +225,28 @@ export const paramDef = {
uniqueItems: true,
items: { type: 'string' },
},
mutualLinkSections: {
type: 'array',
items: {
type: 'object',
properties: {
name: { type: 'string', nullable: true },
mutualLinks: {
type: 'array',
items: {
type: 'object',
properties: {
url: { type: 'string', format: 'url' },
fileId: { type: 'string', format: 'misskey:id' },
description: { type: 'string', nullable: true },
},
required: ['url', 'fileId'],
},
},
},
required: ['mutualLinks'],
},
},
},
} as const;
@ -322,6 +356,43 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
updates.avatarBlurhash = null;
}
if (ps.mutualLinkSections) {
if (ps.mutualLinkSections.length > policy.mutualLinkSectionLimit) {
throw new ApiError(meta.errors.restrictedByRole);
}
const mutualLinkSections = ps.mutualLinkSections.map(async (section) => {
if (section.mutualLinks.length > policy.mutualLinkLimit) {
throw new ApiError(meta.errors.restrictedByRole);
}
const mutualLinks = await Promise.all(section.mutualLinks.map(async (mutualLink) => {
const file = await this.driveFilesRepository.findOneBy({ id: mutualLink.fileId });
if (!file) {
throw new ApiError(meta.errors.noSuchFile);
}
if (!file.type.startsWith('image/')) {
throw new ApiError(meta.errors.fileNotAnImage);
}
return {
url: mutualLink.url,
fileId: file.id,
imgSrc: this.driveFileEntityService.getPublicUrl(file),
description: mutualLink.description ?? null,
};
}));
return {
name: section.name ?? null,
mutualLinks,
};
});
profileUpdates.mutualLinkSections = await Promise.all(mutualLinkSections);
}
if (ps.bannerId) {
if (!policy.canUpdateBanner) throw new ApiError(meta.errors.restrictedByRole);
const banner = await this.driveFilesRepository.findOneBy({ id: ps.bannerId });