diff --git a/locales/index.d.ts b/locales/index.d.ts index a4d8b4247..88ba23568 100644 --- a/locales/index.d.ts +++ b/locales/index.d.ts @@ -2401,13 +2401,13 @@ export interface Locale extends ILocale { */ "unsetUserBannerConfirm": string; /** - * 相互バナーを解除 + * 相互リンクを解除 */ - "unsetUserMutualBanner": string; + "unsetUserMutualLink": string; /** - * 相互バナーを解除しますか? + * 相互リンクを解除しますか? */ - "unsetUserMutualBannerConfirm": string; + "unsetUserMutualLinkConfirm": string; /** * すべてのファイルを削除 */ @@ -5092,9 +5092,9 @@ export interface Locale extends ILocale { */ "here": string; /** - * 相互バナー + * 相互リンク */ - "mutualBanner": string; + "mutualLink": string; /** * このユーザーのバナー */ @@ -6884,6 +6884,14 @@ export interface Locale extends ILocale { * アイコンデコレーションの最大取付個数 */ "avatarDecorationLimit": string; + /** + * 相互リンクのセクションの最大数 + */ + "mutualLinkSectionLimit": string; + /** + * セクション内の相互リンクの最大数 + */ + "mutualLinkLimit": string; }; "_condition": { /** @@ -8233,9 +8241,9 @@ export interface Locale extends ILocale { */ "write:admin:unset-user-banner": string; /** - * ユーザーの相互バナーを削除する + * ユーザーの相互リンクを削除する */ - "write:admin:unset-user-mutual-banner": string; + "write:admin:unset-user-mutual-link": string; /** * ユーザーの凍結を解除する */ @@ -8823,21 +8831,49 @@ export interface Locale extends ILocale { */ "avatarDecorationMax": ParameterizedString<"max">; /** - * 自身の相互リンクのバナーを設定 + * 相互リンクを編集 */ - "myMutualBanner": string; - /** - * あなた自身が相互リンクのバナーとして設定してほしい画像を設定することができます。 - */ - "myMutualBannerDescription": string; + "mutualLinksEdit": string; /** * 相互リンクのバナー */ - "mutualBanner": string; + "mutualLinksBanner": string; /** * 説明 */ - "mutualBannerDescriptionEdit": string; + "mutualLinksDescriptionEdit": string; + /** + * リンク先のURL + */ + "mutualLinksUrl": string; + /** + * このセクションをプロフィールにピン留め + */ + "mutualLinkPining": string; + /** + * 相互リンクを設定すると、あなたのプロフィールにバナーが表示されます。 + */ + "mutualLinksDescription": string; + /** + * 相互リンクを追加 + */ + "addMutualLink": string; + /** + * セクションを追加 + */ + "addMutualLinkSection": string; + /** + * セクション名 + */ + "sectionName": string; + /** + * セクション名を表示しないようにする + */ + "sectionNameNoneDescription": string; + /** + * セクション名を表示しない + */ + "sectionNameNone": string; }; "_exportOrImport": { /** diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml index e4564f9dd..19d01be56 100644 --- a/locales/ja-JP.yml +++ b/locales/ja-JP.yml @@ -596,8 +596,8 @@ unsetUserAvatar: "アイコンを解除" unsetUserAvatarConfirm: "アイコンを解除しますか?" unsetUserBanner: "バナーを解除" unsetUserBannerConfirm: "バナーを解除しますか?" -unsetUserMutualBanner: "相互バナーを解除" -unsetUserMutualBannerConfirm: "相互バナーを解除しますか?" +unsetUserMutualLink: "相互リンクを削除" +unsetUserMutualLinkConfirm: "相互リンクを削除しますか?" deleteAllFiles: "すべてのファイルを削除" deleteAllFilesConfirm: "すべてのファイルを削除しますか?" removeAllFollowing: "フォローを全解除" @@ -1268,7 +1268,7 @@ reportComplete: "通報完了" blockThisUser: "このユーザーをブロックする" muteThisUser: "このユーザーをミュートする" here: "こちら" -mutualBanner: "相互バナー" +mutualLink: "相互リンク" mutualBannerThisUser: "このユーザーのバナー" maximum: "最大" @@ -1776,6 +1776,8 @@ _role: canUseDriveFileInSoundSettings: "サウンド設定でドライブのファイルを利用" canUseReaction: "リアクションの利用" avatarDecorationLimit: "アイコンデコレーションの最大取付個数" + mutualLinkSectionLimit: "相互リンクのセクションの最大数" + mutualLinkLimit: "セクション内の相互リンクの最大数" _condition: roleAssignedTo: "マニュアルロールにアサイン済み" isLocal: "ローカルユーザー" @@ -2157,7 +2159,7 @@ _permissions: "write:admin:suspend-user": "ユーザーを凍結する" "write:admin:unset-user-avatar": "ユーザーのアバターを削除する" "write:admin:unset-user-banner": "ユーザーのバーナーを削除する" - "write:admin:unset-user-mutual-banner": "ユーザーの相互バナーを削除する" + "write:admin:unset-user-mutual-link": "ユーザーの相互リンクを削除する" "write:admin:unsuspend-user": "ユーザーの凍結を解除する" "write:admin:meta": "インスタンスのメタデータを操作する" "write:admin:user-note": "モデレーションノートを操作する" @@ -2319,10 +2321,17 @@ _profile: changeBanner: "バナー画像を変更" verifiedLinkDescription: "内容にURLを設定すると、リンク先のWebサイトに自分のプロフィールへのリンクが含まれている場合に所有者確認済みアイコンを表示させることができます。" avatarDecorationMax: "最大{max}つまでデコレーションを付けられます。" - myMutualBanner: "自身の相互リンクのバナーを設定" - myMutualBannerDescription: "あなた自身が相互リンクのバナーとして設定してほしい画像を設定することができます。" - mutualBanner: "相互リンクのバナー" - mutualBannerDescriptionEdit: "説明" + mutualLinksEdit: "相互リンクを編集" + mutualLinksBanner: "相互リンクのバナー" + mutualLinksDescriptionEdit: "説明" + mutualLinksUrl: "リンク先のURL" + mutualLinkPining: "このセクションをプロフィールにピン留め" + mutualLinksDescription: "相互リンクを設定すると、あなたのプロフィールにバナーが表示されます。" + addMutualLink: "相互リンクを追加" + addMutualLinkSection: "セクションを追加" + sectionName: "セクション名" + sectionNameNoneDescription: "セクション名を表示しないようにする" + sectionNameNone: "セクション名を表示しない" _exportOrImport: allNotes: "全てのノート" diff --git a/packages/backend/migration/1723213482131-mutualBanner.js b/packages/backend/migration/1723213482131-mutualBanner.js deleted file mode 100644 index c3bcb34ce..000000000 --- a/packages/backend/migration/1723213482131-mutualBanner.js +++ /dev/null @@ -1,27 +0,0 @@ -export class MutualBanner1723213482131 { - name = 'MutualBanner1723213482131' - - async up(queryRunner) { - await queryRunner.query(`CREATE TABLE "user_banner" ("id" character varying(32) NOT NULL, "userId" character varying(32) NOT NULL, "description" character varying(1024), "url" character varying(1024), "fileId" character varying(32) NOT NULL, CONSTRAINT "PK_0d9a418f048e308dbfb6562149d" PRIMARY KEY ("id"))`); - await queryRunner.query(`CREATE INDEX "IDX_fa06ea2e2375449537ced781f1" ON "user_banner" ("userId") `); - await queryRunner.query(`CREATE TABLE "user_banner_pining" ("id" character varying(32) NOT NULL, "userId" character varying(32) NOT NULL, "pinnedBannerId" character varying(32) NOT NULL, CONSTRAINT "PK_970d24f72e8d2b20f8c21ec5d11" PRIMARY KEY ("id"))`); - await queryRunner.query(`CREATE INDEX "IDX_3b74dc21b68da606011c81609c" ON "user_banner_pining" ("userId") `); - await queryRunner.query(`CREATE UNIQUE INDEX "IDX_7d51b5a8ae859e0023a98837a1" ON "user_banner_pining" ("userId", "pinnedBannerId") `); - await queryRunner.query(`ALTER TABLE "user_banner" ADD CONSTRAINT "FK_fa06ea2e2375449537ced781f15" FOREIGN KEY ("userId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION`); - await queryRunner.query(`ALTER TABLE "user_banner" ADD CONSTRAINT "FK_3de9f17cce2c10f6938fb261c0b" FOREIGN KEY ("fileId") REFERENCES "drive_file"("id") ON DELETE CASCADE ON UPDATE NO ACTION`); - await queryRunner.query(`ALTER TABLE "user_banner_pining" ADD CONSTRAINT "FK_3b74dc21b68da606011c81609c9" FOREIGN KEY ("userId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION`); - await queryRunner.query(`ALTER TABLE "user_banner_pining" ADD CONSTRAINT "FK_d13be8242980f7018d664f780f6" FOREIGN KEY ("pinnedBannerId") REFERENCES "user_banner"("id") ON DELETE CASCADE ON UPDATE NO ACTION`); - } - - async down(queryRunner) { - await queryRunner.query(`ALTER TABLE "user_banner_pining" DROP CONSTRAINT "FK_d13be8242980f7018d664f780f6"`); - await queryRunner.query(`ALTER TABLE "user_banner_pining" DROP CONSTRAINT "FK_3b74dc21b68da606011c81609c9"`); - await queryRunner.query(`ALTER TABLE "user_banner" DROP CONSTRAINT "FK_3de9f17cce2c10f6938fb261c0b"`); - await queryRunner.query(`ALTER TABLE "user_banner" DROP CONSTRAINT "FK_fa06ea2e2375449537ced781f15"`); - await queryRunner.query(`DROP INDEX "public"."IDX_7d51b5a8ae859e0023a98837a1"`); - await queryRunner.query(`DROP INDEX "public"."IDX_3b74dc21b68da606011c81609c"`); - await queryRunner.query(`DROP TABLE "user_banner_pining"`); - await queryRunner.query(`DROP INDEX "public"."IDX_fa06ea2e2375449537ced781f1"`); - await queryRunner.query(`DROP TABLE "user_banner"`); - } -} diff --git a/packages/backend/migration/1723311628855-mutuallinks.js b/packages/backend/migration/1723311628855-mutuallinks.js new file mode 100644 index 000000000..917bd4ec3 --- /dev/null +++ b/packages/backend/migration/1723311628855-mutuallinks.js @@ -0,0 +1,11 @@ +export class Mutuallinks1723311628855 { + name = 'Mutuallinks1723311628855' + + async up(queryRunner) { + await queryRunner.query(`ALTER TABLE "user_profile" ADD "mutualLinkSections" jsonb NOT NULL DEFAULT '[]'`); + } + + async down(queryRunner) { + await queryRunner.query(`ALTER TABLE "user_profile" DROP COLUMN "mutualLinkSections"`); + } +} diff --git a/packages/backend/src/core/CoreModule.ts b/packages/backend/src/core/CoreModule.ts index 666a462cf..3e5223944 100644 --- a/packages/backend/src/core/CoreModule.ts +++ b/packages/backend/src/core/CoreModule.ts @@ -5,8 +5,6 @@ import { Module } from '@nestjs/common'; import { FanoutTimelineEndpointService } from '@/core/FanoutTimelineEndpointService.js'; -import { UserBannerEntityService } from '@/core/entities/UserBannerEntityService.js'; -import { UserBannerPiningEntityService } from '@/core/entities/UserBannerPiningEntityService.js'; import { AccountMoveService } from './AccountMoveService.js'; import { AccountUpdateService } from './AccountUpdateService.js'; import { AiService } from './AiService.js'; @@ -37,8 +35,6 @@ import { ModerationLogService } from './ModerationLogService.js'; import { NoteCreateService } from './NoteCreateService.js'; import { NoteDeleteService } from './NoteDeleteService.js'; import { NotePiningService } from './NotePiningService.js'; -import { UserBannerPiningService } from './UserBannerPiningService.js'; -import { UserBannerService } from './UserBannerService.js'; import { NoteReadService } from './NoteReadService.js'; import { NotificationService } from './NotificationService.js'; import { PollService } from './PollService.js'; @@ -177,8 +173,6 @@ const $ModerationLogService: Provider = { provide: 'ModerationLogService', useEx const $NoteCreateService: Provider = { provide: 'NoteCreateService', useExisting: NoteCreateService }; const $NoteDeleteService: Provider = { provide: 'NoteDeleteService', useExisting: NoteDeleteService }; const $NotePiningService: Provider = { provide: 'NotePiningService', useExisting: NotePiningService }; -const $UserBannerPiningService: Provider = { provide: 'UserBannerPiningService', useExisting: UserBannerPiningService }; -const $UserBannerService: Provider = { provide: 'UserBannerService', useExisting: UserBannerService }; const $NoteReadService: Provider = { provide: 'NoteReadService', useExisting: NoteReadService }; const $NotificationService: Provider = { provide: 'NotificationService', useExisting: NotificationService }; const $PollService: Provider = { provide: 'PollService', useExisting: PollService }; @@ -259,8 +253,6 @@ const $PageLikeEntityService: Provider = { provide: 'PageLikeEntityService', use const $SigninEntityService: Provider = { provide: 'SigninEntityService', useExisting: SigninEntityService }; const $UserEntityService: Provider = { provide: 'UserEntityService', useExisting: UserEntityService }; const $UserListEntityService: Provider = { provide: 'UserListEntityService', useExisting: UserListEntityService }; -const $UserBannerEntityService: Provider = { provide: 'UserBannerEntityService', useExisting: UserBannerEntityService }; -const $UserBannerPiningEntityService: Provider = { provide: 'UserBannerPiningEntityService', useExisting: UserBannerPiningEntityService }; const $FlashEntityService: Provider = { provide: 'FlashEntityService', useExisting: FlashEntityService }; const $FlashLikeEntityService: Provider = { provide: 'FlashLikeEntityService', useExisting: FlashLikeEntityService }; const $RoleEntityService: Provider = { provide: 'RoleEntityService', useExisting: RoleEntityService }; @@ -323,8 +315,6 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting NoteCreateService, NoteDeleteService, NotePiningService, - UserBannerPiningService, - UserBannerService, NoteReadService, NotificationService, PollService, @@ -403,8 +393,6 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting SigninEntityService, UserEntityService, UserListEntityService, - UserBannerEntityService, - UserBannerPiningEntityService, FlashEntityService, FlashLikeEntityService, RoleEntityService, @@ -463,8 +451,6 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting $NoteCreateService, $NoteDeleteService, $NotePiningService, - $UserBannerService, - $UserBannerPiningService, $NoteReadService, $NotificationService, $PollService, @@ -543,8 +529,6 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting $SigninEntityService, $UserEntityService, $UserListEntityService, - $UserBannerEntityService, - $UserBannerPiningEntityService, $FlashEntityService, $FlashLikeEntityService, $RoleEntityService, @@ -604,8 +588,6 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting NoteCreateService, NoteDeleteService, NotePiningService, - UserBannerService, - UserBannerPiningService, NoteReadService, NotificationService, PollService, @@ -683,8 +665,6 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting SigninEntityService, UserEntityService, UserListEntityService, - UserBannerEntityService, - UserBannerPiningEntityService, FlashEntityService, FlashLikeEntityService, RoleEntityService, @@ -743,8 +723,6 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting $NoteCreateService, $NoteDeleteService, $NotePiningService, - $UserBannerService, - $UserBannerPiningService, $NoteReadService, $NotificationService, $PollService, @@ -822,8 +800,6 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting $SigninEntityService, $UserEntityService, $UserListEntityService, - $UserBannerEntityService, - $UserBannerPiningEntityService, $FlashEntityService, $FlashLikeEntityService, $RoleEntityService, diff --git a/packages/backend/src/core/RoleService.ts b/packages/backend/src/core/RoleService.ts index cf188560b..111eb5c6f 100644 --- a/packages/backend/src/core/RoleService.ts +++ b/packages/backend/src/core/RoleService.ts @@ -69,6 +69,8 @@ export type RolePolicies = { userEachUserListsLimit: number; rateLimitFactor: number; avatarDecorationLimit: number; + mutualLinkSectionLimit: number; + mutualLinkLimit: number; }; export const DEFAULT_POLICIES: RolePolicies = { @@ -108,6 +110,8 @@ export const DEFAULT_POLICIES: RolePolicies = { userEachUserListsLimit: 50, rateLimitFactor: 1, avatarDecorationLimit: 1, + mutualLinkSectionLimit: 1, + mutualLinkLimit: 15, }; @Injectable() @@ -420,6 +424,8 @@ export class RoleService implements OnApplicationShutdown, OnModuleInit { userEachUserListsLimit: calc('userEachUserListsLimit', vs => Math.max(...vs)), rateLimitFactor: calc('rateLimitFactor', vs => Math.max(...vs)), avatarDecorationLimit: calc('avatarDecorationLimit', vs => Math.max(...vs)), + mutualLinkSectionLimit: calc('mutualLinkSectionLimit', vs => Math.max(...vs)), + mutualLinkLimit: calc('mutualLinkLimit', vs => Math.max(...vs)), }; } diff --git a/packages/backend/src/core/UserBannerPiningService.ts b/packages/backend/src/core/UserBannerPiningService.ts deleted file mode 100644 index 1e9887678..000000000 --- a/packages/backend/src/core/UserBannerPiningService.ts +++ /dev/null @@ -1,55 +0,0 @@ -import { Inject, Injectable } from '@nestjs/common'; -import { In } from 'typeorm'; -import { bindThis } from '@/decorators.js'; -import { MiUser } from '@/models/User.js'; -import { MiUserBanner } from '@/models/UserBanner.js'; -import type { MiUserBannerPining, UserBannerPiningRepository, UserBannerRepository } from '@/models/_.js'; -import { DI } from '@/di-symbols.js'; -import { IdentifiableError } from '@/misc/identifiable-error.js'; -import { IdService } from '@/core/IdService.js'; - -@Injectable() -export class UserBannerPiningService { - constructor( - @Inject(DI.userBannerRepository) - private userBannerRepository: UserBannerRepository, - @Inject(DI.userBannerPiningRepository) - private userBannerPiningRepository: UserBannerPiningRepository, - - private idService: IdService, - ) { - - } - - /** - * 指定したユーザーのバナーをピン留めします - * @param userId - * @param bannerIds - */ - public async addPinned(userId: MiUser['id'], bannerIds: MiUserBanner['id'][]) { - const pinsToInsert = bannerIds.map(bannerId => ({ - id: this.idService.gen(), - userId, - pinnedBannerId: bannerId, - } as MiUserBannerPining)); - await this.userBannerPiningRepository - .createQueryBuilder() - .insert() - .values(pinsToInsert) - .orIgnore() - .execute(); - } - - /** - * 指定したユーザーのバナーのピン留めを解除します - * @param userId - * @param bannerIds - */ - @bindThis - public async removePinned(userId:MiUser['id'], bannerIds:MiUserBanner['id'][]) { - await this.userBannerPiningRepository.delete({ - userId, - pinnedBannerId: In(bannerIds), - }); - } -} diff --git a/packages/backend/src/core/UserBannerService.ts b/packages/backend/src/core/UserBannerService.ts deleted file mode 100644 index ff381988f..000000000 --- a/packages/backend/src/core/UserBannerService.ts +++ /dev/null @@ -1,115 +0,0 @@ -import { Inject, Injectable } from '@nestjs/common'; -import { bindThis } from '@/decorators.js'; -import { MiUser } from '@/models/User.js'; -import { MiUserBanner } from '@/models/UserBanner.js'; -import type { DriveFilesRepository, MiDriveFile, UserBannerRepository } from '@/models/_.js'; -import { DI } from '@/di-symbols.js'; -import { IdentifiableError } from '@/misc/identifiable-error.js'; -import { IdService } from '@/core/IdService.js'; - -@Injectable() -export class UserBannerService { - constructor( - @Inject(DI.userBannerRepository) - private userBannerRepository: UserBannerRepository, - @Inject(DI.driveFilesRepository) - private driveFilesRepository: DriveFilesRepository, - - private idService: IdService, - ) { - - } - - /** - * 指定したユーザーのバナーを作成します - * @param userId - * @param description - * @param url - * @param fileId - */ - @bindThis - public async create(userId: MiUser['id'], description: string | null, url: string, fileId: MiDriveFile['id']) { - const banner = await this.userBannerRepository.findOneBy({ - userId, - }); - - if (banner) throw new IdentifiableError('9dab45d9-cc66-4dfa-8305-610834e7f256', 'Already exists.'); - - const file = await this.driveFilesRepository.findOneBy({ - id: fileId, - }); - - if (file == null) throw new IdentifiableError('e61187d1-9270-426b-8dc6-6b233c545133', 'No such file.'); - - return await this.userBannerRepository.insert({ - id: this.idService.gen(), - userId, - description: description ?? null, - fileId: file.id, - url: url, - } as MiUserBanner); - } - - /** - * 指定したユーザーのバナーを更新します - * @param userId - * @param bannerId - * @param description - * @param url - * @param fileId - */ - @bindThis - public async update(userId: MiUser['id'], bannerId: MiUserBanner['id'], description: string | null, url: string | null, fileId: MiDriveFile['id'] ) { - const banner = await this.userBannerRepository.findOneBy({ - id: bannerId, - }); - - if (banner == null) { - throw new IdentifiableError('ac26da32-1659-4fbb-82c2-fc11a494799f', 'No such banner.'); - } - - if (banner.userId !== userId) { - throw new IdentifiableError('dfe79730-96f7-4d65-8c2a-b0975bf3524c', 'Not this user banner.'); - } - - const file = await this.driveFilesRepository.findOneBy({ - id: fileId, - }); - - if (file == null) { - throw new IdentifiableError('e61187d1-9270-426b-8dc6-6b233c545133', 'No such file.'); - } - - await this.userBannerRepository.update({ - id: bannerId, - }, { - description: description ?? null, - fileId: file.id, - url: url ?? null, - }); - } - - /** - * 指定したユーザーのバナー削除します - * @param userId - * @param bannerId - */ - @bindThis - public async delete(userId: MiUser['id'], bannerId: MiUserBanner['id']) { - const banner = await this.userBannerRepository.findOneBy({ - id: bannerId, - }); - - if (banner == null) { - throw new IdentifiableError('f4b158a5-610f-4ed3-b228-3507ebe1bba6', 'No such banner.'); - } - - if (banner.userId !== userId) { - throw new IdentifiableError('ad84053d-0cf4-4446-ac72-209adef15835', 'Not this user banner.'); - } - - await this.userBannerRepository.delete({ - id: bannerId, - }); - } -} diff --git a/packages/backend/src/core/entities/UserBannerEntityService.ts b/packages/backend/src/core/entities/UserBannerEntityService.ts deleted file mode 100644 index 25f4bb27b..000000000 --- a/packages/backend/src/core/entities/UserBannerEntityService.ts +++ /dev/null @@ -1,56 +0,0 @@ -import { Inject, Injectable, OnModuleInit } from '@nestjs/common'; -import { ModuleRef } from '@nestjs/core'; -import { bindThis } from '@/decorators.js'; -import type { MiUser } from '@/models/User.js'; -import { DI } from '@/di-symbols.js'; -import type { DriveFilesRepository, MiUserBanner, UserBannerRepository } from '@/models/_.js'; -import { IdentifiableError } from '@/misc/identifiable-error.js'; -import { UserEntityService } from '@/core/entities/UserEntityService.js'; -import type { Packed } from '@/misc/json-schema.js'; - -@Injectable() -export class UserBannerEntityService implements OnModuleInit { - private userEntityService: UserEntityService; - constructor( - @Inject(DI.userBannerRepository) - private userBannerRepository: UserBannerRepository, - @Inject(DI.driveFilesRepository) - private driveFilesRepository: DriveFilesRepository, - private moduleRef: ModuleRef, - ) { - } - - async onModuleInit() { - this.userEntityService = this.moduleRef.get(UserEntityService.name); - } - - @bindThis - public async pack( - src: MiUserBanner | MiUserBanner['id'] | null | undefined, - me: { id: MiUser['id'] } | null | undefined, - ): Promise> { - if (!src) throw new IdentifiableError('9dab45d9-cc66-4dfa-8305-610834e7f256', 'No such banner.'); - - const banner = typeof src === 'object' ? src : await this.userBannerRepository.findOneByOrFail({ id: src }); - const file = await this.driveFilesRepository.findOneByOrFail({ id: banner.fileId }); - - return { - id: banner.id, - user: await this.userEntityService.pack(banner.userId, me), - description: banner.description, - imgUrl: file.url, - url: banner.url, - fileId: file.id, - }; - } - - @bindThis - public async packMany( - src: MiUserBanner[] | MiUserBanner['id'][], - me: { id: MiUser['id'] } | null | undefined, - ): Promise[]> { - return (await Promise.allSettled(src.map(x => this.pack(x, me)))) - .filter(result => result.status === 'fulfilled') - .map(result => (result as PromiseFulfilledResult>).value); - } -} diff --git a/packages/backend/src/core/entities/UserBannerPiningEntityService.ts b/packages/backend/src/core/entities/UserBannerPiningEntityService.ts deleted file mode 100644 index b550875c9..000000000 --- a/packages/backend/src/core/entities/UserBannerPiningEntityService.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { Injectable } from '@nestjs/common'; -import { bindThis } from '@/decorators.js'; -import type { MiUser } from '@/models/User.js'; -import type { MiUserBannerPining } from '@/models/_.js'; -import { UserBannerEntityService } from '@/core/entities/UserBannerEntityService.js'; -import { Packed } from '@/misc/json-schema.js'; - -@Injectable() -export class UserBannerPiningEntityService { - constructor( - private userBannerEntityService: UserBannerEntityService, - ) {} - - @bindThis - public async packMany( - src: MiUserBannerPining[], - me: { id: MiUser['id'] } | null | undefined, - ) : Promise[]> { - return (await Promise.allSettled(src.map(pining => this.userBannerEntityService.pack(pining.pinnedBannerId, me)))) - .filter(result => result.status === 'fulfilled') - .map(result => (result as PromiseFulfilledResult>).value); - } -} diff --git a/packages/backend/src/core/entities/UserEntityService.ts b/packages/backend/src/core/entities/UserEntityService.ts index 96e56dac7..2091110e2 100644 --- a/packages/backend/src/core/entities/UserEntityService.ts +++ b/packages/backend/src/core/entities/UserEntityService.ts @@ -28,20 +28,16 @@ import type { FollowingsRepository, FollowRequestsRepository, MiFollowing, - MiUserBanner, MiUserNotePining, MiUserProfile, MutingsRepository, NoteUnreadsRepository, RenoteMutingsRepository, - UserBannerRepository, - UserBannerPiningRepository, UserMemoRepository, UserNotePiningsRepository, UserProfilesRepository, UserSecurityKeysRepository, UsersRepository, - MiUserBannerPining, } from '@/models/_.js'; import { bindThis } from '@/decorators.js'; import { RoleService } from '@/core/RoleService.js'; @@ -53,8 +49,6 @@ import type { AnnouncementService } from '@/core/AnnouncementService.js'; import type { CustomEmojiService } from '@/core/CustomEmojiService.js'; import { AvatarDecorationService } from '@/core/AvatarDecorationService.js'; import { isNotNull } from '@/misc/is-not-null.js'; -import { UserBannerEntityService } from '@/core/entities/UserBannerEntityService.js'; -import { UserBannerPiningEntityService } from '@/core/entities/UserBannerPiningEntityService.js'; import type { OnModuleInit } from '@nestjs/common'; import type { NoteEntityService } from './NoteEntityService.js'; import type { PageEntityService } from './PageEntityService.js'; @@ -135,19 +129,11 @@ export class UserEntityService implements OnModuleInit { @Inject(DI.userNotePiningsRepository) private userNotePiningsRepository: UserNotePiningsRepository, - @Inject(DI.userBannerRepository) - private userBannerRepository: UserBannerRepository, - @Inject(DI.userBannerPiningRepository) - private userBannerPiningRepository: UserBannerPiningRepository, - @Inject(DI.userProfilesRepository) private userProfilesRepository: UserProfilesRepository, @Inject(DI.userMemosRepository) private userMemosRepository: UserMemoRepository, - - private userBannerEntityService: UserBannerEntityService, - private userBannerPiningEntityService: UserBannerPiningEntityService, ) { } @@ -457,8 +443,6 @@ export class UserEntityService implements OnModuleInit { } let pins: MiUserNotePining[] = []; - let myMutualBanner: MiUserBanner | null = null; - let mutualBanners: MiUserBannerPining[] = []; if (isDetailed) { if (opts.pinNotes) { pins = opts.pinNotes.get(user.id) ?? []; @@ -469,12 +453,6 @@ export class UserEntityService implements OnModuleInit { .orderBy('pin.id', 'DESC') .getMany(); } - if (user.id) { - [myMutualBanner, mutualBanners] = await Promise.all([ - this.userBannerRepository.findOneBy({ userId: user.id }), - this.userBannerPiningRepository.findBy({ userId: user.id }), - ]); - } } const followingCount = profile == null ? null : @@ -555,8 +533,7 @@ export class UserEntityService implements OnModuleInit { lang: profile!.lang, fields: profile!.fields, verifiedLinks: profile!.verifiedLinks, - mutualBanners: mutualBanners.length > 0 ? this.userBannerPiningEntityService.packMany(mutualBanners, me) : [], - myMutualBanner: myMutualBanner ? this.userBannerEntityService.pack(myMutualBanner, me) : null, + mutualLinkSections: profile!.mutualLinkSections, followersCount: followersCount ?? 0, followingCount: followingCount ?? 0, notesCount: user.notesCount, @@ -727,7 +704,7 @@ export class UserEntityService implements OnModuleInit { } } - return (await Promise.allSettled(_users.map(u => this.pack(u, me, { ...options, userProfile: profilesMap.get(u.id), userRelations: userRelations, userMemos: userMemos, pinNotes: pinNotes })))) + return (await Promise.allSettled(_users.map(u => this.pack(u, me, { ...options, userProfile: profilesMap?.get(u.id), userRelations: userRelations, userMemos: userMemos, pinNotes: pinNotes })))) .filter(result => result.status === 'fulfilled') .map(result => (result as PromiseFulfilledResult>).value); } diff --git a/packages/backend/src/di-symbols.ts b/packages/backend/src/di-symbols.ts index 2d2b231e2..fb570d0b4 100644 --- a/packages/backend/src/di-symbols.ts +++ b/packages/backend/src/di-symbols.ts @@ -28,8 +28,6 @@ export const DI = { pollsRepository: Symbol('pollsRepository'), pollVotesRepository: Symbol('pollVotesRepository'), userProfilesRepository: Symbol('userProfilesRepository'), - userBannerRepository: Symbol('userBannerRepository'), - userBannerPiningRepository: Symbol('userBannerPiningRepository'), userKeypairsRepository: Symbol('userKeypairsRepository'), userPendingsRepository: Symbol('userPendingsRepository'), userSecurityKeysRepository: Symbol('userSecurityKeysRepository'), diff --git a/packages/backend/src/misc/json-schema.ts b/packages/backend/src/misc/json-schema.ts index 9f2b52577..27fd280a8 100644 --- a/packages/backend/src/misc/json-schema.ts +++ b/packages/backend/src/misc/json-schema.ts @@ -11,7 +11,6 @@ import { packedMeDetailedSchema, packedUserDetailedSchema, packedUserSchema, - packedUserBannerSchema, } from '@/models/json-schema/user.js'; import { packedAbuseUserReportSchema } from '@/models/json-schema/abuse-user-report.js'; import { packedAntennaSchema } from '@/models/json-schema/antenna.js'; @@ -69,7 +68,6 @@ export const refs = { MeDetailed: packedMeDetailedSchema, UserDetailed: packedUserDetailedSchema, User: packedUserSchema, - UserBanner: packedUserBannerSchema, UserList: packedUserListSchema, UserListMembership: packedUserListMembershipSchema, diff --git a/packages/backend/src/models/RepositoryModule.ts b/packages/backend/src/models/RepositoryModule.ts index 0910ad74c..a61094500 100644 --- a/packages/backend/src/models/RepositoryModule.ts +++ b/packages/backend/src/models/RepositoryModule.ts @@ -76,8 +76,6 @@ import { MiWebhook, MiBubbleGameRecord, MiReversiGame, - MiUserBannerPining, - MiUserBanner, } from './_.js'; import type { DataSource } from 'typeorm'; import type { Provider } from '@nestjs/common'; @@ -208,18 +206,6 @@ const $userNotePiningsRepository: Provider = { inject: [DI.db], }; -const $userBannerRepository: Provider = { - provide: DI.userBannerRepository, - useFactory: (db: DataSource) => db.getRepository(MiUserBanner), - inject: [DI.db], -}; - -const $userBannerPiningRepository: Provider = { - provide: DI.userBannerPiningRepository, - useFactory: (db: DataSource) => db.getRepository(MiUserBannerPining), - inject: [DI.db], -}; - const $userIpsRepository: Provider = { provide: DI.userIpsRepository, useFactory: (db: DataSource) => db.getRepository(MiUserIp), @@ -539,8 +525,6 @@ const $abuseReportResolversRepository: Provider = { $userListFavoritesRepository, $userListMembershipsRepository, $userNotePiningsRepository, - $userBannerPiningRepository, - $userBannerRepository, $userIpsRepository, $usedUsernamesRepository, $followingsRepository, @@ -613,8 +597,6 @@ const $abuseReportResolversRepository: Provider = { $userListFavoritesRepository, $userListMembershipsRepository, $userNotePiningsRepository, - $userBannerPiningRepository, - $userBannerRepository, $userIpsRepository, $usedUsernamesRepository, $followingsRepository, diff --git a/packages/backend/src/models/UserBanner.ts b/packages/backend/src/models/UserBanner.ts deleted file mode 100644 index 108587b02..000000000 --- a/packages/backend/src/models/UserBanner.ts +++ /dev/null @@ -1,42 +0,0 @@ -import { Entity, Column, Index, JoinColumn, PrimaryColumn, ManyToOne } from 'typeorm'; -import { id } from './util/id.js'; -import { MiUser } from './User.js'; -import { MiDriveFile } from './DriveFile.js'; - -@Entity('user_banner') -export class MiUserBanner { - @PrimaryColumn(id()) - public id: string; - - @Index() - @Column(id()) - public userId: MiUser['id']; - - @ManyToOne(type => MiUser, { - onDelete: 'CASCADE', - }) - @JoinColumn() - public user: MiUser | null; - - @Column('varchar', { - length: 1024, - nullable: true, - }) - public description: string | null; - @Column('varchar', { - length: 1024, - nullable: true, - }) - public url: string | null; - - @Column({ - ...id(), - }) - public fileId: MiDriveFile['id']; - - @ManyToOne(type => MiDriveFile, { - onDelete: 'CASCADE', - }) - @JoinColumn() - public file: MiDriveFile; -} diff --git a/packages/backend/src/models/UserBannerPining.ts b/packages/backend/src/models/UserBannerPining.ts deleted file mode 100644 index 754de012e..000000000 --- a/packages/backend/src/models/UserBannerPining.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { Entity, Column, Index, JoinColumn, PrimaryColumn, ManyToOne, OneToOne } from 'typeorm'; -import { MiUserBanner } from '@/models/UserBanner.js'; -import { id } from './util/id.js'; -import { MiUser } from './User.js'; - -@Entity('user_banner_pining') -@Index(['userId', 'pinnedBannerId'], { unique: true }) -export class MiUserBannerPining { - @PrimaryColumn(id()) - public id: string; - - @Index() - @Column(id()) - public userId: MiUser['id']; - - @ManyToOne(type => MiUser, { - onDelete: 'CASCADE', - }) - @JoinColumn() - public user: MiUser | null; - - @Column({ - ...id(), - }) - public pinnedBannerId: MiUserBanner['id']; - - @ManyToOne(type => MiUserBanner, { - onDelete: 'CASCADE', - }) - @JoinColumn() - public pinnedBanner: MiUserBanner; -} diff --git a/packages/backend/src/models/UserProfile.ts b/packages/backend/src/models/UserProfile.ts index 5f1d9611e..550e70a3f 100644 --- a/packages/backend/src/models/UserProfile.ts +++ b/packages/backend/src/models/UserProfile.ts @@ -9,6 +9,7 @@ import { id } from './util/id.js'; import { MiUser } from './User.js'; import { MiPage } from './Page.js'; import { MiUserList } from './UserList.js'; +import type { MiDriveFile } from './DriveFile.js'; // TODO: このテーブルで管理している情報すべてレジストリで管理するようにしても良いかも // ただ、「emailVerified が true なユーザーを find する」のようなクエリは書けなくなるからウーン @@ -42,6 +43,18 @@ export class MiUserProfile { }) public description: string | null; + @Column('jsonb', { + default: [], + }) + public mutualLinkSections: { + name: string | null; + mutualLinks: { + fileId: MiDriveFile['id']; + description: string | null; + imgSrc: string; + }[]; + }[] | []; + @Column('jsonb', { default: [], }) diff --git a/packages/backend/src/models/_.ts b/packages/backend/src/models/_.ts index 7e4455bb9..ca1410c24 100644 --- a/packages/backend/src/models/_.ts +++ b/packages/backend/src/models/_.ts @@ -60,8 +60,6 @@ import { MiUserListMembership } from '@/models/UserListMembership.js'; import { MiUserNotePining } from '@/models/UserNotePining.js'; import { MiUserPending } from '@/models/UserPending.js'; import { MiUserProfile } from '@/models/UserProfile.js'; -import { MiUserBanner } from '@/models/UserBanner.js'; -import { MiUserBannerPining } from '@/models/UserBannerPining.js'; import { MiUserPublickey } from '@/models/UserPublickey.js'; import { MiUserSecurityKey } from '@/models/UserSecurityKey.js'; import { MiUserMemo } from '@/models/UserMemo.js'; @@ -136,8 +134,6 @@ export { MiUserNotePining, MiUserPending, MiUserProfile, - MiUserBanner, - MiUserBannerPining, MiUserPublickey, MiUserSecurityKey, MiWebhook, @@ -210,8 +206,6 @@ export type UserListMembershipsRepository = Repository; export type UserNotePiningsRepository = Repository; export type UserPendingsRepository = Repository; export type UserProfilesRepository = Repository; -export type UserBannerRepository = Repository; -export type UserBannerPiningRepository = Repository; export type UserPublickeysRepository = Repository; export type UserSecurityKeysRepository = Repository; export type WebhooksRepository = Repository; diff --git a/packages/backend/src/models/json-schema/role.ts b/packages/backend/src/models/json-schema/role.ts index 519e95c47..166a085f5 100644 --- a/packages/backend/src/models/json-schema/role.ts +++ b/packages/backend/src/models/json-schema/role.ts @@ -312,6 +312,14 @@ export const packedRolePoliciesSchema = { type: 'integer', optional: false, nullable: false, }, + mutualLinkSectionLimit: { + type: 'integer', + optional: false, nullable: false, + }, + mutualLinkLimit: { + type: 'integer', + optional: false, nullable: false, + }, }, } as const; diff --git a/packages/backend/src/models/json-schema/user.ts b/packages/backend/src/models/json-schema/user.ts index 18f9c8124..a87383f91 100644 --- a/packages/backend/src/models/json-schema/user.ts +++ b/packages/backend/src/models/json-schema/user.ts @@ -386,6 +386,29 @@ export const packedUserDetailedNotMeOnlySchema = { type: 'string', nullable: false, optional: true, }, + mutualLinkSections: { + type: 'array', + items: { + type: 'object', + properties: { + name: { type: 'string', nullable: true }, + mutualLinks: { + type: 'array', + items: { + type: 'object', + properties: { + url: { type: 'string' }, + fileId: { type: 'string', format: 'misskey:id' }, + description: { type: 'string', nullable: true }, + imgSrc: { type: 'string' }, + }, + required: ['url', 'fileId'], + }, + }, + }, + required: ['mutualLinks'], + }, + }, //#region relations isFollowing: { type: 'boolean', @@ -428,80 +451,6 @@ export const packedUserDetailedNotMeOnlySchema = { type: 'boolean', nullable: false, optional: true, }, - mutualBanners: { - type: 'array', - nullable: true, optional: false, - items: { - type: 'object', - nullable: false, optional: false, - properties: { - id: { - type: 'string', - format: 'id', - nullable: false, optional: false, - }, - user: { - type: 'object', - nullable: false, optional: false, - ref: 'UserLite', - }, - description: { - type: 'string', - nullable: true, optional: false, - }, - imgUrl: { - type: 'string', - format: 'url', - nullable: false, optional: false, - }, - url: { - type: 'string', - format: 'url', - nullable: false, optional: false, - }, - fileId: { - type: 'string', - format: 'id', - nullable: false, optional: false, - }, - }, - }, - }, - myMutualBanner: { - type: 'object', - nullable: true, optional: false, - properties: { - id: { - type: 'string', - format: 'id', - nullable: false, optional: false, - }, - user: { - type: 'object', - nullable: false, optional: false, - ref: 'UserLite', - }, - description: { - type: 'string', - nullable: true, optional: false, - }, - imgUrl: { - type: 'string', - format: 'url', - nullable: false, optional: false, - }, - url: { - type: 'string', - format: 'url', - nullable: false, optional: false, - }, - fileId: { - type: 'string', - format: 'id', - nullable: false, optional: false, - }, - }, - }, //#endregion }, } as const; @@ -787,37 +736,3 @@ export const packedUserSchema = { }, ], } as const; - -export const packedUserBannerSchema = { - type: 'object', - properties: { - id: { - type: 'string', - nullable: false, optional: false, - format: 'id', - }, - user: { - type: 'object', - nullable: false, optional: false, - ref: 'UserLite', - }, - description: { - type: 'string', - nullable: true, optional: false, - }, - imgUrl: { - type: 'string', - format: 'url', - nullable: false, optional: false, - }, - url: { - type: 'string', - nullable: true, optional: false, - }, - fileId: { - type: 'string', - format: 'id', - nullable: false, optional: false, - }, - }, -} as const; diff --git a/packages/backend/src/postgres.ts b/packages/backend/src/postgres.ts index bbf93eb3c..dfa5d86e1 100644 --- a/packages/backend/src/postgres.ts +++ b/packages/backend/src/postgres.ts @@ -83,8 +83,6 @@ import { MiFlashLike } from '@/models/FlashLike.js'; import { MiUserMemo } from '@/models/UserMemo.js'; import { MiBubbleGameRecord } from '@/models/BubbleGameRecord.js'; import { MiReversiGame } from '@/models/ReversiGame.js'; -import { MiUserBanner } from '@/models/UserBanner.js'; -import { MiUserBannerPining } from '@/models/UserBannerPining.js'; import { Config } from '@/config.js'; import { bindThis } from '@/decorators.js'; @@ -205,8 +203,6 @@ export const entities = [ MiFlash, MiFlashLike, MiUserMemo, - MiUserBanner, - MiUserBannerPining, MiBubbleGameRecord, MiReversiGame, ...charts, diff --git a/packages/backend/src/server/api/EndpointsModule.ts b/packages/backend/src/server/api/EndpointsModule.ts index 75d33b15f..c7670bd0f 100644 --- a/packages/backend/src/server/api/EndpointsModule.ts +++ b/packages/backend/src/server/api/EndpointsModule.ts @@ -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, diff --git a/packages/backend/src/server/api/endpoints.ts b/packages/backend/src/server/api/endpoints.ts index 86e417382..298796002 100644 --- a/packages/backend/src/server/api/endpoints.ts +++ b/packages/backend/src/server/api/endpoints.ts @@ -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], diff --git a/packages/backend/src/server/api/endpoints/admin/unset-user-mutual-banner.ts b/packages/backend/src/server/api/endpoints/admin/unset-user-mutual-link.ts similarity index 61% rename from packages/backend/src/server/api/endpoints/admin/unset-user-mutual-banner.ts rename to packages/backend/src/server/api/endpoints/admin/unset-user-mutual-link.ts index 139a5e020..735711c0e 100644 --- a/packages/backend/src/server/api/endpoints/admin/unset-user-mutual-banner.ts +++ b/packages/backend/src/server/api/endpoints/admin/unset-user-mutual-link.ts @@ -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 { 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, }); }); } diff --git a/packages/backend/src/server/api/endpoints/i/update.ts b/packages/backend/src/server/api/endpoints/i/update.ts index 6892660b2..4ccf94e18 100644 --- a/packages/backend/src/server/api/endpoints/i/update.ts +++ b/packages/backend/src/server/api/endpoints/i/update.ts @@ -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 { // 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 { // 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 { // 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) { diff --git a/packages/backend/src/types.ts b/packages/backend/src/types.ts index fc51d2857..8bb3ede04 100644 --- a/packages/backend/src/types.ts +++ b/packages/backend/src/types.ts @@ -96,7 +96,7 @@ export const moderationLogTypes = [ 'deleteAvatarDecoration', 'unsetUserAvatar', 'unsetUserBanner', - 'unsetUserMutualBanner', + 'unsetUserMutualLink', ] as const; export type ModerationLogPayloads = { @@ -315,12 +315,10 @@ export type ModerationLogPayloads = { userHost: string | null; fileId: string; }; - unsetUserMutualBanner: { + unsetUserMutualLink: { userId: string; userUsername: string; - userBannerDescription: string | null; - userBannerUrl: string | null; - fileId: string; + userMutualLinkSections: { name: string | null; mutualLinks: { fileId: string; description: string | null; imgSrc: string; }[]; }[] | [] } }; diff --git a/packages/backend/test/e2e/users.ts b/packages/backend/test/e2e/users.ts index 3f0f10591..2df28ada4 100644 --- a/packages/backend/test/e2e/users.ts +++ b/packages/backend/test/e2e/users.ts @@ -8,7 +8,7 @@ process.env.NODE_ENV = 'test'; import * as assert from 'assert'; import { inspect } from 'node:util'; import { DEFAULT_POLICIES } from '@/core/RoleService.js'; -import { api, post, role, signup, successfulApiCall, uploadFile, failedApiCall } from '../utils.js'; +import { api, failedApiCall, post, role, signup, successfulApiCall, uploadFile } from '../utils.js'; import type * as misskey from 'misskey-js'; describe('ユーザー', () => { @@ -74,8 +74,7 @@ describe('ユーザー', () => { lang: user.lang, fields: user.fields, verifiedLinks: user.verifiedLinks, - myMutualBanner: user.myMutualBanner, - mutualBanners: user.mutualBanners, + mutualLinkSections: user.mutualLinkSections, followersCount: user.followersCount, followingCount: user.followingCount, notesCount: user.notesCount, diff --git a/packages/frontend/src/const.ts b/packages/frontend/src/const.ts index 4ccc5b411..32b0bb353 100644 --- a/packages/frontend/src/const.ts +++ b/packages/frontend/src/const.ts @@ -108,6 +108,8 @@ export const ROLE_POLICIES = [ 'userEachUserListsLimit', 'rateLimitFactor', 'avatarDecorationLimit', + 'mutualLinkSectionLimit', + 'mutualLinkLimit', ] as const; // なんか動かない diff --git a/packages/frontend/src/pages/admin-user.vue b/packages/frontend/src/pages/admin-user.vue index 3282eb5c3..f0f45a22c 100644 --- a/packages/frontend/src/pages/admin-user.vue +++ b/packages/frontend/src/pages/admin-user.vue @@ -66,7 +66,7 @@ SPDX-License-Identifier: AGPL-3.0-only {{ i18n.ts.resetPassword }} {{ i18n.ts.unsetUserAvatar }} {{ i18n.ts.unsetUserBanner }} - {{ i18n.ts.unsetUserMutualBanner }} + {{ i18n.ts.unsetUserMutualLink }} @@ -365,10 +365,10 @@ async function unsetUserBanner() { }).then(refreshUser); } -async function unsetUserMutualBanner() { +async function unsetUserMutualLink() { const confirm = await os.confirm({ type: 'warning', - text: i18n.ts.unsetUserMutualBannerConfirm, + text: i18n.ts.unsetUserMutualLinkConfirm, }); if (confirm.canceled) return; diff --git a/packages/frontend/src/pages/admin/roles.editor.vue b/packages/frontend/src/pages/admin/roles.editor.vue index 6584819fa..cfd70ea1c 100644 --- a/packages/frontend/src/pages/admin/roles.editor.vue +++ b/packages/frontend/src/pages/admin/roles.editor.vue @@ -755,6 +755,44 @@ SPDX-License-Identifier: AGPL-3.0-only + + + +
+ + + + + + + + +
+
+ + + + +
+ + + + + + + + +
+
+ diff --git a/packages/misskey-js/etc/misskey-js.api.md b/packages/misskey-js/etc/misskey-js.api.md index e26970d38..acb3973df 100644 --- a/packages/misskey-js/etc/misskey-js.api.md +++ b/packages/misskey-js/etc/misskey-js.api.md @@ -377,7 +377,7 @@ type AdminUnsetUserAvatarRequest = operations['admin___unset-user-avatar']['requ type AdminUnsetUserBannerRequest = operations['admin___unset-user-banner']['requestBody']['content']['application/json']; // @public (undocumented) -type AdminUnsetUserMutualBannerRequest = operations['admin___unset-user-mutual-banner']['requestBody']['content']['application/json']; +type AdminUnsetUserMutualLinkRequest = operations['admin___unset-user-mutual-link']['requestBody']['content']['application/json']; // @public (undocumented) type AdminUnsuspendUserRequest = operations['admin___unsuspend-user']['requestBody']['content']['application/json']; @@ -1235,7 +1235,7 @@ declare namespace entities { AdminAvatarDecorationsUpdateRequest, AdminUnsetUserAvatarRequest, AdminUnsetUserBannerRequest, - AdminUnsetUserMutualBannerRequest, + AdminUnsetUserMutualLinkRequest, AdminDriveDeleteAllFilesOfAUserRequest, AdminDriveFilesRequest, AdminDriveFilesResponse, @@ -1787,7 +1787,6 @@ declare namespace entities { MeDetailed, UserDetailed, User, - UserBanner, UserList, UserListMembership, Ad, @@ -2759,7 +2758,7 @@ type PagesUpdateRequest = operations['pages___update']['requestBody']['content'] function parse(acct: string): Acct; // @public (undocumented) -export const permissions: readonly ["read:account", "write:account", "read:blocks", "write:blocks", "read:drive", "write:drive", "read:favorites", "write:favorites", "read:following", "write:following", "read:messaging", "write:messaging", "read:mutes", "write:mutes", "write:notes", "read:notifications", "write:notifications", "read:reactions", "write:reactions", "write:votes", "read:pages", "write:pages", "write:page-likes", "read:page-likes", "read:user-groups", "write:user-groups", "read:channels", "write:channels", "read:gallery", "write:gallery", "read:gallery-likes", "write:gallery-likes", "read:flash", "write:flash", "read:flash-likes", "write:flash-likes", "read:admin:abuse-user-reports", "read:admin:abuse-report-resolvers", "write:admin:abuse-report-resolvers", "read:admin:index-stats", "read:admin:table-stats", "read:admin:user-ips", "read:admin:meta", "write:admin:reset-password", "write:admin:resolve-abuse-user-report", "write:admin:send-email", "read:admin:server-info", "read:admin:show-moderation-log", "read:admin:show-user", "read:admin:show-users", "write:admin:suspend-user", "write:admin:unset-user-avatar", "write:admin:unset-user-banner", "write:admin:unset-user-mutual-banner", "write:admin:unsuspend-user", "write:admin:meta", "write:admin:user-note", "write:admin:roles", "read:admin:roles", "write:admin:relays", "read:admin:relays", "write:admin:invite-codes", "read:admin:invite-codes", "write:admin:announcements", "read:admin:announcements", "write:admin:avatar-decorations", "read:admin:avatar-decorations", "write:admin:federation", "write:admin:indie-auth", "read:admin:indie-auth", "write:admin:account", "read:admin:account", "write:admin:emoji", "read:admin:emoji", "write:admin:queue", "read:admin:queue", "write:admin:promo", "write:admin:drive", "read:admin:drive", "write:admin:sso", "read:admin:sso", "write:admin:ad", "read:admin:ad", "write:invite-codes", "read:invite-codes", "write:clip-favorite", "read:clip-favorite", "read:federation", "write:report-abuse"]; +export const permissions: readonly ["read:account", "write:account", "read:blocks", "write:blocks", "read:drive", "write:drive", "read:favorites", "write:favorites", "read:following", "write:following", "read:messaging", "write:messaging", "read:mutes", "write:mutes", "write:notes", "read:notifications", "write:notifications", "read:reactions", "write:reactions", "write:votes", "read:pages", "write:pages", "write:page-likes", "read:page-likes", "read:user-groups", "write:user-groups", "read:channels", "write:channels", "read:gallery", "write:gallery", "read:gallery-likes", "write:gallery-likes", "read:flash", "write:flash", "read:flash-likes", "write:flash-likes", "read:admin:abuse-user-reports", "read:admin:abuse-report-resolvers", "write:admin:abuse-report-resolvers", "read:admin:index-stats", "read:admin:table-stats", "read:admin:user-ips", "read:admin:meta", "write:admin:reset-password", "write:admin:resolve-abuse-user-report", "write:admin:send-email", "read:admin:server-info", "read:admin:show-moderation-log", "read:admin:show-user", "read:admin:show-users", "write:admin:suspend-user", "write:admin:unset-user-avatar", "write:admin:unset-user-banner", "write:admin:unset-user-mutual-link", "write:admin:unsuspend-user", "write:admin:meta", "write:admin:user-note", "write:admin:roles", "read:admin:roles", "write:admin:relays", "read:admin:relays", "write:admin:invite-codes", "read:admin:invite-codes", "write:admin:announcements", "read:admin:announcements", "write:admin:avatar-decorations", "read:admin:avatar-decorations", "write:admin:federation", "write:admin:indie-auth", "read:admin:indie-auth", "write:admin:account", "read:admin:account", "write:admin:emoji", "read:admin:emoji", "write:admin:queue", "read:admin:queue", "write:admin:promo", "write:admin:drive", "read:admin:drive", "write:admin:sso", "read:admin:sso", "write:admin:ad", "read:admin:ad", "write:invite-codes", "read:invite-codes", "write:clip-favorite", "read:clip-favorite", "read:federation", "write:report-abuse"]; // @public (undocumented) type PingResponse = operations['ping']['responses']['200']['content']['application/json']; @@ -3058,9 +3057,6 @@ function toString_2(acct: Acct): string; // @public (undocumented) type User = components['schemas']['User']; -// @public (undocumented) -type UserBanner = components['schemas']['UserBanner']; - // @public (undocumented) type UserDetailed = components['schemas']['UserDetailed']; diff --git a/packages/misskey-js/src/autogen/apiClientJSDoc.ts b/packages/misskey-js/src/autogen/apiClientJSDoc.ts index 9419a50f9..60d8d6ccf 100644 --- a/packages/misskey-js/src/autogen/apiClientJSDoc.ts +++ b/packages/misskey-js/src/autogen/apiClientJSDoc.ts @@ -281,9 +281,9 @@ declare module '../api.js' { /** * No description provided. * - * **Credential required**: *Yes* / **Permission**: *write:admin:unset-user-mutual-banner* + * **Credential required**: *Yes* / **Permission**: *write:admin:unset-user-mutual-link* */ - request( + request( endpoint: E, params: P, credential?: string | null, diff --git a/packages/misskey-js/src/autogen/endpoint.ts b/packages/misskey-js/src/autogen/endpoint.ts index a1cbb26e4..59aa29aa7 100644 --- a/packages/misskey-js/src/autogen/endpoint.ts +++ b/packages/misskey-js/src/autogen/endpoint.ts @@ -37,7 +37,7 @@ import type { AdminAvatarDecorationsUpdateRequest, AdminUnsetUserAvatarRequest, AdminUnsetUserBannerRequest, - AdminUnsetUserMutualBannerRequest, + AdminUnsetUserMutualLinkRequest, AdminDriveDeleteAllFilesOfAUserRequest, AdminDriveFilesRequest, AdminDriveFilesResponse, @@ -609,7 +609,7 @@ export type Endpoints = { 'admin/avatar-decorations/update': { req: AdminAvatarDecorationsUpdateRequest; res: EmptyResponse }; 'admin/unset-user-avatar': { req: AdminUnsetUserAvatarRequest; res: EmptyResponse }; 'admin/unset-user-banner': { req: AdminUnsetUserBannerRequest; res: EmptyResponse }; - 'admin/unset-user-mutual-banner': { req: AdminUnsetUserMutualBannerRequest; res: EmptyResponse }; + 'admin/unset-user-mutual-link': { req: AdminUnsetUserMutualLinkRequest; res: EmptyResponse }; 'admin/drive/clean-remote-files': { req: EmptyRequest; res: EmptyResponse }; 'admin/drive/cleanup': { req: EmptyRequest; res: EmptyResponse }; 'admin/drive/delete-all-files-of-a-user': { req: AdminDriveDeleteAllFilesOfAUserRequest; res: EmptyResponse }; diff --git a/packages/misskey-js/src/autogen/entities.ts b/packages/misskey-js/src/autogen/entities.ts index 24080b7d7..e9ee6a253 100644 --- a/packages/misskey-js/src/autogen/entities.ts +++ b/packages/misskey-js/src/autogen/entities.ts @@ -40,7 +40,7 @@ export type AdminAvatarDecorationsListResponse = operations['admin___avatar-deco export type AdminAvatarDecorationsUpdateRequest = operations['admin___avatar-decorations___update']['requestBody']['content']['application/json']; export type AdminUnsetUserAvatarRequest = operations['admin___unset-user-avatar']['requestBody']['content']['application/json']; export type AdminUnsetUserBannerRequest = operations['admin___unset-user-banner']['requestBody']['content']['application/json']; -export type AdminUnsetUserMutualBannerRequest = operations['admin___unset-user-mutual-banner']['requestBody']['content']['application/json']; +export type AdminUnsetUserMutualLinkRequest = operations['admin___unset-user-mutual-link']['requestBody']['content']['application/json']; export type AdminDriveDeleteAllFilesOfAUserRequest = operations['admin___drive___delete-all-files-of-a-user']['requestBody']['content']['application/json']; export type AdminDriveFilesRequest = operations['admin___drive___files']['requestBody']['content']['application/json']; export type AdminDriveFilesResponse = operations['admin___drive___files']['responses']['200']['content']['application/json']; diff --git a/packages/misskey-js/src/autogen/models.ts b/packages/misskey-js/src/autogen/models.ts index b3b2f3282..6a8eccbf4 100644 --- a/packages/misskey-js/src/autogen/models.ts +++ b/packages/misskey-js/src/autogen/models.ts @@ -7,7 +7,6 @@ export type UserDetailedNotMe = components['schemas']['UserDetailedNotMe']; export type MeDetailed = components['schemas']['MeDetailed']; export type UserDetailed = components['schemas']['UserDetailed']; export type User = components['schemas']['User']; -export type UserBanner = components['schemas']['UserBanner']; export type UserList = components['schemas']['UserList']; export type UserListMembership = components['schemas']['UserListMembership']; export type Ad = components['schemas']['Ad']; diff --git a/packages/misskey-js/src/autogen/types.ts b/packages/misskey-js/src/autogen/types.ts index 709bb5ec7..58a9b2cee 100644 --- a/packages/misskey-js/src/autogen/types.ts +++ b/packages/misskey-js/src/autogen/types.ts @@ -237,14 +237,14 @@ export type paths = { */ post: operations['admin___unset-user-banner']; }; - '/admin/unset-user-mutual-banner': { + '/admin/unset-user-mutual-link': { /** - * admin/unset-user-mutual-banner + * admin/unset-user-mutual-link * @description No description provided. * - * **Credential required**: *Yes* / **Permission**: *write:admin:unset-user-mutual-banner* + * **Credential required**: *Yes* / **Permission**: *write:admin:unset-user-mutual-link* */ - post: operations['admin___unset-user-mutual-banner']; + post: operations['admin___unset-user-mutual-link']; }; '/admin/drive/clean-remote-files': { /** @@ -3842,6 +3842,16 @@ export type components = { roles: components['schemas']['RoleLite'][]; memo: string | null; moderationNote?: string; + mutualLinkSections: ({ + name: string | null; + mutualLinks: ({ + url: string; + /** Format: misskey:id */ + fileId: string; + description: string | null; + imgSrc: string; + })[]; + })[]; isFollowing?: boolean; isFollowed?: boolean; hasPendingFollowRequestFromYou?: boolean; @@ -3853,30 +3863,6 @@ export type components = { /** @enum {string} */ notify?: 'normal' | 'none'; withReplies?: boolean; - mutualBanners: (({ - /** Format: id */ - id: string; - user: components['schemas']['UserLite']; - description: string | null; - /** Format: url */ - imgUrl: string; - /** Format: url */ - url: string; - /** Format: id */ - fileId: string; - })[]) | null; - myMutualBanner: ({ - /** Format: id */ - id: string; - user: components['schemas']['UserLite']; - description: string | null; - /** Format: url */ - imgUrl: string; - /** Format: url */ - url: string; - /** Format: id */ - fileId: string; - }) | null; }; MeDetailedOnly: { /** Format: id */ @@ -4061,17 +4047,6 @@ export type components = { MeDetailed: components['schemas']['UserLite'] & components['schemas']['UserDetailedNotMeOnly'] & components['schemas']['MeDetailedOnly']; UserDetailed: components['schemas']['UserDetailedNotMe'] | components['schemas']['MeDetailed']; User: components['schemas']['UserLite'] | components['schemas']['UserDetailed']; - UserBanner: { - /** Format: id */ - id: string; - user: components['schemas']['UserLite']; - description: string | null; - /** Format: url */ - imgUrl: string; - url: string | null; - /** Format: id */ - fileId: string; - }; UserList: { /** * Format: id @@ -4970,6 +4945,8 @@ export type components = { userEachUserListsLimit: number; rateLimitFactor: number; avatarDecorationLimit: number; + mutualLinkSectionLimit: number; + mutualLinkLimit: number; }; ReversiGameLite: { /** Format: id */ @@ -6864,12 +6841,12 @@ export type operations = { }; }; /** - * admin/unset-user-mutual-banner + * admin/unset-user-mutual-link * @description No description provided. * - * **Credential required**: *Yes* / **Permission**: *write:admin:unset-user-mutual-banner* + * **Credential required**: *Yes* / **Permission**: *write:admin:unset-user-mutual-link* */ - 'admin___unset-user-mutual-banner': { + 'admin___unset-user-mutual-link': { requestBody: { content: { 'application/json': { @@ -20308,14 +20285,16 @@ export type operations = { }; emailNotificationTypes?: string[]; alsoKnownAs?: string[]; - mutualBannerPining?: string[] | null; - myMutualBanner?: ({ - /** Format: misskey:id */ - fileId: string; - description?: string; - /** Format: url */ - url?: string | null; - }) | null; + mutualLinkSections?: ({ + name?: string | null; + mutualLinks: ({ + /** Format: url */ + url: string; + /** Format: misskey:id */ + fileId: string; + description?: string | null; + })[]; + })[]; }; }; }; diff --git a/packages/misskey-js/src/consts.ts b/packages/misskey-js/src/consts.ts index 7a9a4a938..abc03ca5c 100644 --- a/packages/misskey-js/src/consts.ts +++ b/packages/misskey-js/src/consts.ts @@ -62,7 +62,7 @@ export const permissions = [ 'write:admin:suspend-user', 'write:admin:unset-user-avatar', 'write:admin:unset-user-banner', - 'write:admin:unset-user-mutual-banner', + 'write:admin:unset-user-mutual-link', 'write:admin:unsuspend-user', 'write:admin:meta', 'write:admin:user-note', @@ -364,11 +364,9 @@ export type ModerationLogPayloads = { userHost: string | null; fileId: string; }; - unsetUserMutualBanner: { + unsetUserMutualLink: { userId: string; userUsername: string; - userBannerDescription: string | null; - userBannerUrl: string; - fileId: string; + mutualLinkSections: string; }; };