Merge branch 'notification-read-api' into swn
This commit is contained in:
@@ -6,7 +6,6 @@ export type Source = {
|
||||
feedback_url?: string;
|
||||
url: string;
|
||||
port: number;
|
||||
https?: { [x: string]: string };
|
||||
disableHsts?: boolean;
|
||||
db: {
|
||||
host: string;
|
||||
|
@@ -184,7 +184,7 @@ export function initDb(justBorrow = false, sync = false, forceRecreate = false)
|
||||
} catch (e) {}
|
||||
}
|
||||
|
||||
const log = process.env.NODE_ENV != 'production';
|
||||
const log = process.env.NODE_ENV !== 'production';
|
||||
|
||||
return createConnection({
|
||||
type: 'postgres',
|
||||
@@ -193,7 +193,10 @@ export function initDb(justBorrow = false, sync = false, forceRecreate = false)
|
||||
username: config.db.user,
|
||||
password: config.db.pass,
|
||||
database: config.db.db,
|
||||
extra: config.db.extra,
|
||||
extra: {
|
||||
statement_timeout: 1000 * 10,
|
||||
...config.db.extra,
|
||||
},
|
||||
synchronize: process.env.NODE_ENV === 'test' || sync,
|
||||
dropSchema: process.env.NODE_ENV === 'test' && !justBorrow,
|
||||
cache: !config.db.disableCache ? {
|
||||
|
@@ -2,7 +2,7 @@ import * as fs from 'node:fs';
|
||||
import * as crypto from 'node:crypto';
|
||||
import * as stream from 'node:stream';
|
||||
import * as util from 'node:util';
|
||||
import fileType from 'file-type';
|
||||
import { fileTypeFromFile } from 'file-type';
|
||||
import isSvg from 'is-svg';
|
||||
import probeImageSize from 'probe-image-size';
|
||||
import sharp from 'sharp';
|
||||
@@ -109,7 +109,7 @@ export async function detectType(path: string): Promise<{
|
||||
return TYPE_OCTET_STREAM;
|
||||
}
|
||||
|
||||
const type = await fileType.fromFile(path);
|
||||
const type = await fileTypeFromFile(path);
|
||||
|
||||
if (type) {
|
||||
// XMLはSVGかもしれない
|
||||
|
@@ -59,22 +59,6 @@ export class Instance {
|
||||
})
|
||||
public followersCount: number;
|
||||
|
||||
/**
|
||||
* ドライブ使用量
|
||||
*/
|
||||
@Column('bigint', {
|
||||
default: 0,
|
||||
})
|
||||
public driveUsage: number;
|
||||
|
||||
/**
|
||||
* ドライブのファイル数
|
||||
*/
|
||||
@Column('integer', {
|
||||
default: 0,
|
||||
})
|
||||
public driveFiles: number;
|
||||
|
||||
/**
|
||||
* 直近のリクエスト送信日時
|
||||
*/
|
||||
|
@@ -14,6 +14,13 @@ export class Muting {
|
||||
})
|
||||
public createdAt: Date;
|
||||
|
||||
@Index()
|
||||
@Column('timestamp with time zone', {
|
||||
nullable: true,
|
||||
default: null,
|
||||
})
|
||||
public expiresAt: Date | null;
|
||||
|
||||
@Index()
|
||||
@Column({
|
||||
...id(),
|
||||
|
@@ -59,7 +59,8 @@ export class Notification {
|
||||
* renote - (自分または自分がWatchしている)投稿がRenoteされた
|
||||
* quote - (自分または自分がWatchしている)投稿が引用Renoteされた
|
||||
* reaction - (自分または自分がWatchしている)投稿にリアクションされた
|
||||
* pollVote - (自分または自分がWatchしている)投稿の投票に投票された
|
||||
* pollVote - (自分または自分がWatchしている)投稿のアンケートに投票された
|
||||
* pollEnded - 自分のアンケートもしくは自分が投票したアンケートが終了した
|
||||
* receiveFollowRequest - フォローリクエストされた
|
||||
* followRequestAccepted - 自分の送ったフォローリクエストが承認された
|
||||
* groupInvited - グループに招待された
|
||||
|
@@ -16,6 +16,7 @@ export class MutingRepository extends Repository<Muting> {
|
||||
return await awaitAll({
|
||||
id: muting.id,
|
||||
createdAt: muting.createdAt.toISOString(),
|
||||
expiresAt: muting.expiresAt ? muting.expiresAt.toISOString() : null,
|
||||
muteeId: muting.muteeId,
|
||||
mutee: Users.pack(muting.muteeId, me, {
|
||||
detail: true,
|
||||
|
@@ -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!),
|
||||
} : {}),
|
||||
|
@@ -12,6 +12,11 @@ export const packedMutingSchema = {
|
||||
optional: false, nullable: false,
|
||||
format: 'date-time',
|
||||
},
|
||||
expiresAt: {
|
||||
type: 'string',
|
||||
optional: false, nullable: true,
|
||||
format: 'date-time',
|
||||
},
|
||||
muteeId: {
|
||||
type: 'string',
|
||||
optional: false, nullable: false,
|
||||
|
@@ -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);
|
||||
|
||||
@@ -273,6 +275,11 @@ export default function() {
|
||||
repeat: { cron: '0 0 * * *' },
|
||||
});
|
||||
|
||||
systemQueue.add('checkExpiredMutings', {
|
||||
}, {
|
||||
repeat: { cron: '*/5 * * * *' },
|
||||
});
|
||||
|
||||
processSystemQueue(systemQueue);
|
||||
}
|
||||
|
||||
|
@@ -7,7 +7,7 @@ import { addFile } from '@/services/drive/add-file.js';
|
||||
import { format as dateFormat } from 'date-fns';
|
||||
import { getFullApAccount } from '@/misc/convert-host.js';
|
||||
import { Users, Mutings } from '@/models/index.js';
|
||||
import { MoreThan } from 'typeorm';
|
||||
import { IsNull, MoreThan } from 'typeorm';
|
||||
import { DbUserJobData } from '@/queue/types.js';
|
||||
|
||||
const logger = queueLogger.createSubLogger('export-mute');
|
||||
@@ -40,6 +40,7 @@ export async function exportMute(job: Bull.Job<DbUserJobData>, done: any): Promi
|
||||
const mutes = await Mutings.find({
|
||||
where: {
|
||||
muterId: user.id,
|
||||
expiresAt: IsNull(),
|
||||
...(cursor ? { id: MoreThan(cursor) } : {}),
|
||||
},
|
||||
take: 100,
|
||||
|
@@ -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();
|
||||
}
|
@@ -0,0 +1,30 @@
|
||||
import Bull from 'bull';
|
||||
import { In } from 'typeorm';
|
||||
import { Mutings } from '@/models/index.js';
|
||||
import { queueLogger } from '../../logger.js';
|
||||
import { publishUserEvent } from '@/services/stream.js';
|
||||
|
||||
const logger = queueLogger.createSubLogger('check-expired-mutings');
|
||||
|
||||
export async function checkExpiredMutings(job: Bull.Job<Record<string, unknown>>, done: any): Promise<void> {
|
||||
logger.info(`Checking expired mutings...`);
|
||||
|
||||
const expired = await Mutings.createQueryBuilder('muting')
|
||||
.where('muting.expiresAt IS NOT NULL')
|
||||
.andWhere('muting.expiresAt < :now', { now: new Date() })
|
||||
.innerJoinAndSelect('muting.mutee', 'mutee')
|
||||
.getMany();
|
||||
|
||||
if (expired.length > 0) {
|
||||
await Mutings.delete({
|
||||
id: In(expired.map(m => m.id)),
|
||||
});
|
||||
|
||||
for (const m of expired) {
|
||||
publishUserEvent(m.muterId, 'unmute', m.mutee!);
|
||||
}
|
||||
}
|
||||
|
||||
logger.succ(`All expired mutings checked.`);
|
||||
done();
|
||||
}
|
@@ -2,11 +2,13 @@ import Bull from 'bull';
|
||||
import { tickCharts } from './tick-charts.js';
|
||||
import { resyncCharts } from './resync-charts.js';
|
||||
import { cleanCharts } from './clean-charts.js';
|
||||
import { checkExpiredMutings } from './check-expired-mutings.js';
|
||||
|
||||
const jobs = {
|
||||
tickCharts,
|
||||
resyncCharts,
|
||||
cleanCharts,
|
||||
checkExpiredMutings,
|
||||
} as Record<string, Bull.ProcessCallbackFunction<Record<string, unknown>> | Bull.ProcessPromiseFunction<Record<string, unknown>>>;
|
||||
|
||||
export default function(dbQueue: Bull.Queue<Record<string, unknown>>) {
|
||||
|
@@ -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'];
|
||||
};
|
||||
|
@@ -31,7 +31,7 @@ const ajv = new Ajv({
|
||||
useDefaults: true,
|
||||
});
|
||||
|
||||
ajv.addFormat('misskey:id', /^[a-z0-9]+$/);
|
||||
ajv.addFormat('misskey:id', /^[a-zA-Z0-9]+$/);
|
||||
|
||||
export default function <T extends IEndpointMeta, Ps extends Schema>(meta: T, paramDef: Ps, cb: executor<T, Ps>)
|
||||
: (params: any, user: T['requireCredential'] extends true ? SimpleUserInfo : SimpleUserInfo | null, token: AccessToken | null, file?: any) => Promise<any> {
|
||||
|
@@ -55,10 +55,6 @@ export default define(meta, paramDef, async (ps, me) => {
|
||||
case '-caughtAt': query.orderBy('instance.caughtAt', 'ASC'); break;
|
||||
case '+lastCommunicatedAt': query.orderBy('instance.lastCommunicatedAt', 'DESC'); break;
|
||||
case '-lastCommunicatedAt': query.orderBy('instance.lastCommunicatedAt', 'ASC'); break;
|
||||
case '+driveUsage': query.orderBy('instance.driveUsage', 'DESC'); break;
|
||||
case '-driveUsage': query.orderBy('instance.driveUsage', 'ASC'); break;
|
||||
case '+driveFiles': query.orderBy('instance.driveFiles', 'DESC'); break;
|
||||
case '-driveFiles': query.orderBy('instance.driveFiles', 'ASC'); break;
|
||||
|
||||
default: query.orderBy('instance.id', 'DESC'); break;
|
||||
}
|
||||
|
@@ -64,11 +64,6 @@ export const meta = {
|
||||
optional: false, nullable: false,
|
||||
default: 'https://github.com/misskey-dev/misskey/issues/new',
|
||||
},
|
||||
secure: {
|
||||
type: 'boolean',
|
||||
optional: false, nullable: false,
|
||||
default: false,
|
||||
},
|
||||
defaultDarkTheme: {
|
||||
type: 'string',
|
||||
optional: false, nullable: true,
|
||||
@@ -489,9 +484,6 @@ export default define(meta, paramDef, async (ps, me) => {
|
||||
tosUrl: instance.ToSUrl,
|
||||
repositoryUrl: instance.repositoryUrl,
|
||||
feedbackUrl: instance.feedbackUrl,
|
||||
|
||||
secure: config.https != null,
|
||||
|
||||
disableRegistration: instance.disableRegistration,
|
||||
disableLocalTimeline: instance.disableLocalTimeline,
|
||||
disableGlobalTimeline: instance.disableGlobalTimeline,
|
||||
|
@@ -38,6 +38,7 @@ export const paramDef = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
userId: { type: 'string', format: 'misskey:id' },
|
||||
expiresAt: { type: 'integer', nullable: true },
|
||||
},
|
||||
required: ['userId'],
|
||||
} as const;
|
||||
@@ -67,10 +68,15 @@ export default define(meta, paramDef, async (ps, user) => {
|
||||
throw new ApiError(meta.errors.alreadyMuting);
|
||||
}
|
||||
|
||||
if (ps.expiresAt && ps.expiresAt <= Date.now()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Create mute
|
||||
await Mutings.insert({
|
||||
id: genId(),
|
||||
createdAt: new Date(),
|
||||
expiresAt: ps.expiresAt ? new Date(ps.expiresAt) : null,
|
||||
muterId: muter.id,
|
||||
muteeId: mutee.id,
|
||||
} as Muting);
|
||||
|
@@ -4,8 +4,6 @@
|
||||
|
||||
import * as fs from 'node:fs';
|
||||
import * as http from 'http';
|
||||
import * as http2 from 'http2';
|
||||
import * as https from 'https';
|
||||
import Koa from 'koa';
|
||||
import Router from '@koa/router';
|
||||
import mount from 'koa-mount';
|
||||
@@ -123,16 +121,7 @@ app.use(router.routes());
|
||||
app.use(mount(webServer));
|
||||
|
||||
function createServer() {
|
||||
if (config.https) {
|
||||
const certs: any = {};
|
||||
for (const k of Object.keys(config.https)) {
|
||||
certs[k] = fs.readFileSync(config.https[k]);
|
||||
}
|
||||
certs['allowHTTP1'] = true;
|
||||
return http2.createSecureServer(certs, app.callback()) as https.Server;
|
||||
} else {
|
||||
return http.createServer(app.callback());
|
||||
}
|
||||
return http.createServer(app.callback());
|
||||
}
|
||||
|
||||
// For testing
|
||||
|
@@ -59,5 +59,5 @@ html
|
||||
br
|
||||
| Please turn on your JavaScript
|
||||
div#splash
|
||||
img(src='/favicon.ico')
|
||||
img(src= icon || '/static-assets/splash.png')
|
||||
block content
|
||||
|
@@ -9,6 +9,8 @@ export const schema = {
|
||||
'sub': { accumulate: true, range: 'small' },
|
||||
'pub': { accumulate: true, range: 'small' },
|
||||
'pubsub': { accumulate: true, range: 'small' },
|
||||
'subActive': { accumulate: true, range: 'small' },
|
||||
'pubActive': { accumulate: true, range: 'small' },
|
||||
} as const;
|
||||
|
||||
export const entity = Chart.schemaToEntity(name, schema);
|
||||
|
@@ -1,6 +1,7 @@
|
||||
import Chart, { KVs } from '../core.js';
|
||||
import { Followings } from '@/models/index.js';
|
||||
import { Followings, Instances } from '@/models/index.js';
|
||||
import { name, schema } from './entities/federation.js';
|
||||
import { fetchMeta } from '@/misc/fetch-meta.js';
|
||||
|
||||
/**
|
||||
* フェデレーションに関するチャート
|
||||
@@ -17,34 +18,72 @@ export default class FederationChart extends Chart<typeof schema> {
|
||||
}
|
||||
|
||||
protected async tickMinor(): Promise<Partial<KVs<typeof schema>>> {
|
||||
const meta = await fetchMeta();
|
||||
|
||||
const suspendedInstancesQuery = Instances.createQueryBuilder('instance')
|
||||
.select('instance.host')
|
||||
.where('instance.isSuspended = true');
|
||||
|
||||
const pubsubSubQuery = Followings.createQueryBuilder('f')
|
||||
.select('f.followerHost')
|
||||
.where('f.followerHost IS NOT NULL');
|
||||
|
||||
const [sub, pub, pubsub] = await Promise.all([
|
||||
const subInstancesQuery = Followings.createQueryBuilder('f')
|
||||
.select('f.followeeHost')
|
||||
.where('f.followeeHost IS NOT NULL');
|
||||
|
||||
const pubInstancesQuery = Followings.createQueryBuilder('f')
|
||||
.select('f.followerHost')
|
||||
.where('f.followerHost IS NOT NULL');
|
||||
|
||||
const [sub, pub, pubsub, subActive, pubActive] = await Promise.all([
|
||||
Followings.createQueryBuilder('following')
|
||||
.select('COUNT(DISTINCT following.followeeHost)')
|
||||
.where('following.followeeHost IS NOT NULL')
|
||||
.andWhere(meta.blockedHosts.length === 0 ? '1=1' : `following.followeeHost NOT IN (:...blocked)`, { blocked: meta.blockedHosts })
|
||||
.andWhere(`following.followeeHost NOT IN (${ suspendedInstancesQuery.getQuery() })`)
|
||||
.getRawOne()
|
||||
.then(x => parseInt(x.count, 10)),
|
||||
Followings.createQueryBuilder('following')
|
||||
.select('COUNT(DISTINCT following.followerHost)')
|
||||
.where('following.followerHost IS NOT NULL')
|
||||
.andWhere(meta.blockedHosts.length === 0 ? '1=1' : `following.followerHost NOT IN (:...blocked)`, { blocked: meta.blockedHosts })
|
||||
.andWhere(`following.followerHost NOT IN (${ suspendedInstancesQuery.getQuery() })`)
|
||||
.getRawOne()
|
||||
.then(x => parseInt(x.count, 10)),
|
||||
Followings.createQueryBuilder('following')
|
||||
.select('COUNT(DISTINCT following.followeeHost)')
|
||||
.where('following.followeeHost IS NOT NULL')
|
||||
.andWhere(meta.blockedHosts.length === 0 ? '1=1' : `following.followeeHost NOT IN (:...blocked)`, { blocked: meta.blockedHosts })
|
||||
.andWhere(`following.followeeHost NOT IN (${ suspendedInstancesQuery.getQuery() })`)
|
||||
.andWhere(`following.followeeHost IN (${ pubsubSubQuery.getQuery() })`)
|
||||
.setParameters(pubsubSubQuery.getParameters())
|
||||
.getRawOne()
|
||||
.then(x => parseInt(x.count, 10)),
|
||||
Instances.createQueryBuilder('instance')
|
||||
.select('COUNT(instance.id)')
|
||||
.where(`instance.host IN (${ subInstancesQuery.getQuery() })`)
|
||||
.andWhere(meta.blockedHosts.length === 0 ? '1=1' : `instance.host NOT IN (:...blocked)`, { blocked: meta.blockedHosts })
|
||||
.andWhere(`instance.isSuspended = false`)
|
||||
.andWhere(`instance.lastCommunicatedAt > :gt`, { gt: new Date(Date.now() - (1000 * 60 * 60 * 24 * 30)) })
|
||||
.getRawOne()
|
||||
.then(x => parseInt(x.count, 10)),
|
||||
Instances.createQueryBuilder('instance')
|
||||
.select('COUNT(instance.id)')
|
||||
.where(`instance.host IN (${ pubInstancesQuery.getQuery() })`)
|
||||
.andWhere(meta.blockedHosts.length === 0 ? '1=1' : `instance.host NOT IN (:...blocked)`, { blocked: meta.blockedHosts })
|
||||
.andWhere(`instance.isSuspended = false`)
|
||||
.andWhere(`instance.lastCommunicatedAt > :gt`, { gt: new Date(Date.now() - (1000 * 60 * 60 * 24 * 30)) })
|
||||
.getRawOne()
|
||||
.then(x => parseInt(x.count, 10)),
|
||||
]);
|
||||
|
||||
return {
|
||||
'sub': sub,
|
||||
'pub': pub,
|
||||
'pubsub': pubsub,
|
||||
'subActive': subActive,
|
||||
'pubActive': pubActive,
|
||||
};
|
||||
}
|
||||
|
||||
|
@@ -21,14 +21,12 @@ export default class InstanceChart extends Chart<typeof schema> {
|
||||
followingCount,
|
||||
followersCount,
|
||||
driveFiles,
|
||||
//driveUsage,
|
||||
] = await Promise.all([
|
||||
Notes.count({ userHost: group }),
|
||||
Users.count({ host: group }),
|
||||
Followings.count({ followerHost: group }),
|
||||
Followings.count({ followeeHost: group }),
|
||||
DriveFiles.count({ userHost: group }),
|
||||
//DriveFiles.calcDriveUsageOfHost(group),
|
||||
]);
|
||||
|
||||
return {
|
||||
|
@@ -484,8 +484,6 @@ export async function addFile({
|
||||
perUserDriveChart.update(file, true);
|
||||
if (file.userHost !== null) {
|
||||
instanceChart.updateDrive(file, true);
|
||||
Instances.increment({ host: file.userHost }, 'driveUsage', file.size);
|
||||
Instances.increment({ host: file.userHost }, 'driveFiles', 1);
|
||||
}
|
||||
|
||||
return file;
|
||||
|
@@ -86,8 +86,6 @@ async function postProcess(file: DriveFile, isExpired = false) {
|
||||
perUserDriveChart.update(file, false);
|
||||
if (file.userHost !== null) {
|
||||
instanceChart.updateDrive(file, false);
|
||||
Instances.decrement({ host: file.userHost }, 'driveUsage', file.size);
|
||||
Instances.decrement({ host: file.userHost }, 'driveFiles', 1);
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -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