enhance(profile): 相互リンク機能の改修 (MisskeyIO#684)

This commit is contained in:
まっちゃてぃー。 2024-08-11 08:32:14 +09:00 committed by GitHub
parent b6a5a36eaa
commit 5a9d8a5564
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
41 changed files with 471 additions and 900 deletions

View file

@ -31,7 +31,7 @@ import * as ep___admin_avatarDecorations_list from './endpoints/admin/avatar-dec
import * as ep___admin_avatarDecorations_update from './endpoints/admin/avatar-decorations/update.js';
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_unsetUserMutualBanner from './endpoints/admin/unset-user-mutual-banner.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';
@ -422,7 +422,7 @@ const $admin_avatarDecorations_list: Provider = { provide: 'ep:admin/avatar-deco
const $admin_avatarDecorations_update: Provider = { provide: 'ep:admin/avatar-decorations/update', useClass: ep___admin_avatarDecorations_update.default };
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_unsetUserMutualBanner: Provider = { provide: 'ep:admin/unset-user-mutual-banner', useClass: ep___admin_unsetUserMutualBanner.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 };
@ -817,7 +817,7 @@ const $reversi_verify: Provider = { provide: 'ep:reversi/verify', useClass: ep__
$admin_avatarDecorations_update,
$admin_unsetUserAvatar,
$admin_unsetUserBanner,
$admin_unsetUserMutualBanner,
$admin_unsetUserMutualLink,
$admin_drive_cleanRemoteFiles,
$admin_drive_cleanup,
$admin_drive_deleteAllFilesOfAUser,
@ -1206,7 +1206,7 @@ const $reversi_verify: Provider = { provide: 'ep:reversi/verify', useClass: ep__
$admin_avatarDecorations_update,
$admin_unsetUserAvatar,
$admin_unsetUserBanner,
$admin_unsetUserMutualBanner,
$admin_unsetUserMutualLink,
$admin_drive_cleanRemoteFiles,
$admin_drive_cleanup,
$admin_drive_deleteAllFilesOfAUser,

View file

@ -31,7 +31,7 @@ import * as ep___admin_avatarDecorations_list from './endpoints/admin/avatar-dec
import * as ep___admin_avatarDecorations_update from './endpoints/admin/avatar-decorations/update.js';
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_unsetUserMutualBanner from './endpoints/admin/unset-user-mutual-banner.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';
@ -420,7 +420,7 @@ const eps = [
['admin/avatar-decorations/update', ep___admin_avatarDecorations_update],
['admin/unset-user-avatar', ep___admin_unsetUserAvatar],
['admin/unset-user-banner', ep___admin_unsetUserBanner],
['admin/unset-user-mutual-banner', ep___admin_unsetUserMutualBanner],
['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

@ -1,5 +1,8 @@
import { Inject, Injectable } from '@nestjs/common';
import type { UserBannerRepository, UsersRepository } from '@/models/_.js';
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';
@ -9,7 +12,7 @@ export const meta = {
requireCredential: true,
requireModerator: true,
kind: 'write:admin:unset-user-mutual-banner',
kind: 'write:admin:unset-user-mutual-link',
} as const;
export const paramDef = {
@ -26,32 +29,27 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
constructor(
@Inject(DI.usersRepository)
private usersRepository: UsersRepository,
@Inject(DI.userProfilesRepository)
private userProfilesRepository: UserProfilesRepository,
@Inject(DI.userBannerRepository)
private userBannerRepository: UserBannerRepository,
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) {
if (user == null || userProfile == null) {
throw new Error('user not found');
}
const mutualBanner = await this.userBannerRepository.findOneBy({ userId: user.id });
if (mutualBanner == null) return;
await this.userBannerRepository.delete({
id: mutualBanner.id,
await this.userProfilesRepository.update(user.id, {
mutualLinkSections: [],
});
this.moderationLogService.log(me, 'unsetUserMutualBanner', {
this.moderationLogService.log(me, 'unsetUserMutualLink', {
userId: user.id,
userUsername: user.username,
userBannerDescription: mutualBanner.description,
userBannerUrl: mutualBanner.url,
fileId: mutualBanner.fileId,
userMutualLinkSections: userProfile.mutualLinkSections,
});
});
}

View file

@ -11,14 +11,7 @@ import { JSDOM } from 'jsdom';
import { extractCustomEmojisFromMfm } from '@/misc/extract-custom-emojis-from-mfm.js';
import { extractHashtags } from '@/misc/extract-hashtags.js';
import * as Acct from '@/misc/acct.js';
import type {
UsersRepository,
DriveFilesRepository,
UserProfilesRepository,
PagesRepository,
UserBannerRepository,
UserBannerPiningRepository,
} from '@/models/_.js';
import type { UsersRepository, DriveFilesRepository, UserProfilesRepository, PagesRepository } from '@/models/_.js';
import type { MiLocalUser, MiUser } from '@/models/User.js';
import { birthdaySchema, descriptionSchema, locationSchema, nameSchema } from '@/models/User.js';
import type { MiUserProfile } from '@/models/UserProfile.js';
@ -40,8 +33,6 @@ import type { Config } from '@/config.js';
import { safeForSql } from '@/misc/safe-for-sql.js';
import { AvatarDecorationService } from '@/core/AvatarDecorationService.js';
import { notificationRecieveConfig } from '@/models/json-schema/user.js';
import { UserBannerService } from '@/core/UserBannerService.js';
import { UserBannerPiningService } from '@/core/UserBannerPiningService.js';
import { ApiLoggerService } from '../../ApiLoggerService.js';
import { ApiError } from '../../error.js';
@ -234,24 +225,28 @@ export const paramDef = {
uniqueItems: true,
items: { type: 'string' },
},
mutualBannerPining: {
mutualLinkSections: {
type: 'array',
nullable: true,
items: {
type: 'string',
format: 'misskey:id',
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'],
},
},
myMutualBanner: {
type: 'object',
nullable: true,
properties: {
fileId: { type: 'string', format: 'misskey:id' },
description: { type: 'string' },
url: { type: 'string', nullable: true, format: 'url' },
},
required: ['fileId'],
},
},
} as const;
@ -270,17 +265,10 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
@Inject(DI.driveFilesRepository)
private driveFilesRepository: DriveFilesRepository,
@Inject(DI.userBannerRepository)
private userBannerRepository: UserBannerRepository,
@Inject(DI.pagesRepository)
private pagesRepository: PagesRepository,
@Inject(DI.userBannerPiningRepository)
private userBannerPiningRepository: UserBannerPiningRepository,
private userEntityService: UserEntityService,
private userBannerService: UserBannerService,
private driveFileEntityService: DriveFileEntityService,
private globalEventService: GlobalEventService,
private userFollowingService: UserFollowingService,
@ -292,7 +280,6 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
private cacheService: CacheService,
private httpRequestService: HttpRequestService,
private avatarDecorationService: AvatarDecorationService,
private userBannerPiningService: UserBannerPiningService,
) {
super(meta, paramDef, async (ps, _user, token) => {
const user = await this.usersRepository.findOneByOrFail({ id: _user.id }) as MiLocalUser;
@ -369,48 +356,41 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
updates.avatarBlurhash = null;
}
if (ps.mutualBannerPining) {
const bannerPiningNow = await this.userBannerPiningRepository.findBy({ userId: user.id });
const bannerPiningNowIds = new Set(bannerPiningNow.map(b => b.pinnedBannerId));
const mutualBannerPiningIds = new Set(ps.mutualBannerPining);
const bannersToAdd = [...mutualBannerPiningIds].filter(bannerId => !bannerPiningNowIds.has(bannerId));
const bannersToRemove = [...bannerPiningNowIds].filter(bannerId => !mutualBannerPiningIds.has(bannerId));
if (bannersToAdd.length > 0) {
await this.userBannerPiningService.addPinned(user.id, bannersToAdd);
if (ps.mutualLinkSections) {
if (ps.mutualLinkSections.length > policy.mutualLinkSectionLimit) {
throw new ApiError(meta.errors.restrictedByRole);
}
if (bannersToRemove.length > 0) {
await this.userBannerPiningService.removePinned(user.id, bannersToRemove);
}
}
const mutualLinkSections = ps.mutualLinkSections.map(async (section) => {
if (section.mutualLinks.length > policy.mutualLinkLimit) {
throw new ApiError(meta.errors.restrictedByRole);
}
if (ps.myMutualBanner) {
const banner = await this.userBannerRepository.findOneBy({
userId: user.id,
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,
};
});
const file = await this.driveFilesRepository.findOneBy({ id: ps.myMutualBanner.fileId });
const profileUrl = this.config.url + '/@' + user.username;
if (file === null) throw new ApiError(meta.errors.noSuchFile);
if (!file.type.startsWith('image/')) throw new ApiError(meta.errors.fileNotAnImage);
if (banner) {
await this.userBannerService.update(user.id, banner.id, ps.myMutualBanner.description ?? null, ps.myMutualBanner.url ?? profileUrl, ps.myMutualBanner.fileId);
} else {
await this.userBannerService.create(user.id, ps.myMutualBanner.description ?? null, ps.myMutualBanner.url ?? profileUrl, ps.myMutualBanner.fileId);
}
}
if (ps.myMutualBanner === null) {
const banner = await this.userBannerRepository.findOneBy({
userId: user.id,
});
if (banner) {
await this.userBannerService.delete(user.id, banner.id);
}
profileUpdates.mutualLinkSections = await Promise.all(mutualLinkSections);
}
if (ps.bannerId) {