misskey/packages/backend/src/core/AvatarDecorationService.ts

130 lines
4.0 KiB
TypeScript
Raw Normal View History

/*
* 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();
}
}