summaryrefslogtreecommitdiff
path: root/packages/backend/src/core
diff options
context:
space:
mode:
authorsyuilo <Syuilotan@yahoo.co.jp>2023-10-21 18:38:07 +0900
committerGitHub <noreply@github.com>2023-10-21 18:38:07 +0900
commit2c0a139da60ddf33e82353acbb985230c71c78da (patch)
treeb12d2762b332b7c885c5b210954053869618b62a /packages/backend/src/core
parentUpdate CHANGELOG.md (diff)
downloadsharkey-2c0a139da60ddf33e82353acbb985230c71c78da.tar.gz
sharkey-2c0a139da60ddf33e82353acbb985230c71c78da.tar.bz2
sharkey-2c0a139da60ddf33e82353acbb985230c71c78da.zip
feat: Avatar decoration (#12096)
* wip * Update ja-JP.yml * Update profile.vue * .js * Update home.test.ts
Diffstat (limited to 'packages/backend/src/core')
-rw-r--r--packages/backend/src/core/AvatarDecorationService.ts129
-rw-r--r--packages/backend/src/core/CoreModule.ts6
-rw-r--r--packages/backend/src/core/GlobalEventService.ts5
-rw-r--r--packages/backend/src/core/RoleService.ts6
-rw-r--r--packages/backend/src/core/entities/UserEntityService.ts13
5 files changed, 154 insertions, 5 deletions
diff --git a/packages/backend/src/core/AvatarDecorationService.ts b/packages/backend/src/core/AvatarDecorationService.ts
new file mode 100644
index 0000000000..e97946f9dc
--- /dev/null
+++ b/packages/backend/src/core/AvatarDecorationService.ts
@@ -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();
+ }
+}
diff --git a/packages/backend/src/core/CoreModule.ts b/packages/backend/src/core/CoreModule.ts
index e7e66646fc..b46afb1909 100644
--- a/packages/backend/src/core/CoreModule.ts
+++ b/packages/backend/src/core/CoreModule.ts
@@ -11,6 +11,7 @@ import { AnnouncementService } from './AnnouncementService.js';
import { AntennaService } from './AntennaService.js';
import { AppLockService } from './AppLockService.js';
import { AchievementService } from './AchievementService.js';
+import { AvatarDecorationService } from './AvatarDecorationService.js';
import { CaptchaService } from './CaptchaService.js';
import { CreateSystemUserService } from './CreateSystemUserService.js';
import { CustomEmojiService } from './CustomEmojiService.js';
@@ -140,6 +141,7 @@ const $AnnouncementService: Provider = { provide: 'AnnouncementService', useExis
const $AntennaService: Provider = { provide: 'AntennaService', useExisting: AntennaService };
const $AppLockService: Provider = { provide: 'AppLockService', useExisting: AppLockService };
const $AchievementService: Provider = { provide: 'AchievementService', useExisting: AchievementService };
+const $AvatarDecorationService: Provider = { provide: 'AvatarDecorationService', useExisting: AvatarDecorationService };
const $CaptchaService: Provider = { provide: 'CaptchaService', useExisting: CaptchaService };
const $CreateSystemUserService: Provider = { provide: 'CreateSystemUserService', useExisting: CreateSystemUserService };
const $CustomEmojiService: Provider = { provide: 'CustomEmojiService', useExisting: CustomEmojiService };
@@ -273,6 +275,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
AntennaService,
AppLockService,
AchievementService,
+ AvatarDecorationService,
CaptchaService,
CreateSystemUserService,
CustomEmojiService,
@@ -399,6 +402,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
$AntennaService,
$AppLockService,
$AchievementService,
+ $AvatarDecorationService,
$CaptchaService,
$CreateSystemUserService,
$CustomEmojiService,
@@ -526,6 +530,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
AntennaService,
AppLockService,
AchievementService,
+ AvatarDecorationService,
CaptchaService,
CreateSystemUserService,
CustomEmojiService,
@@ -651,6 +656,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
$AntennaService,
$AppLockService,
$AchievementService,
+ $AvatarDecorationService,
$CaptchaService,
$CreateSystemUserService,
$CustomEmojiService,
diff --git a/packages/backend/src/core/GlobalEventService.ts b/packages/backend/src/core/GlobalEventService.ts
index b74fbbe584..bfbdecf688 100644
--- a/packages/backend/src/core/GlobalEventService.ts
+++ b/packages/backend/src/core/GlobalEventService.ts
@@ -18,7 +18,7 @@ import type { MiSignin } from '@/models/Signin.js';
import type { MiPage } from '@/models/Page.js';
import type { MiWebhook } from '@/models/Webhook.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 { DI } from '@/di-symbols.js';
import type { Config } from '@/config.js';
@@ -188,6 +188,9 @@ export interface InternalEventTypes {
antennaCreated: MiAntenna;
antennaDeleted: MiAntenna;
antennaUpdated: MiAntenna;
+ avatarDecorationCreated: MiAvatarDecoration;
+ avatarDecorationDeleted: MiAvatarDecoration;
+ avatarDecorationUpdated: MiAvatarDecoration;
metaUpdated: MiMeta;
followChannel: { userId: MiUser['id']; channelId: MiChannel['id']; };
unfollowChannel: { userId: MiUser['id']; channelId: MiChannel['id']; };
diff --git a/packages/backend/src/core/RoleService.ts b/packages/backend/src/core/RoleService.ts
index 2c2ff7af1d..ef05920d50 100644
--- a/packages/backend/src/core/RoleService.ts
+++ b/packages/backend/src/core/RoleService.ts
@@ -228,6 +228,12 @@ export class RoleService implements OnApplicationShutdown {
}
@bindThis
+ public async getRoles() {
+ const roles = await this.rolesCache.fetch(() => this.rolesRepository.findBy({}));
+ return roles;
+ }
+
+ @bindThis
public async getUserAssigns(userId: MiUser['id']) {
const now = Date.now();
let assigns = await this.roleAssignmentByUserIdCache.fetch(userId, () => this.roleAssignmentsRepository.findBy({ userId }));
diff --git a/packages/backend/src/core/entities/UserEntityService.ts b/packages/backend/src/core/entities/UserEntityService.ts
index b0577fc1a0..66facce4c2 100644
--- a/packages/backend/src/core/entities/UserEntityService.ts
+++ b/packages/backend/src/core/entities/UserEntityService.ts
@@ -21,9 +21,10 @@ import { RoleService } from '@/core/RoleService.js';
import { ApPersonService } from '@/core/activitypub/models/ApPersonService.js';
import { FederatedInstanceService } from '@/core/FederatedInstanceService.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 { AnnouncementService } from '../AnnouncementService.js';
-import type { CustomEmojiService } from '../CustomEmojiService.js';
import type { NoteEntityService } from './NoteEntityService.js';
import type { DriveFileEntityService } from './DriveFileEntityService.js';
import type { PageEntityService } from './PageEntityService.js';
@@ -62,6 +63,7 @@ export class UserEntityService implements OnModuleInit {
private roleService: RoleService;
private federatedInstanceService: FederatedInstanceService;
private idService: IdService;
+ private avatarDecorationService: AvatarDecorationService;
constructor(
private moduleRef: ModuleRef,
@@ -126,6 +128,7 @@ export class UserEntityService implements OnModuleInit {
this.roleService = this.moduleRef.get('RoleService');
this.federatedInstanceService = this.moduleRef.get('FederatedInstanceService');
this.idService = this.moduleRef.get('IdService');
+ this.avatarDecorationService = this.moduleRef.get('AvatarDecorationService');
}
//#region Validators
@@ -328,8 +331,6 @@ export class UserEntityService implements OnModuleInit {
...announcement,
})) : null;
- const falsy = opts.detail ? false : undefined;
-
const packed = {
id: user.id,
name: user.name,
@@ -337,6 +338,10 @@ export class UserEntityService implements OnModuleInit {
host: user.host,
avatarUrl: user.avatarUrl ?? this.getIdenticonUrl(user),
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,
isCat: user.isCat,
instance: user.host ? this.federatedInstanceService.federatedInstanceCache.fetch(user.host).then(instance => instance ? {