diff options
| author | syuilo <Syuilotan@yahoo.co.jp> | 2023-10-21 18:38:07 +0900 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2023-10-21 18:38:07 +0900 |
| commit | 2c0a139da60ddf33e82353acbb985230c71c78da (patch) | |
| tree | b12d2762b332b7c885c5b210954053869618b62a /packages/backend/src/core | |
| parent | Update CHANGELOG.md (diff) | |
| download | sharkey-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.ts | 129 | ||||
| -rw-r--r-- | packages/backend/src/core/CoreModule.ts | 6 | ||||
| -rw-r--r-- | packages/backend/src/core/GlobalEventService.ts | 5 | ||||
| -rw-r--r-- | packages/backend/src/core/RoleService.ts | 6 | ||||
| -rw-r--r-- | packages/backend/src/core/entities/UserEntityService.ts | 13 |
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 ? { |