mirror of
https://github.com/misskey-dev/misskey.git
synced 2024-12-27 08:10:24 +01:00
feat: Avatar decoration (#12096)
* wip * Update ja-JP.yml * Update profile.vue * .js * Update home.test.ts
This commit is contained in:
parent
101e5d622d
commit
2c0a139da6
38 changed files with 888 additions and 19 deletions
4
locales/index.d.ts
vendored
4
locales/index.d.ts
vendored
|
@ -1142,6 +1142,7 @@ export interface Locale {
|
||||||
"privacyPolicy": string;
|
"privacyPolicy": string;
|
||||||
"privacyPolicyUrl": string;
|
"privacyPolicyUrl": string;
|
||||||
"tosAndPrivacyPolicy": string;
|
"tosAndPrivacyPolicy": string;
|
||||||
|
"avatarDecorations": string;
|
||||||
"_announcement": {
|
"_announcement": {
|
||||||
"forExistingUsers": string;
|
"forExistingUsers": string;
|
||||||
"forExistingUsersDescription": string;
|
"forExistingUsersDescription": string;
|
||||||
|
@ -2295,6 +2296,9 @@ export interface Locale {
|
||||||
"createAd": string;
|
"createAd": string;
|
||||||
"deleteAd": string;
|
"deleteAd": string;
|
||||||
"updateAd": string;
|
"updateAd": string;
|
||||||
|
"createAvatarDecoration": string;
|
||||||
|
"updateAvatarDecoration": string;
|
||||||
|
"deleteAvatarDecoration": string;
|
||||||
};
|
};
|
||||||
"_fileViewer": {
|
"_fileViewer": {
|
||||||
"title": string;
|
"title": string;
|
||||||
|
|
|
@ -1139,6 +1139,7 @@ impressumDescription: "ドイツなどの一部の国と地域では表示が義
|
||||||
privacyPolicy: "プライバシーポリシー"
|
privacyPolicy: "プライバシーポリシー"
|
||||||
privacyPolicyUrl: "プライバシーポリシーURL"
|
privacyPolicyUrl: "プライバシーポリシーURL"
|
||||||
tosAndPrivacyPolicy: "利用規約・プライバシーポリシー"
|
tosAndPrivacyPolicy: "利用規約・プライバシーポリシー"
|
||||||
|
avatarDecorations: "アイコンデコレーション"
|
||||||
|
|
||||||
_announcement:
|
_announcement:
|
||||||
forExistingUsers: "既存ユーザーのみ"
|
forExistingUsers: "既存ユーザーのみ"
|
||||||
|
@ -2208,6 +2209,9 @@ _moderationLogTypes:
|
||||||
createAd: "広告を作成"
|
createAd: "広告を作成"
|
||||||
deleteAd: "広告を削除"
|
deleteAd: "広告を削除"
|
||||||
updateAd: "広告を更新"
|
updateAd: "広告を更新"
|
||||||
|
createAvatarDecoration: "アイコンデコレーションを作成"
|
||||||
|
updateAvatarDecoration: "アイコンデコレーションを更新"
|
||||||
|
deleteAvatarDecoration: "アイコンデコレーションを削除"
|
||||||
|
|
||||||
_fileViewer:
|
_fileViewer:
|
||||||
title: "ファイルの詳細"
|
title: "ファイルの詳細"
|
||||||
|
|
|
@ -0,0 +1,18 @@
|
||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: syuilo and other misskey contributors
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
*/
|
||||||
|
|
||||||
|
export class AvatarDecoration1697847397844 {
|
||||||
|
name = 'AvatarDecoration1697847397844'
|
||||||
|
|
||||||
|
async up(queryRunner) {
|
||||||
|
await queryRunner.query(`CREATE TABLE "avatar_decoration" ("id" character varying(32) NOT NULL, "updatedAt" TIMESTAMP WITH TIME ZONE, "url" character varying(1024) NOT NULL, "name" character varying(256) NOT NULL, "description" character varying(2048) NOT NULL, "roleIdsThatCanBeUsedThisDecoration" character varying(128) array NOT NULL DEFAULT '{}', CONSTRAINT "PK_b6de9296f6097078e1dc53f7603" PRIMARY KEY ("id"))`);
|
||||||
|
await queryRunner.query(`ALTER TABLE "user" ADD "avatarDecorations" character varying(512) array NOT NULL DEFAULT '{}'`);
|
||||||
|
}
|
||||||
|
|
||||||
|
async down(queryRunner) {
|
||||||
|
await queryRunner.query(`ALTER TABLE "user" DROP COLUMN "avatarDecorations"`);
|
||||||
|
await queryRunner.query(`DROP TABLE "avatar_decoration"`);
|
||||||
|
}
|
||||||
|
}
|
129
packages/backend/src/core/AvatarDecorationService.ts
Normal file
129
packages/backend/src/core/AvatarDecorationService.ts
Normal file
|
@ -0,0 +1,129 @@
|
||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: syuilo and other misskey contributors
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { Inject, Injectable, OnApplicationShutdown } from '@nestjs/common';
|
||||||
|
import * as Redis from 'ioredis';
|
||||||
|
import type { AvatarDecorationsRepository, MiAvatarDecoration, MiUser } from '@/models/_.js';
|
||||||
|
import { IdService } from '@/core/IdService.js';
|
||||||
|
import { GlobalEventService } from '@/core/GlobalEventService.js';
|
||||||
|
import { DI } from '@/di-symbols.js';
|
||||||
|
import { bindThis } from '@/decorators.js';
|
||||||
|
import { MemorySingleCache } from '@/misc/cache.js';
|
||||||
|
import type { GlobalEvents } from '@/core/GlobalEventService.js';
|
||||||
|
import { ModerationLogService } from '@/core/ModerationLogService.js';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class AvatarDecorationService implements OnApplicationShutdown {
|
||||||
|
public cache: MemorySingleCache<MiAvatarDecoration[]>;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
@Inject(DI.redisForSub)
|
||||||
|
private redisForSub: Redis.Redis,
|
||||||
|
|
||||||
|
@Inject(DI.avatarDecorationsRepository)
|
||||||
|
private avatarDecorationsRepository: AvatarDecorationsRepository,
|
||||||
|
|
||||||
|
private idService: IdService,
|
||||||
|
private moderationLogService: ModerationLogService,
|
||||||
|
private globalEventService: GlobalEventService,
|
||||||
|
) {
|
||||||
|
this.cache = new MemorySingleCache<MiAvatarDecoration[]>(1000 * 60 * 30);
|
||||||
|
|
||||||
|
this.redisForSub.on('message', this.onMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
@bindThis
|
||||||
|
private async onMessage(_: string, data: string): Promise<void> {
|
||||||
|
const obj = JSON.parse(data);
|
||||||
|
|
||||||
|
if (obj.channel === 'internal') {
|
||||||
|
const { type, body } = obj.message as GlobalEvents['internal']['payload'];
|
||||||
|
switch (type) {
|
||||||
|
case 'avatarDecorationCreated':
|
||||||
|
case 'avatarDecorationUpdated':
|
||||||
|
case 'avatarDecorationDeleted': {
|
||||||
|
this.cache.delete();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@bindThis
|
||||||
|
public async create(options: Partial<MiAvatarDecoration>, moderator?: MiUser): Promise<MiAvatarDecoration> {
|
||||||
|
const created = await this.avatarDecorationsRepository.insert({
|
||||||
|
id: this.idService.gen(),
|
||||||
|
...options,
|
||||||
|
}).then(x => this.avatarDecorationsRepository.findOneByOrFail(x.identifiers[0]));
|
||||||
|
|
||||||
|
this.globalEventService.publishInternalEvent('avatarDecorationCreated', created);
|
||||||
|
|
||||||
|
if (moderator) {
|
||||||
|
this.moderationLogService.log(moderator, 'createAvatarDecoration', {
|
||||||
|
avatarDecorationId: created.id,
|
||||||
|
avatarDecoration: created,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return created;
|
||||||
|
}
|
||||||
|
|
||||||
|
@bindThis
|
||||||
|
public async update(id: MiAvatarDecoration['id'], params: Partial<MiAvatarDecoration>, moderator?: MiUser): Promise<void> {
|
||||||
|
const avatarDecoration = await this.avatarDecorationsRepository.findOneByOrFail({ id });
|
||||||
|
|
||||||
|
const date = new Date();
|
||||||
|
await this.avatarDecorationsRepository.update(avatarDecoration.id, {
|
||||||
|
updatedAt: date,
|
||||||
|
...params,
|
||||||
|
});
|
||||||
|
|
||||||
|
const updated = await this.avatarDecorationsRepository.findOneByOrFail({ id: avatarDecoration.id });
|
||||||
|
this.globalEventService.publishInternalEvent('avatarDecorationUpdated', updated);
|
||||||
|
|
||||||
|
if (moderator) {
|
||||||
|
this.moderationLogService.log(moderator, 'updateAvatarDecoration', {
|
||||||
|
avatarDecorationId: avatarDecoration.id,
|
||||||
|
before: avatarDecoration,
|
||||||
|
after: updated,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@bindThis
|
||||||
|
public async delete(id: MiAvatarDecoration['id'], moderator?: MiUser): Promise<void> {
|
||||||
|
const avatarDecoration = await this.avatarDecorationsRepository.findOneByOrFail({ id });
|
||||||
|
|
||||||
|
await this.avatarDecorationsRepository.delete({ id: avatarDecoration.id });
|
||||||
|
this.globalEventService.publishInternalEvent('avatarDecorationDeleted', avatarDecoration);
|
||||||
|
|
||||||
|
if (moderator) {
|
||||||
|
this.moderationLogService.log(moderator, 'deleteAvatarDecoration', {
|
||||||
|
avatarDecorationId: avatarDecoration.id,
|
||||||
|
avatarDecoration: avatarDecoration,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@bindThis
|
||||||
|
public async getAll(noCache = false): Promise<MiAvatarDecoration[]> {
|
||||||
|
if (noCache) {
|
||||||
|
this.cache.delete();
|
||||||
|
}
|
||||||
|
return this.cache.fetch(() => this.avatarDecorationsRepository.find());
|
||||||
|
}
|
||||||
|
|
||||||
|
@bindThis
|
||||||
|
public dispose(): void {
|
||||||
|
this.redisForSub.off('message', this.onMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
@bindThis
|
||||||
|
public onApplicationShutdown(signal?: string | undefined): void {
|
||||||
|
this.dispose();
|
||||||
|
}
|
||||||
|
}
|
|
@ -11,6 +11,7 @@ import { AnnouncementService } from './AnnouncementService.js';
|
||||||
import { AntennaService } from './AntennaService.js';
|
import { AntennaService } from './AntennaService.js';
|
||||||
import { AppLockService } from './AppLockService.js';
|
import { AppLockService } from './AppLockService.js';
|
||||||
import { AchievementService } from './AchievementService.js';
|
import { AchievementService } from './AchievementService.js';
|
||||||
|
import { AvatarDecorationService } from './AvatarDecorationService.js';
|
||||||
import { CaptchaService } from './CaptchaService.js';
|
import { CaptchaService } from './CaptchaService.js';
|
||||||
import { CreateSystemUserService } from './CreateSystemUserService.js';
|
import { CreateSystemUserService } from './CreateSystemUserService.js';
|
||||||
import { CustomEmojiService } from './CustomEmojiService.js';
|
import { CustomEmojiService } from './CustomEmojiService.js';
|
||||||
|
@ -140,6 +141,7 @@ const $AnnouncementService: Provider = { provide: 'AnnouncementService', useExis
|
||||||
const $AntennaService: Provider = { provide: 'AntennaService', useExisting: AntennaService };
|
const $AntennaService: Provider = { provide: 'AntennaService', useExisting: AntennaService };
|
||||||
const $AppLockService: Provider = { provide: 'AppLockService', useExisting: AppLockService };
|
const $AppLockService: Provider = { provide: 'AppLockService', useExisting: AppLockService };
|
||||||
const $AchievementService: Provider = { provide: 'AchievementService', useExisting: AchievementService };
|
const $AchievementService: Provider = { provide: 'AchievementService', useExisting: AchievementService };
|
||||||
|
const $AvatarDecorationService: Provider = { provide: 'AvatarDecorationService', useExisting: AvatarDecorationService };
|
||||||
const $CaptchaService: Provider = { provide: 'CaptchaService', useExisting: CaptchaService };
|
const $CaptchaService: Provider = { provide: 'CaptchaService', useExisting: CaptchaService };
|
||||||
const $CreateSystemUserService: Provider = { provide: 'CreateSystemUserService', useExisting: CreateSystemUserService };
|
const $CreateSystemUserService: Provider = { provide: 'CreateSystemUserService', useExisting: CreateSystemUserService };
|
||||||
const $CustomEmojiService: Provider = { provide: 'CustomEmojiService', useExisting: CustomEmojiService };
|
const $CustomEmojiService: Provider = { provide: 'CustomEmojiService', useExisting: CustomEmojiService };
|
||||||
|
@ -273,6 +275,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
|
||||||
AntennaService,
|
AntennaService,
|
||||||
AppLockService,
|
AppLockService,
|
||||||
AchievementService,
|
AchievementService,
|
||||||
|
AvatarDecorationService,
|
||||||
CaptchaService,
|
CaptchaService,
|
||||||
CreateSystemUserService,
|
CreateSystemUserService,
|
||||||
CustomEmojiService,
|
CustomEmojiService,
|
||||||
|
@ -399,6 +402,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
|
||||||
$AntennaService,
|
$AntennaService,
|
||||||
$AppLockService,
|
$AppLockService,
|
||||||
$AchievementService,
|
$AchievementService,
|
||||||
|
$AvatarDecorationService,
|
||||||
$CaptchaService,
|
$CaptchaService,
|
||||||
$CreateSystemUserService,
|
$CreateSystemUserService,
|
||||||
$CustomEmojiService,
|
$CustomEmojiService,
|
||||||
|
@ -526,6 +530,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
|
||||||
AntennaService,
|
AntennaService,
|
||||||
AppLockService,
|
AppLockService,
|
||||||
AchievementService,
|
AchievementService,
|
||||||
|
AvatarDecorationService,
|
||||||
CaptchaService,
|
CaptchaService,
|
||||||
CreateSystemUserService,
|
CreateSystemUserService,
|
||||||
CustomEmojiService,
|
CustomEmojiService,
|
||||||
|
@ -651,6 +656,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
|
||||||
$AntennaService,
|
$AntennaService,
|
||||||
$AppLockService,
|
$AppLockService,
|
||||||
$AchievementService,
|
$AchievementService,
|
||||||
|
$AvatarDecorationService,
|
||||||
$CaptchaService,
|
$CaptchaService,
|
||||||
$CreateSystemUserService,
|
$CreateSystemUserService,
|
||||||
$CustomEmojiService,
|
$CustomEmojiService,
|
||||||
|
|
|
@ -18,7 +18,7 @@ import type { MiSignin } from '@/models/Signin.js';
|
||||||
import type { MiPage } from '@/models/Page.js';
|
import type { MiPage } from '@/models/Page.js';
|
||||||
import type { MiWebhook } from '@/models/Webhook.js';
|
import type { MiWebhook } from '@/models/Webhook.js';
|
||||||
import type { MiMeta } from '@/models/Meta.js';
|
import type { MiMeta } from '@/models/Meta.js';
|
||||||
import { MiRole, MiRoleAssignment } from '@/models/_.js';
|
import { MiAvatarDecoration, MiRole, MiRoleAssignment } from '@/models/_.js';
|
||||||
import type { Packed } from '@/misc/json-schema.js';
|
import type { Packed } from '@/misc/json-schema.js';
|
||||||
import { DI } from '@/di-symbols.js';
|
import { DI } from '@/di-symbols.js';
|
||||||
import type { Config } from '@/config.js';
|
import type { Config } from '@/config.js';
|
||||||
|
@ -188,6 +188,9 @@ export interface InternalEventTypes {
|
||||||
antennaCreated: MiAntenna;
|
antennaCreated: MiAntenna;
|
||||||
antennaDeleted: MiAntenna;
|
antennaDeleted: MiAntenna;
|
||||||
antennaUpdated: MiAntenna;
|
antennaUpdated: MiAntenna;
|
||||||
|
avatarDecorationCreated: MiAvatarDecoration;
|
||||||
|
avatarDecorationDeleted: MiAvatarDecoration;
|
||||||
|
avatarDecorationUpdated: MiAvatarDecoration;
|
||||||
metaUpdated: MiMeta;
|
metaUpdated: MiMeta;
|
||||||
followChannel: { userId: MiUser['id']; channelId: MiChannel['id']; };
|
followChannel: { userId: MiUser['id']; channelId: MiChannel['id']; };
|
||||||
unfollowChannel: { userId: MiUser['id']; channelId: MiChannel['id']; };
|
unfollowChannel: { userId: MiUser['id']; channelId: MiChannel['id']; };
|
||||||
|
|
|
@ -227,6 +227,12 @@ export class RoleService implements OnApplicationShutdown {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@bindThis
|
||||||
|
public async getRoles() {
|
||||||
|
const roles = await this.rolesCache.fetch(() => this.rolesRepository.findBy({}));
|
||||||
|
return roles;
|
||||||
|
}
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
public async getUserAssigns(userId: MiUser['id']) {
|
public async getUserAssigns(userId: MiUser['id']) {
|
||||||
const now = Date.now();
|
const now = Date.now();
|
||||||
|
|
|
@ -21,9 +21,10 @@ import { RoleService } from '@/core/RoleService.js';
|
||||||
import { ApPersonService } from '@/core/activitypub/models/ApPersonService.js';
|
import { ApPersonService } from '@/core/activitypub/models/ApPersonService.js';
|
||||||
import { FederatedInstanceService } from '@/core/FederatedInstanceService.js';
|
import { FederatedInstanceService } from '@/core/FederatedInstanceService.js';
|
||||||
import { IdService } from '@/core/IdService.js';
|
import { IdService } from '@/core/IdService.js';
|
||||||
|
import type { AnnouncementService } from '@/core/AnnouncementService.js';
|
||||||
|
import type { CustomEmojiService } from '@/core/CustomEmojiService.js';
|
||||||
|
import { AvatarDecorationService } from '@/core/AvatarDecorationService.js';
|
||||||
import type { OnModuleInit } from '@nestjs/common';
|
import type { OnModuleInit } from '@nestjs/common';
|
||||||
import type { AnnouncementService } from '../AnnouncementService.js';
|
|
||||||
import type { CustomEmojiService } from '../CustomEmojiService.js';
|
|
||||||
import type { NoteEntityService } from './NoteEntityService.js';
|
import type { NoteEntityService } from './NoteEntityService.js';
|
||||||
import type { DriveFileEntityService } from './DriveFileEntityService.js';
|
import type { DriveFileEntityService } from './DriveFileEntityService.js';
|
||||||
import type { PageEntityService } from './PageEntityService.js';
|
import type { PageEntityService } from './PageEntityService.js';
|
||||||
|
@ -62,6 +63,7 @@ export class UserEntityService implements OnModuleInit {
|
||||||
private roleService: RoleService;
|
private roleService: RoleService;
|
||||||
private federatedInstanceService: FederatedInstanceService;
|
private federatedInstanceService: FederatedInstanceService;
|
||||||
private idService: IdService;
|
private idService: IdService;
|
||||||
|
private avatarDecorationService: AvatarDecorationService;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private moduleRef: ModuleRef,
|
private moduleRef: ModuleRef,
|
||||||
|
@ -126,6 +128,7 @@ export class UserEntityService implements OnModuleInit {
|
||||||
this.roleService = this.moduleRef.get('RoleService');
|
this.roleService = this.moduleRef.get('RoleService');
|
||||||
this.federatedInstanceService = this.moduleRef.get('FederatedInstanceService');
|
this.federatedInstanceService = this.moduleRef.get('FederatedInstanceService');
|
||||||
this.idService = this.moduleRef.get('IdService');
|
this.idService = this.moduleRef.get('IdService');
|
||||||
|
this.avatarDecorationService = this.moduleRef.get('AvatarDecorationService');
|
||||||
}
|
}
|
||||||
|
|
||||||
//#region Validators
|
//#region Validators
|
||||||
|
@ -328,8 +331,6 @@ export class UserEntityService implements OnModuleInit {
|
||||||
...announcement,
|
...announcement,
|
||||||
})) : null;
|
})) : null;
|
||||||
|
|
||||||
const falsy = opts.detail ? false : undefined;
|
|
||||||
|
|
||||||
const packed = {
|
const packed = {
|
||||||
id: user.id,
|
id: user.id,
|
||||||
name: user.name,
|
name: user.name,
|
||||||
|
@ -337,6 +338,10 @@ export class UserEntityService implements OnModuleInit {
|
||||||
host: user.host,
|
host: user.host,
|
||||||
avatarUrl: user.avatarUrl ?? this.getIdenticonUrl(user),
|
avatarUrl: user.avatarUrl ?? this.getIdenticonUrl(user),
|
||||||
avatarBlurhash: user.avatarBlurhash,
|
avatarBlurhash: user.avatarBlurhash,
|
||||||
|
avatarDecorations: user.avatarDecorations.length > 0 ? this.avatarDecorationService.getAll().then(decorations => decorations.filter(decoration => user.avatarDecorations.includes(decoration.id)).map(decoration => ({
|
||||||
|
id: decoration.id,
|
||||||
|
url: decoration.url,
|
||||||
|
}))) : [],
|
||||||
isBot: user.isBot,
|
isBot: user.isBot,
|
||||||
isCat: user.isCat,
|
isCat: user.isCat,
|
||||||
instance: user.host ? this.federatedInstanceService.federatedInstanceCache.fetch(user.host).then(instance => instance ? {
|
instance: user.host ? this.federatedInstanceService.federatedInstanceCache.fetch(user.host).then(instance => instance ? {
|
||||||
|
|
|
@ -18,6 +18,7 @@ export const DI = {
|
||||||
announcementsRepository: Symbol('announcementsRepository'),
|
announcementsRepository: Symbol('announcementsRepository'),
|
||||||
announcementReadsRepository: Symbol('announcementReadsRepository'),
|
announcementReadsRepository: Symbol('announcementReadsRepository'),
|
||||||
appsRepository: Symbol('appsRepository'),
|
appsRepository: Symbol('appsRepository'),
|
||||||
|
avatarDecorationsRepository: Symbol('avatarDecorationsRepository'),
|
||||||
noteFavoritesRepository: Symbol('noteFavoritesRepository'),
|
noteFavoritesRepository: Symbol('noteFavoritesRepository'),
|
||||||
noteThreadMutingsRepository: Symbol('noteThreadMutingsRepository'),
|
noteThreadMutingsRepository: Symbol('noteThreadMutingsRepository'),
|
||||||
noteReactionsRepository: Symbol('noteReactionsRepository'),
|
noteReactionsRepository: Symbol('noteReactionsRepository'),
|
||||||
|
|
39
packages/backend/src/models/AvatarDecoration.ts
Normal file
39
packages/backend/src/models/AvatarDecoration.ts
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: syuilo and other misskey contributors
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { Entity, PrimaryColumn, Index, Column, ManyToOne, JoinColumn } from 'typeorm';
|
||||||
|
import { id } from './util/id.js';
|
||||||
|
|
||||||
|
@Entity('avatar_decoration')
|
||||||
|
export class MiAvatarDecoration {
|
||||||
|
@PrimaryColumn(id())
|
||||||
|
public id: string;
|
||||||
|
|
||||||
|
@Column('timestamp with time zone', {
|
||||||
|
nullable: true,
|
||||||
|
})
|
||||||
|
public updatedAt: Date | null;
|
||||||
|
|
||||||
|
@Column('varchar', {
|
||||||
|
length: 1024,
|
||||||
|
})
|
||||||
|
public url: string;
|
||||||
|
|
||||||
|
@Column('varchar', {
|
||||||
|
length: 256,
|
||||||
|
})
|
||||||
|
public name: string;
|
||||||
|
|
||||||
|
@Column('varchar', {
|
||||||
|
length: 2048,
|
||||||
|
})
|
||||||
|
public description: string;
|
||||||
|
|
||||||
|
// TODO: 定期ジョブで存在しなくなったロールIDを除去するようにする
|
||||||
|
@Column('varchar', {
|
||||||
|
array: true, length: 128, default: '{}',
|
||||||
|
})
|
||||||
|
public roleIdsThatCanBeUsedThisDecoration: string[];
|
||||||
|
}
|
|
@ -5,7 +5,7 @@
|
||||||
|
|
||||||
import { Module } from '@nestjs/common';
|
import { Module } from '@nestjs/common';
|
||||||
import { DI } from '@/di-symbols.js';
|
import { DI } from '@/di-symbols.js';
|
||||||
import { MiAbuseUserReport, MiAccessToken, MiAd, MiAnnouncement, MiAnnouncementRead, MiAntenna, MiApp, MiAuthSession, MiBlocking, MiChannel, MiChannelFavorite, MiChannelFollowing, MiClip, MiClipFavorite, MiClipNote, MiDriveFile, MiDriveFolder, MiEmoji, MiFlash, MiFlashLike, MiFollowRequest, MiFollowing, MiGalleryLike, MiGalleryPost, MiHashtag, MiInstance, MiMeta, MiModerationLog, MiMuting, MiNote, MiNoteFavorite, MiNoteReaction, MiNoteThreadMuting, MiNoteUnread, MiPage, MiPageLike, MiPasswordResetRequest, MiPoll, MiPollVote, MiPromoNote, MiPromoRead, MiRegistrationTicket, MiRegistryItem, MiRelay, MiRenoteMuting, MiRetentionAggregation, MiRole, MiRoleAssignment, MiSignin, MiSwSubscription, MiUsedUsername, MiUser, MiUserIp, MiUserKeypair, MiUserList, MiUserListFavorite, MiUserListMembership, MiUserMemo, MiUserNotePining, MiUserPending, MiUserProfile, MiUserPublickey, MiUserSecurityKey, MiWebhook } from './_.js';
|
import { MiAbuseUserReport, MiAccessToken, MiAd, MiAnnouncement, MiAnnouncementRead, MiAntenna, MiApp, MiAuthSession, MiAvatarDecoration, MiBlocking, MiChannel, MiChannelFavorite, MiChannelFollowing, MiClip, MiClipFavorite, MiClipNote, MiDriveFile, MiDriveFolder, MiEmoji, MiFlash, MiFlashLike, MiFollowRequest, MiFollowing, MiGalleryLike, MiGalleryPost, MiHashtag, MiInstance, MiMeta, MiModerationLog, MiMuting, MiNote, MiNoteFavorite, MiNoteReaction, MiNoteThreadMuting, MiNoteUnread, MiPage, MiPageLike, MiPasswordResetRequest, MiPoll, MiPollVote, MiPromoNote, MiPromoRead, MiRegistrationTicket, MiRegistryItem, MiRelay, MiRenoteMuting, MiRetentionAggregation, MiRole, MiRoleAssignment, MiSignin, MiSwSubscription, MiUsedUsername, MiUser, MiUserIp, MiUserKeypair, MiUserList, MiUserListFavorite, MiUserListMembership, MiUserMemo, MiUserNotePining, MiUserPending, MiUserProfile, MiUserPublickey, MiUserSecurityKey, MiWebhook } from './_.js';
|
||||||
import type { DataSource } from 'typeorm';
|
import type { DataSource } from 'typeorm';
|
||||||
import type { Provider } from '@nestjs/common';
|
import type { Provider } from '@nestjs/common';
|
||||||
|
|
||||||
|
@ -39,6 +39,12 @@ const $appsRepository: Provider = {
|
||||||
inject: [DI.db],
|
inject: [DI.db],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const $avatarDecorationsRepository: Provider = {
|
||||||
|
provide: DI.avatarDecorationsRepository,
|
||||||
|
useFactory: (db: DataSource) => db.getRepository(MiAvatarDecoration),
|
||||||
|
inject: [DI.db],
|
||||||
|
};
|
||||||
|
|
||||||
const $noteFavoritesRepository: Provider = {
|
const $noteFavoritesRepository: Provider = {
|
||||||
provide: DI.noteFavoritesRepository,
|
provide: DI.noteFavoritesRepository,
|
||||||
useFactory: (db: DataSource) => db.getRepository(MiNoteFavorite),
|
useFactory: (db: DataSource) => db.getRepository(MiNoteFavorite),
|
||||||
|
@ -402,6 +408,7 @@ const $userMemosRepository: Provider = {
|
||||||
$announcementsRepository,
|
$announcementsRepository,
|
||||||
$announcementReadsRepository,
|
$announcementReadsRepository,
|
||||||
$appsRepository,
|
$appsRepository,
|
||||||
|
$avatarDecorationsRepository,
|
||||||
$noteFavoritesRepository,
|
$noteFavoritesRepository,
|
||||||
$noteThreadMutingsRepository,
|
$noteThreadMutingsRepository,
|
||||||
$noteReactionsRepository,
|
$noteReactionsRepository,
|
||||||
|
@ -468,6 +475,7 @@ const $userMemosRepository: Provider = {
|
||||||
$announcementsRepository,
|
$announcementsRepository,
|
||||||
$announcementReadsRepository,
|
$announcementReadsRepository,
|
||||||
$appsRepository,
|
$appsRepository,
|
||||||
|
$avatarDecorationsRepository,
|
||||||
$noteFavoritesRepository,
|
$noteFavoritesRepository,
|
||||||
$noteThreadMutingsRepository,
|
$noteThreadMutingsRepository,
|
||||||
$noteReactionsRepository,
|
$noteReactionsRepository,
|
||||||
|
|
|
@ -138,6 +138,11 @@ export class MiUser {
|
||||||
})
|
})
|
||||||
public bannerBlurhash: string | null;
|
public bannerBlurhash: string | null;
|
||||||
|
|
||||||
|
@Column('varchar', {
|
||||||
|
length: 512, array: true, default: '{}',
|
||||||
|
})
|
||||||
|
public avatarDecorations: string[];
|
||||||
|
|
||||||
@Index()
|
@Index()
|
||||||
@Column('varchar', {
|
@Column('varchar', {
|
||||||
length: 128, array: true, default: '{}',
|
length: 128, array: true, default: '{}',
|
||||||
|
|
|
@ -10,6 +10,7 @@ import { MiAnnouncement } from '@/models/Announcement.js';
|
||||||
import { MiAnnouncementRead } from '@/models/AnnouncementRead.js';
|
import { MiAnnouncementRead } from '@/models/AnnouncementRead.js';
|
||||||
import { MiAntenna } from '@/models/Antenna.js';
|
import { MiAntenna } from '@/models/Antenna.js';
|
||||||
import { MiApp } from '@/models/App.js';
|
import { MiApp } from '@/models/App.js';
|
||||||
|
import { MiAvatarDecoration } from '@/models/AvatarDecoration.js';
|
||||||
import { MiAuthSession } from '@/models/AuthSession.js';
|
import { MiAuthSession } from '@/models/AuthSession.js';
|
||||||
import { MiBlocking } from '@/models/Blocking.js';
|
import { MiBlocking } from '@/models/Blocking.js';
|
||||||
import { MiChannelFollowing } from '@/models/ChannelFollowing.js';
|
import { MiChannelFollowing } from '@/models/ChannelFollowing.js';
|
||||||
|
@ -77,6 +78,7 @@ export {
|
||||||
MiAnnouncementRead,
|
MiAnnouncementRead,
|
||||||
MiAntenna,
|
MiAntenna,
|
||||||
MiApp,
|
MiApp,
|
||||||
|
MiAvatarDecoration,
|
||||||
MiAuthSession,
|
MiAuthSession,
|
||||||
MiBlocking,
|
MiBlocking,
|
||||||
MiChannelFollowing,
|
MiChannelFollowing,
|
||||||
|
@ -143,6 +145,7 @@ export type AnnouncementsRepository = Repository<MiAnnouncement>;
|
||||||
export type AnnouncementReadsRepository = Repository<MiAnnouncementRead>;
|
export type AnnouncementReadsRepository = Repository<MiAnnouncementRead>;
|
||||||
export type AntennasRepository = Repository<MiAntenna>;
|
export type AntennasRepository = Repository<MiAntenna>;
|
||||||
export type AppsRepository = Repository<MiApp>;
|
export type AppsRepository = Repository<MiApp>;
|
||||||
|
export type AvatarDecorationsRepository = Repository<MiAvatarDecoration>;
|
||||||
export type AuthSessionsRepository = Repository<MiAuthSession>;
|
export type AuthSessionsRepository = Repository<MiAuthSession>;
|
||||||
export type BlockingsRepository = Repository<MiBlocking>;
|
export type BlockingsRepository = Repository<MiBlocking>;
|
||||||
export type ChannelFollowingsRepository = Repository<MiChannelFollowing>;
|
export type ChannelFollowingsRepository = Repository<MiChannelFollowing>;
|
||||||
|
|
|
@ -37,6 +37,26 @@ export const packedUserLiteSchema = {
|
||||||
type: 'string',
|
type: 'string',
|
||||||
nullable: true, optional: false,
|
nullable: true, optional: false,
|
||||||
},
|
},
|
||||||
|
avatarDecorations: {
|
||||||
|
type: 'array',
|
||||||
|
nullable: false, optional: false,
|
||||||
|
items: {
|
||||||
|
type: 'object',
|
||||||
|
nullable: false, optional: false,
|
||||||
|
properties: {
|
||||||
|
id: {
|
||||||
|
type: 'string',
|
||||||
|
nullable: false, optional: false,
|
||||||
|
format: 'id',
|
||||||
|
},
|
||||||
|
url: {
|
||||||
|
type: 'string',
|
||||||
|
format: 'url',
|
||||||
|
nullable: false, optional: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
isAdmin: {
|
isAdmin: {
|
||||||
type: 'boolean',
|
type: 'boolean',
|
||||||
nullable: false, optional: true,
|
nullable: false, optional: true,
|
||||||
|
|
|
@ -18,6 +18,7 @@ import { MiAnnouncement } from '@/models/Announcement.js';
|
||||||
import { MiAnnouncementRead } from '@/models/AnnouncementRead.js';
|
import { MiAnnouncementRead } from '@/models/AnnouncementRead.js';
|
||||||
import { MiAntenna } from '@/models/Antenna.js';
|
import { MiAntenna } from '@/models/Antenna.js';
|
||||||
import { MiApp } from '@/models/App.js';
|
import { MiApp } from '@/models/App.js';
|
||||||
|
import { MiAvatarDecoration } from '@/models/AvatarDecoration.js';
|
||||||
import { MiAuthSession } from '@/models/AuthSession.js';
|
import { MiAuthSession } from '@/models/AuthSession.js';
|
||||||
import { MiBlocking } from '@/models/Blocking.js';
|
import { MiBlocking } from '@/models/Blocking.js';
|
||||||
import { MiChannelFollowing } from '@/models/ChannelFollowing.js';
|
import { MiChannelFollowing } from '@/models/ChannelFollowing.js';
|
||||||
|
@ -129,6 +130,7 @@ export const entities = [
|
||||||
MiMeta,
|
MiMeta,
|
||||||
MiInstance,
|
MiInstance,
|
||||||
MiApp,
|
MiApp,
|
||||||
|
MiAvatarDecoration,
|
||||||
MiAuthSession,
|
MiAuthSession,
|
||||||
MiAccessToken,
|
MiAccessToken,
|
||||||
MiUser,
|
MiUser,
|
||||||
|
|
|
@ -18,6 +18,10 @@ import * as ep___admin_announcements_create from './endpoints/admin/announcement
|
||||||
import * as ep___admin_announcements_delete from './endpoints/admin/announcements/delete.js';
|
import * as ep___admin_announcements_delete from './endpoints/admin/announcements/delete.js';
|
||||||
import * as ep___admin_announcements_list from './endpoints/admin/announcements/list.js';
|
import * as ep___admin_announcements_list from './endpoints/admin/announcements/list.js';
|
||||||
import * as ep___admin_announcements_update from './endpoints/admin/announcements/update.js';
|
import * as ep___admin_announcements_update from './endpoints/admin/announcements/update.js';
|
||||||
|
import * as ep___admin_avatarDecorations_create from './endpoints/admin/avatar-decorations/create.js';
|
||||||
|
import * as ep___admin_avatarDecorations_delete from './endpoints/admin/avatar-decorations/delete.js';
|
||||||
|
import * as ep___admin_avatarDecorations_list from './endpoints/admin/avatar-decorations/list.js';
|
||||||
|
import * as ep___admin_avatarDecorations_update from './endpoints/admin/avatar-decorations/update.js';
|
||||||
import * as ep___admin_deleteAllFilesOfAUser from './endpoints/admin/delete-all-files-of-a-user.js';
|
import * as ep___admin_deleteAllFilesOfAUser from './endpoints/admin/delete-all-files-of-a-user.js';
|
||||||
import * as ep___admin_drive_cleanRemoteFiles from './endpoints/admin/drive/clean-remote-files.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_cleanup from './endpoints/admin/drive/cleanup.js';
|
||||||
|
@ -176,6 +180,7 @@ import * as ep___gallery_posts_show from './endpoints/gallery/posts/show.js';
|
||||||
import * as ep___gallery_posts_unlike from './endpoints/gallery/posts/unlike.js';
|
import * as ep___gallery_posts_unlike from './endpoints/gallery/posts/unlike.js';
|
||||||
import * as ep___gallery_posts_update from './endpoints/gallery/posts/update.js';
|
import * as ep___gallery_posts_update from './endpoints/gallery/posts/update.js';
|
||||||
import * as ep___getOnlineUsersCount from './endpoints/get-online-users-count.js';
|
import * as ep___getOnlineUsersCount from './endpoints/get-online-users-count.js';
|
||||||
|
import * as ep___getAvatarDecorations from './endpoints/get-avatar-decorations.js';
|
||||||
import * as ep___hashtags_list from './endpoints/hashtags/list.js';
|
import * as ep___hashtags_list from './endpoints/hashtags/list.js';
|
||||||
import * as ep___hashtags_search from './endpoints/hashtags/search.js';
|
import * as ep___hashtags_search from './endpoints/hashtags/search.js';
|
||||||
import * as ep___hashtags_show from './endpoints/hashtags/show.js';
|
import * as ep___hashtags_show from './endpoints/hashtags/show.js';
|
||||||
|
@ -368,6 +373,10 @@ const $admin_announcements_create: Provider = { provide: 'ep:admin/announcements
|
||||||
const $admin_announcements_delete: Provider = { provide: 'ep:admin/announcements/delete', useClass: ep___admin_announcements_delete.default };
|
const $admin_announcements_delete: Provider = { provide: 'ep:admin/announcements/delete', useClass: ep___admin_announcements_delete.default };
|
||||||
const $admin_announcements_list: Provider = { provide: 'ep:admin/announcements/list', useClass: ep___admin_announcements_list.default };
|
const $admin_announcements_list: Provider = { provide: 'ep:admin/announcements/list', useClass: ep___admin_announcements_list.default };
|
||||||
const $admin_announcements_update: Provider = { provide: 'ep:admin/announcements/update', useClass: ep___admin_announcements_update.default };
|
const $admin_announcements_update: Provider = { provide: 'ep:admin/announcements/update', useClass: ep___admin_announcements_update.default };
|
||||||
|
const $admin_avatarDecorations_create: Provider = { provide: 'ep:admin/avatar-decorations/create', useClass: ep___admin_avatarDecorations_create.default };
|
||||||
|
const $admin_avatarDecorations_delete: Provider = { provide: 'ep:admin/avatar-decorations/delete', useClass: ep___admin_avatarDecorations_delete.default };
|
||||||
|
const $admin_avatarDecorations_list: Provider = { provide: 'ep:admin/avatar-decorations/list', useClass: ep___admin_avatarDecorations_list.default };
|
||||||
|
const $admin_avatarDecorations_update: Provider = { provide: 'ep:admin/avatar-decorations/update', useClass: ep___admin_avatarDecorations_update.default };
|
||||||
const $admin_deleteAllFilesOfAUser: Provider = { provide: 'ep:admin/delete-all-files-of-a-user', useClass: ep___admin_deleteAllFilesOfAUser.default };
|
const $admin_deleteAllFilesOfAUser: Provider = { provide: 'ep:admin/delete-all-files-of-a-user', useClass: ep___admin_deleteAllFilesOfAUser.default };
|
||||||
const $admin_drive_cleanRemoteFiles: Provider = { provide: 'ep:admin/drive/clean-remote-files', useClass: ep___admin_drive_cleanRemoteFiles.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_cleanup: Provider = { provide: 'ep:admin/drive/cleanup', useClass: ep___admin_drive_cleanup.default };
|
||||||
|
@ -526,6 +535,7 @@ const $gallery_posts_show: Provider = { provide: 'ep:gallery/posts/show', useCla
|
||||||
const $gallery_posts_unlike: Provider = { provide: 'ep:gallery/posts/unlike', useClass: ep___gallery_posts_unlike.default };
|
const $gallery_posts_unlike: Provider = { provide: 'ep:gallery/posts/unlike', useClass: ep___gallery_posts_unlike.default };
|
||||||
const $gallery_posts_update: Provider = { provide: 'ep:gallery/posts/update', useClass: ep___gallery_posts_update.default };
|
const $gallery_posts_update: Provider = { provide: 'ep:gallery/posts/update', useClass: ep___gallery_posts_update.default };
|
||||||
const $getOnlineUsersCount: Provider = { provide: 'ep:get-online-users-count', useClass: ep___getOnlineUsersCount.default };
|
const $getOnlineUsersCount: Provider = { provide: 'ep:get-online-users-count', useClass: ep___getOnlineUsersCount.default };
|
||||||
|
const $getAvatarDecorations: Provider = { provide: 'ep:get-avatar-decorations', useClass: ep___getAvatarDecorations.default };
|
||||||
const $hashtags_list: Provider = { provide: 'ep:hashtags/list', useClass: ep___hashtags_list.default };
|
const $hashtags_list: Provider = { provide: 'ep:hashtags/list', useClass: ep___hashtags_list.default };
|
||||||
const $hashtags_search: Provider = { provide: 'ep:hashtags/search', useClass: ep___hashtags_search.default };
|
const $hashtags_search: Provider = { provide: 'ep:hashtags/search', useClass: ep___hashtags_search.default };
|
||||||
const $hashtags_show: Provider = { provide: 'ep:hashtags/show', useClass: ep___hashtags_show.default };
|
const $hashtags_show: Provider = { provide: 'ep:hashtags/show', useClass: ep___hashtags_show.default };
|
||||||
|
@ -722,6 +732,10 @@ const $retention: Provider = { provide: 'ep:retention', useClass: ep___retention
|
||||||
$admin_announcements_delete,
|
$admin_announcements_delete,
|
||||||
$admin_announcements_list,
|
$admin_announcements_list,
|
||||||
$admin_announcements_update,
|
$admin_announcements_update,
|
||||||
|
$admin_avatarDecorations_create,
|
||||||
|
$admin_avatarDecorations_delete,
|
||||||
|
$admin_avatarDecorations_list,
|
||||||
|
$admin_avatarDecorations_update,
|
||||||
$admin_deleteAllFilesOfAUser,
|
$admin_deleteAllFilesOfAUser,
|
||||||
$admin_drive_cleanRemoteFiles,
|
$admin_drive_cleanRemoteFiles,
|
||||||
$admin_drive_cleanup,
|
$admin_drive_cleanup,
|
||||||
|
@ -880,6 +894,7 @@ const $retention: Provider = { provide: 'ep:retention', useClass: ep___retention
|
||||||
$gallery_posts_unlike,
|
$gallery_posts_unlike,
|
||||||
$gallery_posts_update,
|
$gallery_posts_update,
|
||||||
$getOnlineUsersCount,
|
$getOnlineUsersCount,
|
||||||
|
$getAvatarDecorations,
|
||||||
$hashtags_list,
|
$hashtags_list,
|
||||||
$hashtags_search,
|
$hashtags_search,
|
||||||
$hashtags_show,
|
$hashtags_show,
|
||||||
|
@ -1070,6 +1085,10 @@ const $retention: Provider = { provide: 'ep:retention', useClass: ep___retention
|
||||||
$admin_announcements_delete,
|
$admin_announcements_delete,
|
||||||
$admin_announcements_list,
|
$admin_announcements_list,
|
||||||
$admin_announcements_update,
|
$admin_announcements_update,
|
||||||
|
$admin_avatarDecorations_create,
|
||||||
|
$admin_avatarDecorations_delete,
|
||||||
|
$admin_avatarDecorations_list,
|
||||||
|
$admin_avatarDecorations_update,
|
||||||
$admin_deleteAllFilesOfAUser,
|
$admin_deleteAllFilesOfAUser,
|
||||||
$admin_drive_cleanRemoteFiles,
|
$admin_drive_cleanRemoteFiles,
|
||||||
$admin_drive_cleanup,
|
$admin_drive_cleanup,
|
||||||
|
@ -1228,6 +1247,7 @@ const $retention: Provider = { provide: 'ep:retention', useClass: ep___retention
|
||||||
$gallery_posts_unlike,
|
$gallery_posts_unlike,
|
||||||
$gallery_posts_update,
|
$gallery_posts_update,
|
||||||
$getOnlineUsersCount,
|
$getOnlineUsersCount,
|
||||||
|
$getAvatarDecorations,
|
||||||
$hashtags_list,
|
$hashtags_list,
|
||||||
$hashtags_search,
|
$hashtags_search,
|
||||||
$hashtags_show,
|
$hashtags_show,
|
||||||
|
|
|
@ -18,6 +18,10 @@ import * as ep___admin_announcements_create from './endpoints/admin/announcement
|
||||||
import * as ep___admin_announcements_delete from './endpoints/admin/announcements/delete.js';
|
import * as ep___admin_announcements_delete from './endpoints/admin/announcements/delete.js';
|
||||||
import * as ep___admin_announcements_list from './endpoints/admin/announcements/list.js';
|
import * as ep___admin_announcements_list from './endpoints/admin/announcements/list.js';
|
||||||
import * as ep___admin_announcements_update from './endpoints/admin/announcements/update.js';
|
import * as ep___admin_announcements_update from './endpoints/admin/announcements/update.js';
|
||||||
|
import * as ep___admin_avatarDecorations_create from './endpoints/admin/avatar-decorations/create.js';
|
||||||
|
import * as ep___admin_avatarDecorations_delete from './endpoints/admin/avatar-decorations/delete.js';
|
||||||
|
import * as ep___admin_avatarDecorations_list from './endpoints/admin/avatar-decorations/list.js';
|
||||||
|
import * as ep___admin_avatarDecorations_update from './endpoints/admin/avatar-decorations/update.js';
|
||||||
import * as ep___admin_deleteAllFilesOfAUser from './endpoints/admin/delete-all-files-of-a-user.js';
|
import * as ep___admin_deleteAllFilesOfAUser from './endpoints/admin/delete-all-files-of-a-user.js';
|
||||||
import * as ep___admin_drive_cleanRemoteFiles from './endpoints/admin/drive/clean-remote-files.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_cleanup from './endpoints/admin/drive/cleanup.js';
|
||||||
|
@ -176,6 +180,7 @@ import * as ep___gallery_posts_show from './endpoints/gallery/posts/show.js';
|
||||||
import * as ep___gallery_posts_unlike from './endpoints/gallery/posts/unlike.js';
|
import * as ep___gallery_posts_unlike from './endpoints/gallery/posts/unlike.js';
|
||||||
import * as ep___gallery_posts_update from './endpoints/gallery/posts/update.js';
|
import * as ep___gallery_posts_update from './endpoints/gallery/posts/update.js';
|
||||||
import * as ep___getOnlineUsersCount from './endpoints/get-online-users-count.js';
|
import * as ep___getOnlineUsersCount from './endpoints/get-online-users-count.js';
|
||||||
|
import * as ep___getAvatarDecorations from './endpoints/get-avatar-decorations.js';
|
||||||
import * as ep___hashtags_list from './endpoints/hashtags/list.js';
|
import * as ep___hashtags_list from './endpoints/hashtags/list.js';
|
||||||
import * as ep___hashtags_search from './endpoints/hashtags/search.js';
|
import * as ep___hashtags_search from './endpoints/hashtags/search.js';
|
||||||
import * as ep___hashtags_show from './endpoints/hashtags/show.js';
|
import * as ep___hashtags_show from './endpoints/hashtags/show.js';
|
||||||
|
@ -366,6 +371,10 @@ const eps = [
|
||||||
['admin/announcements/delete', ep___admin_announcements_delete],
|
['admin/announcements/delete', ep___admin_announcements_delete],
|
||||||
['admin/announcements/list', ep___admin_announcements_list],
|
['admin/announcements/list', ep___admin_announcements_list],
|
||||||
['admin/announcements/update', ep___admin_announcements_update],
|
['admin/announcements/update', ep___admin_announcements_update],
|
||||||
|
['admin/avatar-decorations/create', ep___admin_avatarDecorations_create],
|
||||||
|
['admin/avatar-decorations/delete', ep___admin_avatarDecorations_delete],
|
||||||
|
['admin/avatar-decorations/list', ep___admin_avatarDecorations_list],
|
||||||
|
['admin/avatar-decorations/update', ep___admin_avatarDecorations_update],
|
||||||
['admin/delete-all-files-of-a-user', ep___admin_deleteAllFilesOfAUser],
|
['admin/delete-all-files-of-a-user', ep___admin_deleteAllFilesOfAUser],
|
||||||
['admin/drive/clean-remote-files', ep___admin_drive_cleanRemoteFiles],
|
['admin/drive/clean-remote-files', ep___admin_drive_cleanRemoteFiles],
|
||||||
['admin/drive/cleanup', ep___admin_drive_cleanup],
|
['admin/drive/cleanup', ep___admin_drive_cleanup],
|
||||||
|
@ -524,6 +533,7 @@ const eps = [
|
||||||
['gallery/posts/unlike', ep___gallery_posts_unlike],
|
['gallery/posts/unlike', ep___gallery_posts_unlike],
|
||||||
['gallery/posts/update', ep___gallery_posts_update],
|
['gallery/posts/update', ep___gallery_posts_update],
|
||||||
['get-online-users-count', ep___getOnlineUsersCount],
|
['get-online-users-count', ep___getOnlineUsersCount],
|
||||||
|
['get-avatar-decorations', ep___getAvatarDecorations],
|
||||||
['hashtags/list', ep___hashtags_list],
|
['hashtags/list', ep___hashtags_list],
|
||||||
['hashtags/search', ep___hashtags_search],
|
['hashtags/search', ep___hashtags_search],
|
||||||
['hashtags/show', ep___hashtags_show],
|
['hashtags/show', ep___hashtags_show],
|
||||||
|
|
|
@ -0,0 +1,44 @@
|
||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: syuilo and other misskey contributors
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { Injectable } from '@nestjs/common';
|
||||||
|
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||||
|
import { AvatarDecorationService } from '@/core/AvatarDecorationService.js';
|
||||||
|
|
||||||
|
export const meta = {
|
||||||
|
tags: ['admin'],
|
||||||
|
|
||||||
|
requireCredential: true,
|
||||||
|
requireModerator: true,
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
export const paramDef = {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
name: { type: 'string', minLength: 1 },
|
||||||
|
description: { type: 'string' },
|
||||||
|
url: { type: 'string', minLength: 1 },
|
||||||
|
roleIdsThatCanBeUsedThisDecoration: { type: 'array', items: {
|
||||||
|
type: 'string',
|
||||||
|
} },
|
||||||
|
},
|
||||||
|
required: ['name', 'description', 'url'],
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
|
||||||
|
constructor(
|
||||||
|
private avatarDecorationService: AvatarDecorationService,
|
||||||
|
) {
|
||||||
|
super(meta, paramDef, async (ps, me) => {
|
||||||
|
await this.avatarDecorationService.create({
|
||||||
|
name: ps.name,
|
||||||
|
description: ps.description,
|
||||||
|
url: ps.url,
|
||||||
|
roleIdsThatCanBeUsedThisDecoration: ps.roleIdsThatCanBeUsedThisDecoration,
|
||||||
|
}, me);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,39 @@
|
||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: syuilo and other misskey contributors
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { Inject, Injectable } from '@nestjs/common';
|
||||||
|
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||||
|
import { DI } from '@/di-symbols.js';
|
||||||
|
import { AvatarDecorationService } from '@/core/AvatarDecorationService.js';
|
||||||
|
import { ApiError } from '../../../error.js';
|
||||||
|
|
||||||
|
export const meta = {
|
||||||
|
tags: ['admin'],
|
||||||
|
|
||||||
|
requireCredential: true,
|
||||||
|
requireModerator: true,
|
||||||
|
|
||||||
|
errors: {
|
||||||
|
},
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
export const paramDef = {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
id: { type: 'string', format: 'misskey:id' },
|
||||||
|
},
|
||||||
|
required: ['id'],
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
|
||||||
|
constructor(
|
||||||
|
private avatarDecorationService: AvatarDecorationService,
|
||||||
|
) {
|
||||||
|
super(meta, paramDef, async (ps, me) => {
|
||||||
|
await this.avatarDecorationService.delete(ps.id, me);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,101 @@
|
||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: syuilo and other misskey contributors
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { Inject, Injectable } from '@nestjs/common';
|
||||||
|
import type { AnnouncementsRepository, AnnouncementReadsRepository } from '@/models/_.js';
|
||||||
|
import type { MiAnnouncement } from '@/models/Announcement.js';
|
||||||
|
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||||
|
import { QueryService } from '@/core/QueryService.js';
|
||||||
|
import { DI } from '@/di-symbols.js';
|
||||||
|
import { IdService } from '@/core/IdService.js';
|
||||||
|
import { AvatarDecorationService } from '@/core/AvatarDecorationService.js';
|
||||||
|
|
||||||
|
export const meta = {
|
||||||
|
tags: ['admin'],
|
||||||
|
|
||||||
|
requireCredential: true,
|
||||||
|
requireModerator: true,
|
||||||
|
|
||||||
|
res: {
|
||||||
|
type: 'array',
|
||||||
|
optional: false, nullable: false,
|
||||||
|
items: {
|
||||||
|
type: 'object',
|
||||||
|
optional: false, nullable: false,
|
||||||
|
properties: {
|
||||||
|
id: {
|
||||||
|
type: 'string',
|
||||||
|
optional: false, nullable: false,
|
||||||
|
format: 'id',
|
||||||
|
example: 'xxxxxxxxxx',
|
||||||
|
},
|
||||||
|
createdAt: {
|
||||||
|
type: 'string',
|
||||||
|
optional: false, nullable: false,
|
||||||
|
format: 'date-time',
|
||||||
|
},
|
||||||
|
updatedAt: {
|
||||||
|
type: 'string',
|
||||||
|
optional: false, nullable: true,
|
||||||
|
format: 'date-time',
|
||||||
|
},
|
||||||
|
name: {
|
||||||
|
type: 'string',
|
||||||
|
optional: false, nullable: false,
|
||||||
|
},
|
||||||
|
description: {
|
||||||
|
type: 'string',
|
||||||
|
optional: false, nullable: false,
|
||||||
|
},
|
||||||
|
url: {
|
||||||
|
type: 'string',
|
||||||
|
optional: false, nullable: false,
|
||||||
|
},
|
||||||
|
roleIdsThatCanBeUsedThisDecoration: {
|
||||||
|
type: 'array',
|
||||||
|
optional: false, nullable: false,
|
||||||
|
items: {
|
||||||
|
type: 'string',
|
||||||
|
optional: false, nullable: false,
|
||||||
|
format: 'id',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
export const paramDef = {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 },
|
||||||
|
sinceId: { type: 'string', format: 'misskey:id' },
|
||||||
|
untilId: { type: 'string', format: 'misskey:id' },
|
||||||
|
userId: { type: 'string', format: 'misskey:id', nullable: true },
|
||||||
|
},
|
||||||
|
required: [],
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
|
||||||
|
constructor(
|
||||||
|
private avatarDecorationService: AvatarDecorationService,
|
||||||
|
private idService: IdService,
|
||||||
|
) {
|
||||||
|
super(meta, paramDef, async (ps, me) => {
|
||||||
|
const avatarDecorations = await this.avatarDecorationService.getAll(true);
|
||||||
|
|
||||||
|
return avatarDecorations.map(avatarDecoration => ({
|
||||||
|
id: avatarDecoration.id,
|
||||||
|
createdAt: this.idService.parse(avatarDecoration.id).date.toISOString(),
|
||||||
|
updatedAt: avatarDecoration.updatedAt?.toISOString() ?? null,
|
||||||
|
name: avatarDecoration.name,
|
||||||
|
description: avatarDecoration.description,
|
||||||
|
url: avatarDecoration.url,
|
||||||
|
roleIdsThatCanBeUsedThisDecoration: avatarDecoration.roleIdsThatCanBeUsedThisDecoration,
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,50 @@
|
||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: syuilo and other misskey contributors
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { Inject, Injectable } from '@nestjs/common';
|
||||||
|
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||||
|
import { DI } from '@/di-symbols.js';
|
||||||
|
import { AvatarDecorationService } from '@/core/AvatarDecorationService.js';
|
||||||
|
import { ApiError } from '../../../error.js';
|
||||||
|
|
||||||
|
export const meta = {
|
||||||
|
tags: ['admin'],
|
||||||
|
|
||||||
|
requireCredential: true,
|
||||||
|
requireModerator: true,
|
||||||
|
|
||||||
|
errors: {
|
||||||
|
},
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
export const paramDef = {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
id: { type: 'string', format: 'misskey:id' },
|
||||||
|
name: { type: 'string', minLength: 1 },
|
||||||
|
description: { type: 'string' },
|
||||||
|
url: { type: 'string', minLength: 1 },
|
||||||
|
roleIdsThatCanBeUsedThisDecoration: { type: 'array', items: {
|
||||||
|
type: 'string',
|
||||||
|
} },
|
||||||
|
},
|
||||||
|
required: ['id'],
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
|
||||||
|
constructor(
|
||||||
|
private avatarDecorationService: AvatarDecorationService,
|
||||||
|
) {
|
||||||
|
super(meta, paramDef, async (ps, me) => {
|
||||||
|
await this.avatarDecorationService.update(ps.id, {
|
||||||
|
name: ps.name,
|
||||||
|
description: ps.description,
|
||||||
|
url: ps.url,
|
||||||
|
roleIdsThatCanBeUsedThisDecoration: ps.roleIdsThatCanBeUsedThisDecoration,
|
||||||
|
}, me);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,79 @@
|
||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: syuilo and other misskey contributors
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { IsNull } from 'typeorm';
|
||||||
|
import { Inject, Injectable } from '@nestjs/common';
|
||||||
|
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||||
|
import { DI } from '@/di-symbols.js';
|
||||||
|
import { AvatarDecorationService } from '@/core/AvatarDecorationService.js';
|
||||||
|
|
||||||
|
export const meta = {
|
||||||
|
tags: ['users'],
|
||||||
|
|
||||||
|
requireCredential: false,
|
||||||
|
|
||||||
|
res: {
|
||||||
|
type: 'array',
|
||||||
|
optional: false, nullable: false,
|
||||||
|
items: {
|
||||||
|
type: 'object',
|
||||||
|
optional: false, nullable: false,
|
||||||
|
properties: {
|
||||||
|
id: {
|
||||||
|
type: 'string',
|
||||||
|
optional: false, nullable: false,
|
||||||
|
format: 'id',
|
||||||
|
example: 'xxxxxxxxxx',
|
||||||
|
},
|
||||||
|
name: {
|
||||||
|
type: 'string',
|
||||||
|
optional: false, nullable: false,
|
||||||
|
},
|
||||||
|
description: {
|
||||||
|
type: 'string',
|
||||||
|
optional: false, nullable: false,
|
||||||
|
},
|
||||||
|
url: {
|
||||||
|
type: 'string',
|
||||||
|
optional: false, nullable: false,
|
||||||
|
},
|
||||||
|
roleIdsThatCanBeUsedThisDecoration: {
|
||||||
|
type: 'array',
|
||||||
|
optional: false, nullable: false,
|
||||||
|
items: {
|
||||||
|
type: 'string',
|
||||||
|
optional: false, nullable: false,
|
||||||
|
format: 'id',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
export const paramDef = {
|
||||||
|
type: 'object',
|
||||||
|
properties: {},
|
||||||
|
required: [],
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
|
||||||
|
constructor(
|
||||||
|
private avatarDecorationService: AvatarDecorationService,
|
||||||
|
) {
|
||||||
|
super(meta, paramDef, async (ps, me) => {
|
||||||
|
const decorations = await this.avatarDecorationService.getAll(true);
|
||||||
|
|
||||||
|
return decorations.map(decoration => ({
|
||||||
|
id: decoration.id,
|
||||||
|
name: decoration.name,
|
||||||
|
description: decoration.description,
|
||||||
|
url: decoration.url,
|
||||||
|
roleIdsThatCanBeUsedThisDecoration: decoration.roleIdsThatCanBeUsedThisDecoration,
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -32,6 +32,7 @@ import { DriveFileEntityService } from '@/core/entities/DriveFileEntityService.j
|
||||||
import { HttpRequestService } from '@/core/HttpRequestService.js';
|
import { HttpRequestService } from '@/core/HttpRequestService.js';
|
||||||
import type { Config } from '@/config.js';
|
import type { Config } from '@/config.js';
|
||||||
import { safeForSql } from '@/misc/safe-for-sql.js';
|
import { safeForSql } from '@/misc/safe-for-sql.js';
|
||||||
|
import { AvatarDecorationService } from '@/core/AvatarDecorationService.js';
|
||||||
import { ApiLoggerService } from '../../ApiLoggerService.js';
|
import { ApiLoggerService } from '../../ApiLoggerService.js';
|
||||||
import { ApiError } from '../../error.js';
|
import { ApiError } from '../../error.js';
|
||||||
|
|
||||||
|
@ -131,6 +132,9 @@ export const paramDef = {
|
||||||
birthday: { ...birthdaySchema, nullable: true },
|
birthday: { ...birthdaySchema, nullable: true },
|
||||||
lang: { type: 'string', enum: [null, ...Object.keys(langmap)] as string[], nullable: true },
|
lang: { type: 'string', enum: [null, ...Object.keys(langmap)] as string[], nullable: true },
|
||||||
avatarId: { type: 'string', format: 'misskey:id', nullable: true },
|
avatarId: { type: 'string', format: 'misskey:id', nullable: true },
|
||||||
|
avatarDecorations: { type: 'array', maxItems: 1, items: {
|
||||||
|
type: 'string',
|
||||||
|
} },
|
||||||
bannerId: { type: 'string', format: 'misskey:id', nullable: true },
|
bannerId: { type: 'string', format: 'misskey:id', nullable: true },
|
||||||
fields: {
|
fields: {
|
||||||
type: 'array',
|
type: 'array',
|
||||||
|
@ -207,6 +211,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||||
private roleService: RoleService,
|
private roleService: RoleService,
|
||||||
private cacheService: CacheService,
|
private cacheService: CacheService,
|
||||||
private httpRequestService: HttpRequestService,
|
private httpRequestService: HttpRequestService,
|
||||||
|
private avatarDecorationService: AvatarDecorationService,
|
||||||
) {
|
) {
|
||||||
super(meta, paramDef, async (ps, _user, token) => {
|
super(meta, paramDef, async (ps, _user, token) => {
|
||||||
const user = await this.usersRepository.findOneByOrFail({ id: _user.id }) as MiLocalUser;
|
const user = await this.usersRepository.findOneByOrFail({ id: _user.id }) as MiLocalUser;
|
||||||
|
@ -296,6 +301,17 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||||
updates.bannerBlurhash = null;
|
updates.bannerBlurhash = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (ps.avatarDecorations) {
|
||||||
|
const decorations = await this.avatarDecorationService.getAll(true);
|
||||||
|
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);
|
||||||
|
|
||||||
|
updates.avatarDecorations = ps.avatarDecorations.filter(id => decorationIds.includes(id));
|
||||||
|
}
|
||||||
|
|
||||||
if (ps.pinnedPageId) {
|
if (ps.pinnedPageId) {
|
||||||
const page = await this.pagesRepository.findOneBy({ id: ps.pinnedPageId });
|
const page = await this.pagesRepository.findOneBy({ id: ps.pinnedPageId });
|
||||||
|
|
||||||
|
|
|
@ -60,6 +60,9 @@ export const moderationLogTypes = [
|
||||||
'createAd',
|
'createAd',
|
||||||
'updateAd',
|
'updateAd',
|
||||||
'deleteAd',
|
'deleteAd',
|
||||||
|
'createAvatarDecoration',
|
||||||
|
'updateAvatarDecoration',
|
||||||
|
'deleteAvatarDecoration',
|
||||||
] as const;
|
] as const;
|
||||||
|
|
||||||
export type ModerationLogPayloads = {
|
export type ModerationLogPayloads = {
|
||||||
|
@ -221,6 +224,19 @@ export type ModerationLogPayloads = {
|
||||||
adId: string;
|
adId: string;
|
||||||
ad: any;
|
ad: any;
|
||||||
};
|
};
|
||||||
|
createAvatarDecoration: {
|
||||||
|
avatarDecorationId: string;
|
||||||
|
avatarDecoration: any;
|
||||||
|
};
|
||||||
|
updateAvatarDecoration: {
|
||||||
|
avatarDecorationId: string;
|
||||||
|
before: any;
|
||||||
|
after: any;
|
||||||
|
};
|
||||||
|
deleteAvatarDecoration: {
|
||||||
|
avatarDecorationId: string;
|
||||||
|
avatarDecoration: any;
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export type Serialized<T> = {
|
export type Serialized<T> = {
|
||||||
|
|
|
@ -68,6 +68,7 @@ describe('ユーザー', () => {
|
||||||
host: user.host,
|
host: user.host,
|
||||||
avatarUrl: user.avatarUrl,
|
avatarUrl: user.avatarUrl,
|
||||||
avatarBlurhash: user.avatarBlurhash,
|
avatarBlurhash: user.avatarBlurhash,
|
||||||
|
avatarDecorations: user.avatarDecorations,
|
||||||
isBot: user.isBot,
|
isBot: user.isBot,
|
||||||
isCat: user.isCat,
|
isCat: user.isCat,
|
||||||
instance: user.instance,
|
instance: user.instance,
|
||||||
|
@ -349,6 +350,7 @@ describe('ユーザー', () => {
|
||||||
assert.strictEqual(response.host, null);
|
assert.strictEqual(response.host, null);
|
||||||
assert.match(response.avatarUrl, /^[-a-zA-Z0-9@:%._\+~#&?=\/]+$/);
|
assert.match(response.avatarUrl, /^[-a-zA-Z0-9@:%._\+~#&?=\/]+$/);
|
||||||
assert.strictEqual(response.avatarBlurhash, null);
|
assert.strictEqual(response.avatarBlurhash, null);
|
||||||
|
assert.deepStrictEqual(response.avatarDecorations, []);
|
||||||
assert.strictEqual(response.isBot, false);
|
assert.strictEqual(response.isBot, false);
|
||||||
assert.strictEqual(response.isCat, false);
|
assert.strictEqual(response.isCat, false);
|
||||||
assert.strictEqual(response.instance, undefined);
|
assert.strictEqual(response.instance, undefined);
|
||||||
|
|
|
@ -74,6 +74,7 @@ export function userDetailed(id = 'someuserid', username = 'miskist', host = 'mi
|
||||||
onlineStatus: 'unknown',
|
onlineStatus: 'unknown',
|
||||||
avatarUrl: 'https://github.com/misskey-dev/misskey/blob/master/packages/frontend/assets/about-icon.png?raw=true',
|
avatarUrl: 'https://github.com/misskey-dev/misskey/blob/master/packages/frontend/assets/about-icon.png?raw=true',
|
||||||
avatarBlurhash: 'eQFRshof5NWBRi},juayfPju53WB?0ofs;s*a{ofjuay^SoMEJR%ay',
|
avatarBlurhash: 'eQFRshof5NWBRi},juayfPju53WB?0ofs;s*a{ofjuay^SoMEJR%ay',
|
||||||
|
avatarDecorations: [],
|
||||||
emojis: [],
|
emojis: [],
|
||||||
bannerBlurhash: 'eQA^IW^-MH8w9tE8I=S^o{$*R4RikXtSxutRozjEnNR.RQadoyozog',
|
bannerBlurhash: 'eQA^IW^-MH8w9tE8I=S^o{$*R4RikXtSxutRozjEnNR.RQadoyozog',
|
||||||
bannerColor: '#000000',
|
bannerColor: '#000000',
|
||||||
|
|
|
@ -5,7 +5,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<component :is="link ? MkA : 'span'" v-user-preview="preview ? user.id : undefined" v-bind="bound" class="_noSelect" :class="[$style.root, { [$style.animation]: animation, [$style.cat]: user.isCat, [$style.square]: squareAvatars }]" :style="{ color }" :title="acct(user)" @click="onClick">
|
<component :is="link ? MkA : 'span'" v-user-preview="preview ? user.id : undefined" v-bind="bound" class="_noSelect" :class="[$style.root, { [$style.animation]: animation, [$style.cat]: user.isCat, [$style.square]: squareAvatars }]" :style="{ color }" :title="acct(user)" @click="onClick">
|
||||||
<MkImgWithBlurhash :class="$style.inner" :src="url" :hash="user?.avatarBlurhash" :cover="true" :onlyAvgColor="true"/>
|
<MkImgWithBlurhash :class="$style.inner" :src="url" :hash="user.avatarBlurhash" :cover="true" :onlyAvgColor="true"/>
|
||||||
<MkUserOnlineIndicator v-if="indicator" :class="$style.indicator" :user="user"/>
|
<MkUserOnlineIndicator v-if="indicator" :class="$style.indicator" :user="user"/>
|
||||||
<div v-if="user.isCat" :class="[$style.ears]">
|
<div v-if="user.isCat" :class="[$style.ears]">
|
||||||
<div :class="$style.earLeft">
|
<div :class="$style.earLeft">
|
||||||
|
@ -23,6 +23,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<img v-if="decoration || user.avatarDecorations.length > 0" :class="[$style.decoration]" :src="decoration ?? user.avatarDecorations[0].url" alt="">
|
||||||
</component>
|
</component>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
@ -47,6 +48,7 @@ const props = withDefaults(defineProps<{
|
||||||
link?: boolean;
|
link?: boolean;
|
||||||
preview?: boolean;
|
preview?: boolean;
|
||||||
indicator?: boolean;
|
indicator?: boolean;
|
||||||
|
decoration?: string;
|
||||||
}>(), {
|
}>(), {
|
||||||
target: null,
|
target: null,
|
||||||
link: false,
|
link: false,
|
||||||
|
@ -134,7 +136,7 @@ watch(() => props.user.avatarBlurhash, () => {
|
||||||
|
|
||||||
.indicator {
|
.indicator {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
z-index: 1;
|
z-index: 2;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
width: 20%;
|
width: 20%;
|
||||||
|
@ -278,4 +280,13 @@ watch(() => props.user.avatarBlurhash, () => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.decoration {
|
||||||
|
position: absolute;
|
||||||
|
z-index: 1;
|
||||||
|
top: -50%;
|
||||||
|
left: -50%;
|
||||||
|
width: 200%;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
103
packages/frontend/src/pages/admin/avatar-decorations.vue
Normal file
103
packages/frontend/src/pages/admin/avatar-decorations.vue
Normal file
|
@ -0,0 +1,103 @@
|
||||||
|
<!--
|
||||||
|
SPDX-FileCopyrightText: syuilo and other misskey contributors
|
||||||
|
SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
-->
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<MkStickyContainer>
|
||||||
|
<template #header><XHeader :actions="headerActions" :tabs="headerTabs"/></template>
|
||||||
|
<MkSpacer :contentMax="900">
|
||||||
|
<div class="_gaps">
|
||||||
|
<MkFolder v-for="avatarDecoration in avatarDecorations" :key="avatarDecoration.id ?? avatarDecoration._id" :defaultOpen="avatarDecoration.id == null">
|
||||||
|
<template #label>{{ avatarDecoration.name }}</template>
|
||||||
|
<template #caption>{{ avatarDecoration.description }}</template>
|
||||||
|
|
||||||
|
<div class="_gaps_m">
|
||||||
|
<MkInput v-model="avatarDecoration.name">
|
||||||
|
<template #label>{{ i18n.ts.name }}</template>
|
||||||
|
</MkInput>
|
||||||
|
<MkTextarea v-model="avatarDecoration.description">
|
||||||
|
<template #label>{{ i18n.ts.description }}</template>
|
||||||
|
</MkTextarea>
|
||||||
|
<MkInput v-model="avatarDecoration.url">
|
||||||
|
<template #label>{{ i18n.ts.imageUrl }}</template>
|
||||||
|
</MkInput>
|
||||||
|
<div class="buttons _buttons">
|
||||||
|
<MkButton class="button" inline primary @click="save(avatarDecoration)"><i class="ti ti-device-floppy"></i> {{ i18n.ts.save }}</MkButton>
|
||||||
|
<MkButton v-if="avatarDecoration.id != null" class="button" inline danger @click="del(avatarDecoration)"><i class="ti ti-trash"></i> {{ i18n.ts.delete }}</MkButton>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</MkFolder>
|
||||||
|
</div>
|
||||||
|
</MkSpacer>
|
||||||
|
</MkStickyContainer>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { } from 'vue';
|
||||||
|
import XHeader from './_header_.vue';
|
||||||
|
import MkButton from '@/components/MkButton.vue';
|
||||||
|
import MkInput from '@/components/MkInput.vue';
|
||||||
|
import MkTextarea from '@/components/MkTextarea.vue';
|
||||||
|
import MkSwitch from '@/components/MkSwitch.vue';
|
||||||
|
import MkRadios from '@/components/MkRadios.vue';
|
||||||
|
import MkInfo from '@/components/MkInfo.vue';
|
||||||
|
import * as os from '@/os.js';
|
||||||
|
import { i18n } from '@/i18n.js';
|
||||||
|
import { definePageMetadata } from '@/scripts/page-metadata.js';
|
||||||
|
import MkFolder from '@/components/MkFolder.vue';
|
||||||
|
|
||||||
|
let avatarDecorations: any[] = $ref([]);
|
||||||
|
|
||||||
|
function add() {
|
||||||
|
avatarDecorations.unshift({
|
||||||
|
_id: Math.random().toString(36),
|
||||||
|
id: null,
|
||||||
|
name: '',
|
||||||
|
description: '',
|
||||||
|
url: '',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function del(avatarDecoration) {
|
||||||
|
os.confirm({
|
||||||
|
type: 'warning',
|
||||||
|
text: i18n.t('deleteAreYouSure', { x: avatarDecoration.name }),
|
||||||
|
}).then(({ canceled }) => {
|
||||||
|
if (canceled) return;
|
||||||
|
avatarDecorations = avatarDecorations.filter(x => x !== avatarDecoration);
|
||||||
|
os.api('admin/avatar-decorations/delete', avatarDecoration);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function save(avatarDecoration) {
|
||||||
|
if (avatarDecoration.id == null) {
|
||||||
|
await os.apiWithDialog('admin/avatar-decorations/create', avatarDecoration);
|
||||||
|
load();
|
||||||
|
} else {
|
||||||
|
os.apiWithDialog('admin/avatar-decorations/update', avatarDecoration);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function load() {
|
||||||
|
os.api('admin/avatar-decorations/list').then(_avatarDecorations => {
|
||||||
|
avatarDecorations = _avatarDecorations;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
load();
|
||||||
|
|
||||||
|
const headerActions = $computed(() => [{
|
||||||
|
asFullButton: true,
|
||||||
|
icon: 'ti ti-plus',
|
||||||
|
text: i18n.ts.add,
|
||||||
|
handler: add,
|
||||||
|
}]);
|
||||||
|
|
||||||
|
const headerTabs = $computed(() => []);
|
||||||
|
|
||||||
|
definePageMetadata({
|
||||||
|
title: i18n.ts.avatarDecorations,
|
||||||
|
icon: 'ti ti-sparkles',
|
||||||
|
});
|
||||||
|
</script>
|
|
@ -115,6 +115,11 @@ const menuDef = $computed(() => [{
|
||||||
text: i18n.ts.customEmojis,
|
text: i18n.ts.customEmojis,
|
||||||
to: '/admin/emojis',
|
to: '/admin/emojis',
|
||||||
active: currentPage?.route.name === 'emojis',
|
active: currentPage?.route.name === 'emojis',
|
||||||
|
}, {
|
||||||
|
icon: 'ti ti-sparkles',
|
||||||
|
text: i18n.ts.avatarDecorations,
|
||||||
|
to: '/admin/avatar-decorations',
|
||||||
|
active: currentPage?.route.name === 'avatarDecorations',
|
||||||
}, {
|
}, {
|
||||||
icon: 'ti ti-whirl',
|
icon: 'ti ti-whirl',
|
||||||
text: i18n.ts.federation,
|
text: i18n.ts.federation,
|
||||||
|
|
|
@ -8,9 +8,9 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<template #label>
|
<template #label>
|
||||||
<b
|
<b
|
||||||
:class="{
|
:class="{
|
||||||
[$style.logGreen]: ['createRole', 'addCustomEmoji', 'createGlobalAnnouncement', 'createUserAnnouncement', 'createAd', 'createInvitation'].includes(log.type),
|
[$style.logGreen]: ['createRole', 'addCustomEmoji', 'createGlobalAnnouncement', 'createUserAnnouncement', 'createAd', 'createInvitation', 'createAvatarDecoration'].includes(log.type),
|
||||||
[$style.logYellow]: ['markSensitiveDriveFile', 'resetPassword'].includes(log.type),
|
[$style.logYellow]: ['markSensitiveDriveFile', 'resetPassword'].includes(log.type),
|
||||||
[$style.logRed]: ['suspend', 'deleteRole', 'suspendRemoteInstance', 'deleteGlobalAnnouncement', 'deleteUserAnnouncement', 'deleteCustomEmoji', 'deleteNote', 'deleteDriveFile', 'deleteAd'].includes(log.type)
|
[$style.logRed]: ['suspend', 'deleteRole', 'suspendRemoteInstance', 'deleteGlobalAnnouncement', 'deleteUserAnnouncement', 'deleteCustomEmoji', 'deleteNote', 'deleteDriveFile', 'deleteAd', 'deleteAvatarDecoration'].includes(log.type)
|
||||||
}"
|
}"
|
||||||
>{{ i18n.ts._moderationLogTypes[log.type] }}</b>
|
>{{ i18n.ts._moderationLogTypes[log.type] }}</b>
|
||||||
<span v-if="log.type === 'updateUserNote'">: @{{ log.info.userUsername }}{{ log.info.userHost ? '@' + log.info.userHost : '' }}</span>
|
<span v-if="log.type === 'updateUserNote'">: @{{ log.info.userUsername }}{{ log.info.userHost ? '@' + log.info.userHost : '' }}</span>
|
||||||
|
@ -37,6 +37,9 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<span v-else-if="log.type === 'deleteUserAnnouncement'">: @{{ log.info.userUsername }}{{ log.info.userHost ? '@' + log.info.userHost : '' }}</span>
|
<span v-else-if="log.type === 'deleteUserAnnouncement'">: @{{ log.info.userUsername }}{{ log.info.userHost ? '@' + log.info.userHost : '' }}</span>
|
||||||
<span v-else-if="log.type === 'deleteNote'">: @{{ log.info.noteUserUsername }}{{ log.info.noteUserHost ? '@' + log.info.noteUserHost : '' }}</span>
|
<span v-else-if="log.type === 'deleteNote'">: @{{ log.info.noteUserUsername }}{{ log.info.noteUserHost ? '@' + log.info.noteUserHost : '' }}</span>
|
||||||
<span v-else-if="log.type === 'deleteDriveFile'">: @{{ log.info.fileUserUsername }}{{ log.info.fileUserHost ? '@' + log.info.fileUserHost : '' }}</span>
|
<span v-else-if="log.type === 'deleteDriveFile'">: @{{ log.info.fileUserUsername }}{{ log.info.fileUserHost ? '@' + log.info.fileUserHost : '' }}</span>
|
||||||
|
<span v-else-if="log.type === 'createAvatarDecoration'">: {{ log.info.avatarDecoration.name }}</span>
|
||||||
|
<span v-else-if="log.type === 'updateAvatarDecoration'">: {{ log.info.before.name }}</span>
|
||||||
|
<span v-else-if="log.type === 'deleteAvatarDecoration'">: {{ log.info.avatarDecoration.name }}</span>
|
||||||
</template>
|
</template>
|
||||||
<template #icon>
|
<template #icon>
|
||||||
<MkAvatar :user="log.user" :class="$style.avatar"/>
|
<MkAvatar :user="log.user" :class="$style.avatar"/>
|
||||||
|
@ -102,6 +105,11 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<CodeDiff :context="5" :hideHeader="true" :oldString="JSON5.stringify(log.info.before, null, '\t')" :newString="JSON5.stringify(log.info.after, null, '\t')" language="javascript" maxHeight="300px"/>
|
<CodeDiff :context="5" :hideHeader="true" :oldString="JSON5.stringify(log.info.before, null, '\t')" :newString="JSON5.stringify(log.info.after, null, '\t')" language="javascript" maxHeight="300px"/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
<template v-else-if="log.type === 'updateAvatarDecoration'">
|
||||||
|
<div :class="$style.diff">
|
||||||
|
<CodeDiff :context="5" :hideHeader="true" :oldString="JSON5.stringify(log.info.before, null, '\t')" :newString="JSON5.stringify(log.info.after, null, '\t')" language="javascript" maxHeight="300px"/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
<details>
|
<details>
|
||||||
<summary>raw</summary>
|
<summary>raw</summary>
|
||||||
|
|
|
@ -83,6 +83,23 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<template #caption>{{ i18n.ts._profile.metadataDescription }}</template>
|
<template #caption>{{ i18n.ts._profile.metadataDescription }}</template>
|
||||||
</FormSlot>
|
</FormSlot>
|
||||||
|
|
||||||
|
<MkFolder>
|
||||||
|
<template #icon><i class="ti ti-sparkles"></i></template>
|
||||||
|
<template #label>{{ i18n.ts.avatarDecorations }}</template>
|
||||||
|
|
||||||
|
<div style="display: grid; grid-template-columns: repeat(auto-fill, minmax(140px, 1fr)); grid-gap: 12px;">
|
||||||
|
<div
|
||||||
|
v-for="avatarDecoration in avatarDecorations"
|
||||||
|
:key="avatarDecoration.id"
|
||||||
|
:class="[$style.avatarDecoration, { [$style.avatarDecorationActive]: $i.avatarDecorations.some(x => x.id === avatarDecoration.id) }]"
|
||||||
|
@click="toggleDecoration(avatarDecoration)"
|
||||||
|
>
|
||||||
|
<div :class="$style.avatarDecorationName">{{ avatarDecoration.name }}</div>
|
||||||
|
<MkAvatar style="width: 64px; height: 64px;" :user="$i" :decoration="avatarDecoration.url"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</MkFolder>
|
||||||
|
|
||||||
<MkFolder>
|
<MkFolder>
|
||||||
<template #label>{{ i18n.ts.advancedSettings }}</template>
|
<template #label>{{ i18n.ts.advancedSettings }}</template>
|
||||||
|
|
||||||
|
@ -126,6 +143,7 @@ import MkInfo from '@/components/MkInfo.vue';
|
||||||
const Sortable = defineAsyncComponent(() => import('vuedraggable').then(x => x.default));
|
const Sortable = defineAsyncComponent(() => import('vuedraggable').then(x => x.default));
|
||||||
|
|
||||||
const reactionAcceptance = computed(defaultStore.makeGetterSetter('reactionAcceptance'));
|
const reactionAcceptance = computed(defaultStore.makeGetterSetter('reactionAcceptance'));
|
||||||
|
let avatarDecorations: any[] = $ref([]);
|
||||||
|
|
||||||
const profile = reactive({
|
const profile = reactive({
|
||||||
name: $i.name,
|
name: $i.name,
|
||||||
|
@ -146,6 +164,10 @@ watch(() => profile, () => {
|
||||||
const fields = ref($i?.fields.map(field => ({ id: Math.random().toString(), name: field.name, value: field.value })) ?? []);
|
const fields = ref($i?.fields.map(field => ({ id: Math.random().toString(), name: field.name, value: field.value })) ?? []);
|
||||||
const fieldEditMode = ref(false);
|
const fieldEditMode = ref(false);
|
||||||
|
|
||||||
|
os.api('get-avatar-decorations').then(_avatarDecorations => {
|
||||||
|
avatarDecorations = _avatarDecorations;
|
||||||
|
});
|
||||||
|
|
||||||
function addField() {
|
function addField() {
|
||||||
fields.value.push({
|
fields.value.push({
|
||||||
id: Math.random().toString(),
|
id: Math.random().toString(),
|
||||||
|
@ -244,6 +266,20 @@ function changeBanner(ev) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function toggleDecoration(avatarDecoration) {
|
||||||
|
if ($i.avatarDecorations.some(x => x.id === avatarDecoration.id)) {
|
||||||
|
os.apiWithDialog('i/update', {
|
||||||
|
avatarDecorations: [],
|
||||||
|
});
|
||||||
|
$i.avatarDecorations = [];
|
||||||
|
} else {
|
||||||
|
os.apiWithDialog('i/update', {
|
||||||
|
avatarDecorations: [avatarDecoration.id],
|
||||||
|
});
|
||||||
|
$i.avatarDecorations.push(avatarDecoration);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const headerActions = $computed(() => []);
|
const headerActions = $computed(() => []);
|
||||||
|
|
||||||
const headerTabs = $computed(() => []);
|
const headerTabs = $computed(() => []);
|
||||||
|
@ -338,4 +374,23 @@ definePageMetadata({
|
||||||
.dragItemForm {
|
.dragItemForm {
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.avatarDecoration {
|
||||||
|
cursor: pointer;
|
||||||
|
padding: 16px 16px 24px 16px;
|
||||||
|
border: solid 2px var(--divider);
|
||||||
|
border-radius: 8px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.avatarDecorationActive {
|
||||||
|
border-color: var(--accent);
|
||||||
|
}
|
||||||
|
|
||||||
|
.avatarDecorationName {
|
||||||
|
position: relative;
|
||||||
|
z-index: 10;
|
||||||
|
font-weight: bold;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -343,6 +343,10 @@ export const routes = [{
|
||||||
path: '/emojis',
|
path: '/emojis',
|
||||||
name: 'emojis',
|
name: 'emojis',
|
||||||
component: page(() => import('./pages/custom-emojis-manager.vue')),
|
component: page(() => import('./pages/custom-emojis-manager.vue')),
|
||||||
|
}, {
|
||||||
|
path: '/avatar-decorations',
|
||||||
|
name: 'avatarDecorations',
|
||||||
|
component: page(() => import('./pages/admin/avatar-decorations.vue')),
|
||||||
}, {
|
}, {
|
||||||
path: '/queue',
|
path: '/queue',
|
||||||
name: 'queue',
|
name: 'queue',
|
||||||
|
|
|
@ -7,8 +7,8 @@ import { describe, test, assert, afterEach } from 'vitest';
|
||||||
import { render, cleanup, type RenderResult } from '@testing-library/vue';
|
import { render, cleanup, type RenderResult } from '@testing-library/vue';
|
||||||
import './init';
|
import './init';
|
||||||
import type * as Misskey from 'misskey-js';
|
import type * as Misskey from 'misskey-js';
|
||||||
import { directives } from '@/directives';
|
import { directives } from '@/directives/index.js';
|
||||||
import { components } from '@/components/index';
|
import { components } from '@/components/index.js';
|
||||||
import XHome from '@/pages/user/home.vue';
|
import XHome from '@/pages/user/home.vue';
|
||||||
|
|
||||||
describe('XHome', () => {
|
describe('XHome', () => {
|
||||||
|
@ -34,6 +34,8 @@ describe('XHome', () => {
|
||||||
createdAt: '1970-01-01T00:00:00.000Z',
|
createdAt: '1970-01-01T00:00:00.000Z',
|
||||||
fields: [],
|
fields: [],
|
||||||
pinnedNotes: [],
|
pinnedNotes: [],
|
||||||
|
avatarUrl: 'https://example.com',
|
||||||
|
avatarDecorations: [],
|
||||||
});
|
});
|
||||||
|
|
||||||
const anchor = home.container.querySelector<HTMLAnchorElement>('a[href^="https://example.com/"]');
|
const anchor = home.container.querySelector<HTMLAnchorElement>('a[href^="https://example.com/"]');
|
||||||
|
@ -54,6 +56,8 @@ describe('XHome', () => {
|
||||||
createdAt: '1970-01-01T00:00:00.000Z',
|
createdAt: '1970-01-01T00:00:00.000Z',
|
||||||
fields: [],
|
fields: [],
|
||||||
pinnedNotes: [],
|
pinnedNotes: [],
|
||||||
|
avatarUrl: 'https://example.com',
|
||||||
|
avatarDecorations: [],
|
||||||
});
|
});
|
||||||
|
|
||||||
const anchor = home.container.querySelector<HTMLAnchorElement>('a[href^="https://example.com/"]');
|
const anchor = home.container.querySelector<HTMLAnchorElement>('a[href^="https://example.com/"]');
|
||||||
|
|
|
@ -7,8 +7,8 @@ import { describe, test, assert, afterEach } from 'vitest';
|
||||||
import { render, cleanup, type RenderResult } from '@testing-library/vue';
|
import { render, cleanup, type RenderResult } from '@testing-library/vue';
|
||||||
import './init';
|
import './init';
|
||||||
import type * as Misskey from 'misskey-js';
|
import type * as Misskey from 'misskey-js';
|
||||||
import { components } from '@/components';
|
import { components } from '@/components/index.js';
|
||||||
import { directives } from '@/directives';
|
import { directives } from '@/directives/index.js';
|
||||||
import MkMediaImage from '@/components/MkMediaImage.vue';
|
import MkMediaImage from '@/components/MkMediaImage.vue';
|
||||||
|
|
||||||
describe('MkMediaImage', () => {
|
describe('MkMediaImage', () => {
|
||||||
|
|
|
@ -7,8 +7,8 @@ import { describe, test, assert, afterEach } from 'vitest';
|
||||||
import { render, cleanup, type RenderResult } from '@testing-library/vue';
|
import { render, cleanup, type RenderResult } from '@testing-library/vue';
|
||||||
import './init';
|
import './init';
|
||||||
import type { summaly } from 'summaly';
|
import type { summaly } from 'summaly';
|
||||||
import { components } from '@/components';
|
import { components } from '@/components/index.js';
|
||||||
import { directives } from '@/directives';
|
import { directives } from '@/directives/index.js';
|
||||||
import MkUrlPreview from '@/components/MkUrlPreview.vue';
|
import MkUrlPreview from '@/components/MkUrlPreview.vue';
|
||||||
|
|
||||||
type SummalyResult = Awaited<ReturnType<typeof summaly>>;
|
type SummalyResult = Awaited<ReturnType<typeof summaly>>;
|
||||||
|
|
|
@ -2634,10 +2634,22 @@ type ModerationLog = {
|
||||||
} | {
|
} | {
|
||||||
type: 'deleteAd';
|
type: 'deleteAd';
|
||||||
info: ModerationLogPayloads['deleteAd'];
|
info: ModerationLogPayloads['deleteAd'];
|
||||||
|
} | {
|
||||||
|
type: 'createAvatarDecoration';
|
||||||
|
info: ModerationLogPayloads['createAvatarDecoration'];
|
||||||
|
} | {
|
||||||
|
type: 'updateAvatarDecoration';
|
||||||
|
info: ModerationLogPayloads['updateAvatarDecoration'];
|
||||||
|
} | {
|
||||||
|
type: 'deleteAvatarDecoration';
|
||||||
|
info: ModerationLogPayloads['deleteAvatarDecoration'];
|
||||||
|
} | {
|
||||||
|
type: 'resolveAbuseReport';
|
||||||
|
info: ModerationLogPayloads['resolveAbuseReport'];
|
||||||
});
|
});
|
||||||
|
|
||||||
// @public (undocumented)
|
// @public (undocumented)
|
||||||
export const moderationLogTypes: readonly ["updateServerSettings", "suspend", "unsuspend", "updateUserNote", "addCustomEmoji", "updateCustomEmoji", "deleteCustomEmoji", "assignRole", "unassignRole", "createRole", "updateRole", "deleteRole", "clearQueue", "promoteQueue", "deleteDriveFile", "deleteNote", "createGlobalAnnouncement", "createUserAnnouncement", "updateGlobalAnnouncement", "updateUserAnnouncement", "deleteGlobalAnnouncement", "deleteUserAnnouncement", "resetPassword", "suspendRemoteInstance", "unsuspendRemoteInstance", "markSensitiveDriveFile", "unmarkSensitiveDriveFile", "resolveAbuseReport", "createInvitation", "createAd", "updateAd", "deleteAd"];
|
export const moderationLogTypes: readonly ["updateServerSettings", "suspend", "unsuspend", "updateUserNote", "addCustomEmoji", "updateCustomEmoji", "deleteCustomEmoji", "assignRole", "unassignRole", "createRole", "updateRole", "deleteRole", "clearQueue", "promoteQueue", "deleteDriveFile", "deleteNote", "createGlobalAnnouncement", "createUserAnnouncement", "updateGlobalAnnouncement", "updateUserAnnouncement", "deleteGlobalAnnouncement", "deleteUserAnnouncement", "resetPassword", "suspendRemoteInstance", "unsuspendRemoteInstance", "markSensitiveDriveFile", "unmarkSensitiveDriveFile", "resolveAbuseReport", "createInvitation", "createAd", "updateAd", "deleteAd", "createAvatarDecoration", "updateAvatarDecoration", "deleteAvatarDecoration"];
|
||||||
|
|
||||||
// @public (undocumented)
|
// @public (undocumented)
|
||||||
export const mutedNoteReasons: readonly ["word", "manual", "spam", "other"];
|
export const mutedNoteReasons: readonly ["word", "manual", "spam", "other"];
|
||||||
|
@ -2965,6 +2977,10 @@ type UserLite = {
|
||||||
onlineStatus: 'online' | 'active' | 'offline' | 'unknown';
|
onlineStatus: 'online' | 'active' | 'offline' | 'unknown';
|
||||||
avatarUrl: string;
|
avatarUrl: string;
|
||||||
avatarBlurhash: string;
|
avatarBlurhash: string;
|
||||||
|
avatarDecorations: {
|
||||||
|
id: ID;
|
||||||
|
url: string;
|
||||||
|
}[];
|
||||||
emojis: {
|
emojis: {
|
||||||
name: string;
|
name: string;
|
||||||
url: string;
|
url: string;
|
||||||
|
@ -2989,8 +3005,8 @@ type UserSorting = '+follower' | '-follower' | '+createdAt' | '-createdAt' | '+u
|
||||||
// src/api.types.ts:16:32 - (ae-forgotten-export) The symbol "TODO" needs to be exported by the entry point index.d.ts
|
// src/api.types.ts:16:32 - (ae-forgotten-export) The symbol "TODO" needs to be exported by the entry point index.d.ts
|
||||||
// src/api.types.ts:18:25 - (ae-forgotten-export) The symbol "NoParams" needs to be exported by the entry point index.d.ts
|
// src/api.types.ts:18:25 - (ae-forgotten-export) The symbol "NoParams" needs to be exported by the entry point index.d.ts
|
||||||
// src/api.types.ts:633:18 - (ae-forgotten-export) The symbol "ShowUserReq" needs to be exported by the entry point index.d.ts
|
// src/api.types.ts:633:18 - (ae-forgotten-export) The symbol "ShowUserReq" needs to be exported by the entry point index.d.ts
|
||||||
// src/entities.ts:109:2 - (ae-forgotten-export) The symbol "notificationTypes_2" needs to be exported by the entry point index.d.ts
|
// src/entities.ts:113:2 - (ae-forgotten-export) The symbol "notificationTypes_2" needs to be exported by the entry point index.d.ts
|
||||||
// src/entities.ts:605:2 - (ae-forgotten-export) The symbol "ModerationLogPayloads" needs to be exported by the entry point index.d.ts
|
// src/entities.ts:609:2 - (ae-forgotten-export) The symbol "ModerationLogPayloads" needs to be exported by the entry point index.d.ts
|
||||||
// src/streaming.types.ts:33:4 - (ae-forgotten-export) The symbol "FIXME" needs to be exported by the entry point index.d.ts
|
// src/streaming.types.ts:33:4 - (ae-forgotten-export) The symbol "FIXME" needs to be exported by the entry point index.d.ts
|
||||||
|
|
||||||
// (No @packageDocumentation comment for this package)
|
// (No @packageDocumentation comment for this package)
|
||||||
|
|
|
@ -78,6 +78,9 @@ export const moderationLogTypes = [
|
||||||
'createAd',
|
'createAd',
|
||||||
'updateAd',
|
'updateAd',
|
||||||
'deleteAd',
|
'deleteAd',
|
||||||
|
'createAvatarDecoration',
|
||||||
|
'updateAvatarDecoration',
|
||||||
|
'deleteAvatarDecoration',
|
||||||
] as const;
|
] as const;
|
||||||
|
|
||||||
export type ModerationLogPayloads = {
|
export type ModerationLogPayloads = {
|
||||||
|
@ -239,4 +242,17 @@ export type ModerationLogPayloads = {
|
||||||
adId: string;
|
adId: string;
|
||||||
ad: any;
|
ad: any;
|
||||||
};
|
};
|
||||||
|
createAvatarDecoration: {
|
||||||
|
avatarDecorationId: string;
|
||||||
|
avatarDecoration: any;
|
||||||
|
};
|
||||||
|
updateAvatarDecoration: {
|
||||||
|
avatarDecorationId: string;
|
||||||
|
before: any;
|
||||||
|
after: any;
|
||||||
|
};
|
||||||
|
deleteAvatarDecoration: {
|
||||||
|
avatarDecorationId: string;
|
||||||
|
avatarDecoration: any;
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
|
@ -16,6 +16,10 @@ export type UserLite = {
|
||||||
onlineStatus: 'online' | 'active' | 'offline' | 'unknown';
|
onlineStatus: 'online' | 'active' | 'offline' | 'unknown';
|
||||||
avatarUrl: string;
|
avatarUrl: string;
|
||||||
avatarBlurhash: string;
|
avatarBlurhash: string;
|
||||||
|
avatarDecorations: {
|
||||||
|
id: ID;
|
||||||
|
url: string;
|
||||||
|
}[];
|
||||||
emojis: {
|
emojis: {
|
||||||
name: string;
|
name: string;
|
||||||
url: string;
|
url: string;
|
||||||
|
@ -693,4 +697,16 @@ export type ModerationLog = {
|
||||||
} | {
|
} | {
|
||||||
type: 'deleteAd';
|
type: 'deleteAd';
|
||||||
info: ModerationLogPayloads['deleteAd'];
|
info: ModerationLogPayloads['deleteAd'];
|
||||||
|
} | {
|
||||||
|
type: 'createAvatarDecoration';
|
||||||
|
info: ModerationLogPayloads['createAvatarDecoration'];
|
||||||
|
} | {
|
||||||
|
type: 'updateAvatarDecoration';
|
||||||
|
info: ModerationLogPayloads['updateAvatarDecoration'];
|
||||||
|
} | {
|
||||||
|
type: 'deleteAvatarDecoration';
|
||||||
|
info: ModerationLogPayloads['deleteAvatarDecoration'];
|
||||||
|
} | {
|
||||||
|
type: 'resolveAbuseReport';
|
||||||
|
info: ModerationLogPayloads['resolveAbuseReport'];
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in a new issue