yumechi-no-kuni/packages/backend/src/core/UserWebhookService.ts
eternal-flame-AD 18d5587e5c
Some checks failed
Lint / lint (frontend-shared) (pull_request) Blocked by required conditions
Lint / lint (misskey-bubble-game) (pull_request) Blocked by required conditions
Lint / pnpm_install (pull_request) Successful in 1m42s
Publish Docker image / Build (pull_request) Successful in 5m0s
Test (production install and build) / production (22.11.0) (pull_request) Successful in 1m10s
Lint / lint (backend) (pull_request) Successful in 2m21s
Lint / lint (frontend) (pull_request) Successful in 10m7s
Lint / lint (misskey-js) (pull_request) Successful in 2m30s
Lint / lint (sw) (pull_request) Successful in 3m55s
Lint / lint (misskey-reversi) (pull_request) Successful in 3m57s
Lint / typecheck (backend) (pull_request) Successful in 2m20s
Lint / typecheck (misskey-js) (pull_request) Successful in 1m38s
Lint / typecheck (sw) (pull_request) Successful in 1m49s
Lint / lint (frontend-embed) (pull_request) Failing after 15m54s
Test (backend) / unit (22.11.0) (pull_request) Successful in 7m1s
Test (backend) / e2e (22.11.0) (pull_request) Successful in 10m29s
Lint / typecheck (misskey-js) (push) Blocked by required conditions
Lint / typecheck (sw) (push) Blocked by required conditions
Lint / pnpm_install (push) Successful in 2m10s
Publish Docker image / Build (push) Successful in 4m50s
Test (production install and build) / production (22.11.0) (push) Successful in 1m17s
Lint / lint (backend) (push) Successful in 2m10s
Test (backend) / unit (22.11.0) (push) Successful in 8m55s
Lint / lint (frontend) (push) Successful in 2m11s
Lint / lint (frontend-embed) (push) Successful in 2m12s
Lint / lint (frontend-shared) (push) Successful in 2m7s
Lint / lint (misskey-bubble-game) (push) Successful in 2m5s
Test (backend) / e2e (22.11.0) (push) Failing after 11m29s
Lint / lint (misskey-js) (push) Successful in 2m20s
Lint / lint (misskey-reversi) (push) Has been cancelled
Lint / lint (sw) (push) Has been cancelled
Lint / typecheck (backend) (push) Has been cancelled
Merge tag '2024.11.0-alpha.1' into develop
Signed-off-by: eternal-flame-AD <yume@yumechi.jp>
2024-11-15 04:46:18 -06:00

144 lines
4.1 KiB
TypeScript

/*
* SPDX-FileCopyrightText: syuilo and misskey-project
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { Inject, Injectable } from '@nestjs/common';
import * as Redis from 'ioredis';
import { type WebhooksRepository } from '@/models/_.js';
import { MiWebhook, WebhookEventTypes } from '@/models/Webhook.js';
import { DI } from '@/di-symbols.js';
import { bindThis } from '@/decorators.js';
import { GlobalEvents } from '@/core/GlobalEventService.js';
import type { OnApplicationShutdown } from '@nestjs/common';
import type { Packed } from '@/misc/json-schema.js';
export type UserWebhookPayload<T extends WebhookEventTypes> =
T extends 'reaction' ? {
reaction: {
id: string,
user: Packed<'UserLite'>,
reaction: string,
}
note: Packed<'Note'>,
}:
T extends 'note' | 'reply' | 'renote' |'mention' ? {
note: Packed<'Note'>,
} :
T extends 'follow' | 'unfollow' ? {
user: Packed<'UserDetailedNotMe'>,
} :
T extends 'followed' ? {
user: Packed<'UserLite'>,
} : never;
@Injectable()
export class UserWebhookService implements OnApplicationShutdown {
private activeWebhooksFetched = false;
private activeWebhooks: MiWebhook[] = [];
constructor(
@Inject(DI.redisForSub)
private redisForSub: Redis.Redis,
@Inject(DI.webhooksRepository)
private webhooksRepository: WebhooksRepository,
) {
this.redisForSub.on('message', this.onMessage);
}
@bindThis
public async getActiveWebhooks() {
if (!this.activeWebhooksFetched) {
this.activeWebhooks = await this.webhooksRepository.findBy({
active: true,
});
this.activeWebhooksFetched = true;
}
return this.activeWebhooks;
}
/**
* UserWebhook の一覧を取得する.
*/
@bindThis
public fetchWebhooks(params?: {
ids?: MiWebhook['id'][];
isActive?: MiWebhook['active'];
on?: MiWebhook['on'];
}): Promise<MiWebhook[]> {
const query = this.webhooksRepository.createQueryBuilder('webhook');
if (params) {
if (params.ids && params.ids.length > 0) {
query.andWhere('webhook.id IN (:...ids)', { ids: params.ids });
}
if (params.isActive !== undefined) {
query.andWhere('webhook.active = :isActive', { isActive: params.isActive });
}
if (params.on && params.on.length > 0) {
query.andWhere(':on <@ webhook.on', { on: params.on });
}
}
return query.getMany();
}
@bindThis
private async onMessage(_: string, data: string): Promise<void> {
const obj = JSON.parse(data);
if (obj.channel !== 'internal') {
return;
}
const { type, body } = obj.message as GlobalEvents['internal']['payload'];
switch (type) {
case 'webhookCreated': {
if (body.active) {
this.activeWebhooks.push({ // TODO: このあたりのデシリアライズ処理は各modelファイル内に関数としてexportしたい
...body,
latestSentAt: body.latestSentAt ? new Date(body.latestSentAt) : null,
user: null, // joinなカラムは通常取ってこないので
});
}
break;
}
case 'webhookUpdated': {
if (body.active) {
const i = this.activeWebhooks.findIndex(a => a.id === body.id);
if (i > -1) {
this.activeWebhooks[i] = { // TODO: このあたりのデシリアライズ処理は各modelファイル内に関数としてexportしたい
...body,
latestSentAt: body.latestSentAt ? new Date(body.latestSentAt) : null,
user: null, // joinなカラムは通常取ってこないので
};
} else {
this.activeWebhooks.push({ // TODO: このあたりのデシリアライズ処理は各modelファイル内に関数としてexportしたい
...body,
latestSentAt: body.latestSentAt ? new Date(body.latestSentAt) : null,
user: null, // joinなカラムは通常取ってこないので
});
}
} else {
this.activeWebhooks = this.activeWebhooks.filter(a => a.id !== body.id);
}
break;
}
case 'webhookDeleted': {
this.activeWebhooks = this.activeWebhooks.filter(a => a.id !== body.id);
break;
}
default:
break;
}
}
@bindThis
public dispose(): void {
this.redisForSub.off('message', this.onMessage);
}
@bindThis
public onApplicationShutdown(signal?: string | undefined): void {
this.dispose();
}
}