feat: アンケート終了通知

Resolve #4664
This commit is contained in:
syuilo
2022-03-06 16:06:27 +09:00
parent 8bb586c1fd
commit 2442592ef1
14 changed files with 108 additions and 10 deletions

View File

@ -0,0 +1,18 @@
export class pollEndedNotification1646549089451 {
name = 'pollEndedNotification1646549089451'
async up(queryRunner) {
await queryRunner.query(`ALTER TYPE "public"."notification_type_enum" RENAME TO "notification_type_enum_old"`);
await queryRunner.query(`CREATE TYPE "public"."notification_type_enum" AS ENUM('follow', 'mention', 'reply', 'renote', 'quote', 'reaction', 'pollVote', 'pollEnded', 'receiveFollowRequest', 'followRequestAccepted', 'groupInvited', 'app')`);
await queryRunner.query(`ALTER TABLE "notification" ALTER COLUMN "type" TYPE "public"."notification_type_enum" USING "type"::"text"::"public"."notification_type_enum"`);
await queryRunner.query(`DROP TYPE "public"."notification_type_enum_old"`);
}
async down(queryRunner) {
await queryRunner.query(`CREATE TYPE "public"."notification_type_enum_old" AS ENUM('follow', 'mention', 'reply', 'renote', 'quote', 'reaction', 'pollVote', 'receiveFollowRequest', 'followRequestAccepted', 'groupInvited', 'app')`);
await queryRunner.query(`ALTER TABLE "notification" ALTER COLUMN "type" TYPE "public"."notification_type_enum_old" USING "type"::"text"::"public"."notification_type_enum_old"`);
await queryRunner.query(`DROP TYPE "public"."notification_type_enum"`);
await queryRunner.query(`ALTER TYPE "public"."notification_type_enum_old" RENAME TO "notification_type_enum"`);
}
}

View File

@ -59,7 +59,8 @@ export class Notification {
* renote - (自分または自分がWatchしている)投稿がRenoteされた
* quote - (自分または自分がWatchしている)投稿が引用Renoteされた
* reaction - (自分または自分がWatchしている)投稿にリアクションされた
* pollVote - (自分または自分がWatchしている)投稿の投票に投票された
* pollVote - (自分または自分がWatchしている)投稿のアンケートに投票された
* pollEnded - 自分のアンケートもしくは自分が投票したアンケートが終了した
* receiveFollowRequest - フォローリクエストされた
* followRequestAccepted - 自分の送ったフォローリクエストが承認された
* groupInvited - グループに招待された

View File

@ -67,6 +67,12 @@ export class NotificationRepository extends Repository<Notification> {
}),
choice: notification.choice,
} : {}),
...(notification.type === 'pollEnded' ? {
note: Notes.pack(notification.note || notification.noteId!, { id: notification.notifieeId }, {
detail: true,
_hint_: options._hintForEachNotes_,
}),
} : {}),
...(notification.type === 'groupInvited' ? {
invitation: UserGroupInvitations.pack(notification.userGroupInvitationId!),
} : {}),

View File

