@ -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"`);
|
||||
}
|
||||
}
|
@ -59,7 +59,8 @@ export class Notification {
|
||||
* renote - (自分または自分がWatchしている)投稿がRenoteされた
|
||||
* quote - (自分または自分がWatchしている)投稿が引用Renoteされた
|
||||
* reaction - (自分または自分がWatchしている)投稿にリアクションされた
|
||||
* pollVote - (自分または自分がWatchしている)投稿の投票に投票された
|
||||
* pollVote - (自分または自分がWatchしている)投稿のアンケートに投票された
|
||||
* pollEnded - 自分のアンケートもしくは自分が投票したアンケートが終了した
|
||||
* receiveFollowRequest - フォローリクエストされた
|
||||
* followRequestAccepted - 自分の送ったフォローリクエストが承認された
|
||||
* groupInvited - グループに招待された
|
||||
|
@ -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!),
|
||||
} : {}),
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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();
|
||||
}
|
@ -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');
|
||||
|
@ -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'];
|
||||
};
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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;
|
||||
|
||||
|
Reference in New Issue
Block a user