misskey/packages/backend/src/core/AvatarDecorationService.ts
Acid Chicken 2b8056a852
fix(backend): use insertOne insteadof insert/findOneOrFail combination (#13908)
* fix(backend): use insertOne insteadof insert/findOneOrFail combination

* fix: typo

* fix(backend): inherit mainAlias?

* refactor(backend): use extend

* fix(backend): invalid entityTarget

* fix(backend): fake where

* chore: debug

* chore: debug

* test: log

* fix(backend): column names

* fix(backend): remove dummy from

* revert: log

* fix(backend): position

* fix(backend): automatic aliasing

* chore(backend): alias

* chore(backend): remove from

* fix(backend): type

* fix(backend): avoid pure name

* test(backend): fix type

* chore(backend): use cte

* fix(backend): avoid useless alias

* fix(backend): fix typo

* fix(backend): __disambiguation__

* fix(backend): quote

* chore(backend): t

* chore(backend): accessible

* chore(backend): concrete returning

* fix(backend): quote

* chore: log more

* chore: log metadata

* chore(backend): use raw

* fix(backend): returning column name

* fix(backend): transform

* build(backend): wanna logging

* build(backend): transform empty

* build(backend): build alias

* build(backend): restore name

* chore: return entity

* fix: test case

* test(backend): 204

* chore(backend): log sql

* chore(backend): assert user joined

* fix(backend): typo

* chore(backend): log long sql

* chore(backend): log join

* chore(backend): log join depth null

* chore(backend): joinAttributes

* chore(backend): override createJoinExpression

* chore: join log

* fix(backend): escape

* test(backend): log log

* chore(backend): join gonna success?

* chore(backend): relations

* chore(backend): undefined

* chore(backend): target

* chore(backend): remove log

* chore(backend): log chart update

* chore(backend): log columns

* chore(backend): check hasMetadata

* chore(backend): unshift id when not included

* chore(backend): missing select

* chore(backend): remove debug code
2024-06-01 11:16:44 +09:00

130 lines
3.9 KiB
TypeScript

/*
* SPDX-FileCopyrightText: syuilo and misskey-project
* 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.insertOne({
id: this.idService.gen(),
...options,
});
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();
}
}