@ -8,10 +8,11 @@ import processInbox from './processors/inbox.js';
import processDb from './processors/db/index.js';
import processObjectStorage from './processors/object-storage/index.js';
import processSystemQueue from './processors/system/index.js';
import { endedPollNotification } from './processors/ended-poll-notification.js';
import { queueLogger } from './logger.js';
import { DriveFile } from '@/models/entities/drive-file.js';
import { getJobInfo } from './get-job-info.js';
import { systemQueue, dbQueue, deliverQueue, inboxQueue, objectStorageQueue } from './queues.js';
import { systemQueue, dbQueue, deliverQueue, inboxQueue, objectStorageQueue, endedPollNotificationQueue } from './queues.js';
import { ThinUser } from './types.js';
import { IActivity } from '@/remote/activitypub/type.js';
@ -255,6 +256,7 @@ export default function() {
deliverQueue.process(config.deliverJobConcurrency || 128, processDeliver);
inboxQueue.process(config.inboxJobConcurrency || 16, processInbox);
endedPollNotificationQueue.process(endedPollNotification);
processDb(dbQueue);
processObjectStorage(objectStorageQueue);

View File

@ -0,0 +1,33 @@
import Bull from 'bull';
import { In } from 'typeorm';
import { Notes, Polls, PollVotes } from '@/models/index.js';
import { queueLogger } from '../logger.js';
import { EndedPollNotificationJobData } from '@/queue/types.js';
import { createNotification } from '@/services/create-notification.js';
const logger = queueLogger.createSubLogger('ended-poll-notification');
export async function endedPollNotification(job: Bull.Job<EndedPollNotificationJobData>, done: any): Promise<void> {
const note = await Notes.findOne(job.data.noteId);
if (note == null || !note.hasPoll) {
done();
return;
}
const votes = await PollVotes.createQueryBuilder('vote')
.select('vote.userId')
.where('vote.noteId = :noteId', { noteId: note.id })
.innerJoinAndSelect('vote.user', 'user')
.andWhere('user.host IS NULL')
.getMany();
const userIds = [...new Set([note.userId, ...votes.map(v => v.userId)])];
for (const userId of userIds) {
createNotification(userId, 'pollEnded', {
noteId: note.id,
});
}
done();
}

View File

@ -1,8 +1,9 @@
import config from '@/config/index.js';
import { initialize as initializeQueue } from './initialize.js';
import { DeliverJobData, InboxJobData, DbJobData, ObjectStorageJobData } from './types.js';
import { DeliverJobData, InboxJobData, DbJobData, ObjectStorageJobData, EndedPollNotificationJobData } from './types.js';
export const systemQueue = initializeQueue<Record<string, unknown>>('system');
export const endedPollNotificationQueue = initializeQueue<EndedPollNotificationJobData>('endedPollNotification');
export const deliverQueue = initializeQueue<DeliverJobData>('deliver', config.deliverJobPerSec || 128);
export const inboxQueue = initializeQueue<InboxJobData>('inbox', config.inboxJobPerSec || 16);
export const dbQueue = initializeQueue<DbJobData>('db');

View File

@ -1,4 +1,5 @@
import { DriveFile } from '@/models/entities/drive-file.js';
import { Note } from '@/models/entities/note';
import { User } from '@/models/entities/user.js';
import { IActivity } from '@/remote/activitypub/type.js';
import httpSignature from 'http-signature';
@ -41,6 +42,10 @@ export type ObjectStorageFileJobData = {
key: string;
};
export type EndedPollNotificationJobData = {
noteId: Note['id'];
};
export type ThinUser = {
id: User['id'];
};

View File

@ -34,6 +34,7 @@ import { deliverToRelays } from '../relay.js';
import { Channel } from '@/models/entities/channel.js';
import { normalizeForSearch } from '@/misc/normalize-for-search.js';
import { getAntennas } from '@/misc/antenna-cache.js';
import { endedPollNotificationQueue } from '@/queue/queues.js';
type NotificationType = 'reply' | 'renote' | 'quote' | 'mention';
@ -296,6 +297,15 @@ export default async (user: { id: User['id']; username: User['username']; host:
incRenoteCount(data.renote);
}
if (data.poll && data.poll.expiresAt) {
const delay = data.poll.expiresAt.getTime() - Date.now();
endedPollNotificationQueue.add({
noteId: note.id,
}, {
delay
});
}
if (!silent) {
if (Users.isLocalUser(user)) activeUsersChart.write(user);

View File

@ -1,4 +1,4 @@
export const notificationTypes = ['follow', 'mention', 'reply', 'renote', 'quote', 'reaction', 'pollVote', 'receiveFollowRequest', 'followRequestAccepted', 'groupInvited', 'app'] as const;
export const notificationTypes = ['follow', 'mention', 'reply', 'renote', 'quote', 'reaction', 'pollVote', 'pollEnded', 'receiveFollowRequest', 'followRequestAccepted', 'groupInvited', 'app'] as const;
export const noteVisibilities = ['public', 'home', 'followers', 'specified'] as const;