diff --git a/packages/backend/src/core/AbuseReportNotificationService.ts b/packages/backend/src/core/AbuseReportNotificationService.ts index 307f22586e..70c9092777 100644 --- a/packages/backend/src/core/AbuseReportNotificationService.ts +++ b/packages/backend/src/core/AbuseReportNotificationService.ts @@ -23,6 +23,7 @@ import { RecipientMethod } from '@/models/AbuseReportNotificationRecipient.js'; import { ModerationLogService } from '@/core/ModerationLogService.js'; import { SystemWebhookService } from '@/core/SystemWebhookService.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; +import { TimeService } from '@/core/TimeService.js'; import { IdService } from './IdService.js'; @Injectable() @@ -44,6 +45,7 @@ export class AbuseReportNotificationService implements OnApplicationShutdown { private moderationLogService: ModerationLogService, private globalEventService: GlobalEventService, private userEntityService: UserEntityService, + private readonly timeService: TimeService, ) { this.redisForSub.on('message', this.onMessage); } @@ -326,7 +328,7 @@ export class AbuseReportNotificationService implements OnApplicationShutdown { await this.abuseReportNotificationRecipientRepository.update(params.id, { isActive: params.isActive, - updatedAt: new Date(), + updatedAt: this.timeService.date, name: params.name, method: params.method, userId: params.userId, diff --git a/packages/backend/src/core/AccountMoveService.ts b/packages/backend/src/core/AccountMoveService.ts index 3c9f8bb518..91155347b5 100644 --- a/packages/backend/src/core/AccountMoveService.ts +++ b/packages/backend/src/core/AccountMoveService.ts @@ -28,6 +28,7 @@ import { RoleService } from '@/core/RoleService.js'; import { AntennaService } from '@/core/AntennaService.js'; import { CacheService } from '@/core/CacheService.js'; import { UserListService } from '@/core/UserListService.js'; +import { TimeService } from '@/core/TimeService.js'; @Injectable() export class AccountMoveService { @@ -72,6 +73,7 @@ export class AccountMoveService { private antennaService: AntennaService, private readonly cacheService: CacheService, private readonly userListService: UserListService, + private readonly timeService: TimeService, ) { } @@ -89,7 +91,7 @@ export class AccountMoveService { const update = {} as Partial; update.alsoKnownAs = src.alsoKnownAs?.includes(dstUri) ? src.alsoKnownAs : src.alsoKnownAs?.concat([dstUri]) ?? [dstUri]; update.movedToUri = dstUri; - update.movedAt = new Date(); + update.movedAt = this.timeService.date; await this.usersRepository.update(src.id, update); Object.assign(src, update); @@ -181,7 +183,7 @@ export class AccountMoveService { // Insert new mutings with the same values except mutee const oldMutings = await this.mutingsRepository.findBy([ { muteeId: src.id, expiresAt: IsNull() }, - { muteeId: src.id, expiresAt: MoreThan(new Date()) }, + { muteeId: src.id, expiresAt: MoreThan(this.timeService.date) }, ]); if (oldMutings.length === 0) return; @@ -348,7 +350,7 @@ export class AccountMoveService { let resultUser: MiLocalUser | MiRemoteUser | null = null; if (this.userEntityService.isRemoteUser(dst)) { - if (Date.now() - (dst.lastFetchedAt?.getTime() ?? 0) > 10 * 1000) { + if (this.timeService.now - (dst.lastFetchedAt?.getTime() ?? 0) > 10 * 1000) { await this.apPersonService.updatePerson(dst.uri); } dst = await this.apPersonService.fetchPerson(dst.uri) ?? dst; @@ -364,7 +366,7 @@ export class AccountMoveService { if (!src) continue; // oldAccountを探してもこのサーバーに存在しない場合はフォロー関係もないということなのでスルー if (this.userEntityService.isRemoteUser(dst)) { - if (Date.now() - (src.lastFetchedAt?.getTime() ?? 0) > 10 * 1000) { + if (this.timeService.now - (src.lastFetchedAt?.getTime() ?? 0) > 10 * 1000) { await this.apPersonService.updatePerson(srcUri); } diff --git a/packages/backend/src/core/AchievementService.ts b/packages/backend/src/core/AchievementService.ts index 8d2de89efd..9da7843680 100644 --- a/packages/backend/src/core/AchievementService.ts +++ b/packages/backend/src/core/AchievementService.ts @@ -9,6 +9,7 @@ import type { MiUser } from '@/models/User.js'; import { DI } from '@/di-symbols.js'; import { bindThis } from '@/decorators.js'; import { NotificationService } from '@/core/NotificationService.js'; +import { TimeService } from '@/core/TimeService.js'; import { ACHIEVEMENT_TYPES } from '@/models/UserProfile.js'; @Injectable() @@ -18,6 +19,7 @@ export class AchievementService { private userProfilesRepository: UserProfilesRepository, private notificationService: NotificationService, + private readonly timeService: TimeService, ) { } @@ -28,7 +30,7 @@ export class AchievementService { ): Promise { if (!ACHIEVEMENT_TYPES.includes(type)) return; - const date = Date.now(); + const date = this.timeService.now; const profile = await this.userProfilesRepository.findOneByOrFail({ userId: userId }); diff --git a/packages/backend/src/core/AnnouncementService.ts b/packages/backend/src/core/AnnouncementService.ts index ddeea1eed6..177ea1d691 100644 --- a/packages/backend/src/core/AnnouncementService.ts +++ b/packages/backend/src/core/AnnouncementService.ts @@ -17,6 +17,7 @@ import { ModerationLogService } from '@/core/ModerationLogService.js'; import { IdentifiableError } from '@/misc/identifiable-error.js'; import type { Config } from '@/config.js'; import { RoleService } from '@/core/RoleService.js'; +import { TimeService } from '@/core/TimeService.js'; @Injectable() export class AnnouncementService { @@ -38,6 +39,7 @@ export class AnnouncementService { private moderationLogService: ModerationLogService, private announcementEntityService: AnnouncementEntityService, private roleService: RoleService, + private readonly timeService: TimeService, ) { } @@ -143,7 +145,7 @@ export class AnnouncementService { } await this.announcementsRepository.update(announcement.id, { - updatedAt: new Date(), + updatedAt: this.timeService.date, title: values.title, text: values.text, /* eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing -- 空の文字列の場合、nullを渡すようにするため */ diff --git a/packages/backend/src/core/ApLogService.ts b/packages/backend/src/core/ApLogService.ts index f21c6da313..aaddf2e4c7 100644 --- a/packages/backend/src/core/ApLogService.ts +++ b/packages/backend/src/core/ApLogService.ts @@ -12,6 +12,7 @@ import type { ApContextsRepository, ApFetchLogsRepository, ApInboxLogsRepository import type { Config } from '@/config.js'; import { JsonValue } from '@/misc/json-value.js'; import { UtilityService } from '@/core/UtilityService.js'; +import { TimeService } from '@/core/TimeService.js'; import { IdService } from '@/core/IdService.js'; import { IActivity, IObject } from './activitypub/type.js'; @@ -32,6 +33,7 @@ export class ApLogService { private readonly utilityService: UtilityService, private readonly idService: IdService, + private readonly timeService: TimeService, ) {} /** @@ -46,7 +48,7 @@ export class ApLogService { const log = new SkApInboxLog({ id: this.idService.gen(), - at: new Date(), + at: this.timeService.date, verified: false, accepted: false, host, @@ -85,7 +87,7 @@ export class ApLogService { }): Promise { const log = new SkApFetchLog({ id: this.idService.gen(), - at: new Date(), + at: this.timeService.date, accepted: false, ...data, }); @@ -163,7 +165,7 @@ export class ApLogService { */ public async deleteExpiredLogs(): Promise { // This is the date in UTC of the oldest log to KEEP - const oldestAllowed = new Date(Date.now() - this.config.activityLogging.maxAge); + const oldestAllowed = new Date(this.timeService.now - this.config.activityLogging.maxAge); // Delete all logs older than the threshold. const inboxDeleted = await this.deleteExpiredInboxLogs(oldestAllowed); diff --git a/packages/backend/src/core/AvatarDecorationService.ts b/packages/backend/src/core/AvatarDecorationService.ts index d811f6a28a..5eded086e6 100644 --- a/packages/backend/src/core/AvatarDecorationService.ts +++ b/packages/backend/src/core/AvatarDecorationService.ts @@ -15,6 +15,7 @@ import type { GlobalEvents } from '@/core/GlobalEventService.js'; import { ModerationLogService } from '@/core/ModerationLogService.js'; import { CacheManagementService, type ManagedMemorySingleCache } from '@/core/CacheManagementService.js'; import { InternalEventService } from '@/core/InternalEventService.js'; +import { TimeService } from '@/core/TimeService.js'; @Injectable() export class AvatarDecorationService implements OnApplicationShutdown { @@ -31,6 +32,8 @@ export class AvatarDecorationService implements OnApplicationShutdown { private moderationLogService: ModerationLogService, private globalEventService: GlobalEventService, private readonly internalEventService: InternalEventService, + private readonly timeService: TimeService, + cacheManagementService: CacheManagementService, ) { this.cache = cacheManagementService.createMemorySingleCache(1000 * 60 * 30); // 30s @@ -68,7 +71,7 @@ export class AvatarDecorationService implements OnApplicationShutdown { public async update(id: MiAvatarDecoration['id'], params: Partial, moderator?: MiUser): Promise { const avatarDecoration = await this.avatarDecorationsRepository.findOneByOrFail({ id }); - const date = new Date(); + const date = this.timeService.date; await this.avatarDecorationsRepository.update(avatarDecoration.id, { updatedAt: date, ...params, diff --git a/packages/backend/src/core/ChatService.ts b/packages/backend/src/core/ChatService.ts index 0db2c62872..b816a85857 100644 --- a/packages/backend/src/core/ChatService.ts +++ b/packages/backend/src/core/ChatService.ts @@ -28,6 +28,7 @@ import { CustomEmojiService } from '@/core/CustomEmojiService.js'; import { emojiRegex } from '@/misc/emoji-regex.js'; import { NotificationService } from '@/core/NotificationService.js'; import { ModerationLogService } from '@/core/ModerationLogService.js'; +import { TimeService } from '@/core/TimeService.js'; const MAX_ROOM_MEMBERS = 30; const MAX_REACTIONS_PER_MESSAGE = 100; @@ -91,6 +92,7 @@ export class ChatService { private userFollowingService: UserFollowingService, private customEmojiService: CustomEmojiService, private moderationLogService: ModerationLogService, + private readonly timeService: TimeService, ) { } @@ -225,7 +227,7 @@ export class ChatService { // 3秒経っても既読にならなかったらイベント発行 if (this.userEntityService.isLocalUser(toUser)) { - setTimeout(async () => { + this.timeService.startTimer(async () => { const marker = await this.redisClient.get(`newUserChatMessageExists:${toUser.id}:${fromUser.id}`); if (marker == null) return; // 既読 @@ -285,7 +287,7 @@ export class ChatService { redisPipeline.exec(); // 3秒経っても既読にならなかったらイベント発行 - setTimeout(async () => { + this.timeService.startTimer(async () => { const redisPipeline = this.redisClient.pipeline(); for (const membership of membershipsOtherThanMe) { redisPipeline.get(`newRoomChatMessageExists:${membership.userId}:${toRoom.id}`); diff --git a/packages/backend/src/core/ClipService.ts b/packages/backend/src/core/ClipService.ts index 929a9db064..93e215e8b7 100644 --- a/packages/backend/src/core/ClipService.ts +++ b/packages/backend/src/core/ClipService.ts @@ -12,6 +12,7 @@ import { isDuplicateKeyValueError } from '@/misc/is-duplicate-key-value-error.js import { RoleService } from '@/core/RoleService.js'; import { IdService } from '@/core/IdService.js'; import type { MiLocalUser } from '@/models/User.js'; +import { TimeService } from '@/core/TimeService.js'; @Injectable() export class ClipService { @@ -33,6 +34,7 @@ export class ClipService { private roleService: RoleService, private idService: IdService, + private readonly timeService: TimeService, ) { } @@ -125,7 +127,7 @@ export class ClipService { } this.clipsRepository.update(clip.id, { - lastClippedAt: new Date(), + lastClippedAt: this.timeService.date, }); this.notesRepository.increment({ id: noteId }, 'clippedCount', 1); diff --git a/packages/backend/src/core/CustomEmojiService.ts b/packages/backend/src/core/CustomEmojiService.ts index 969a23245b..ea2d4f1e2c 100644 --- a/packages/backend/src/core/CustomEmojiService.ts +++ b/packages/backend/src/core/CustomEmojiService.ts @@ -362,7 +362,7 @@ export class CustomEmojiService { await this.bulkUpdateEmojis(ids, async emojis => { for (const emoji of emojis) { await this.emojisRepository.update(emoji.id, { - updatedAt: new Date(), + updatedAt: this.timeService.date, aliases: [...new Set(emoji.aliases.concat(aliases))], }); } @@ -374,7 +374,7 @@ export class CustomEmojiService { await this.emojisRepository.update({ id: In(ids), }, { - updatedAt: new Date(), + updatedAt: this.timeService.date, aliases: aliases, }); @@ -386,7 +386,7 @@ export class CustomEmojiService { await this.bulkUpdateEmojis(ids, async emojis => { for (const emoji of emojis) { await this.emojisRepository.update(emoji.id, { - updatedAt: new Date(), + updatedAt: this.timeService.date, aliases: emoji.aliases.filter(x => !aliases.includes(x)), }); } @@ -398,7 +398,7 @@ export class CustomEmojiService { await this.emojisRepository.update({ id: In(ids), }, { - updatedAt: new Date(), + updatedAt: this.timeService.date, category: category, }); @@ -410,7 +410,7 @@ export class CustomEmojiService { await this.emojisRepository.update({ id: In(ids), }, { - updatedAt: new Date(), + updatedAt: this.timeService.date, license: license, }); diff --git a/packages/backend/src/core/FanoutTimelineService.ts b/packages/backend/src/core/FanoutTimelineService.ts index 24999bf4da..630b13d352 100644 --- a/packages/backend/src/core/FanoutTimelineService.ts +++ b/packages/backend/src/core/FanoutTimelineService.ts @@ -8,6 +8,7 @@ import * as Redis from 'ioredis'; import { DI } from '@/di-symbols.js'; import { bindThis } from '@/decorators.js'; import { IdService } from '@/core/IdService.js'; +import { TimeService } from '@/core/TimeService.js'; export type FanoutTimelineName = ( // home timeline @@ -46,6 +47,7 @@ export class FanoutTimelineService { private redisForTimelines: Redis.Redis, private idService: IdService, + private readonly timeService: TimeService, ) { } @@ -53,7 +55,7 @@ export class FanoutTimelineService { public push(tl: FanoutTimelineName, id: string, maxlen: number, pipeline: Redis.ChainableCommander) { // リモートから遅れて届いた(もしくは後から追加された)投稿日時が古い投稿が追加されるとページネーション時に問題を引き起こすため、 // 3分以内に投稿されたものでない場合、Redisにある最古のIDより新しい場合のみ追加する - if (this.idService.parse(id).date.getTime() > Date.now() - 1000 * 60 * 3) { + if (this.idService.parse(id).date.getTime() > this.timeService.now - 1000 * 60 * 3) { pipeline.lpush('list:' + tl, id); if (Math.random() < 0.1) { // 10%の確率でトリム pipeline.ltrim('list:' + tl, 0, maxlen - 1); diff --git a/packages/backend/src/core/FeaturedService.ts b/packages/backend/src/core/FeaturedService.ts index cabbb46504..d912f3349d 100644 --- a/packages/backend/src/core/FeaturedService.ts +++ b/packages/backend/src/core/FeaturedService.ts @@ -9,6 +9,7 @@ import type { MiGalleryPost, MiNote, MiUser } from '@/models/_.js'; import { DI } from '@/di-symbols.js'; import { bindThis } from '@/decorators.js'; import { RoleService } from '@/core/RoleService.js'; +import { TimeService } from '@/core/TimeService.js'; const GLOBAL_NOTES_RANKING_WINDOW = 1000 * 60 * 60 * 24 * 3; // 3日ごと export const GALLERY_POSTS_RANKING_WINDOW = 1000 * 60 * 60 * 24 * 3; // 3日ごと @@ -24,12 +25,13 @@ export class FeaturedService { private redisClient: Redis.Redis, // TODO: 専用のRedisサーバーを設定できるようにする private readonly roleService: RoleService, + private readonly timeService: TimeService, ) { } @bindThis private getCurrentWindow(windowRange: number): number { - const passed = new Date().getTime() - featuredEpoc; + const passed = this.timeService.now - featuredEpoc; return Math.floor(passed / windowRange); } diff --git a/packages/backend/src/core/FederatedInstanceService.ts b/packages/backend/src/core/FederatedInstanceService.ts index 94958534b7..9451bfa345 100644 --- a/packages/backend/src/core/FederatedInstanceService.ts +++ b/packages/backend/src/core/FederatedInstanceService.ts @@ -16,6 +16,7 @@ import { CacheManagementService, type ManagedQuantumKVCache } from '@/core/Cache import { InternalEventService } from '@/core/InternalEventService.js'; import { diffArraysSimple } from '@/misc/diff-arrays.js'; import { bindThis } from '@/decorators.js'; +import { TimeService } from '@/core/TimeService.js'; import type { QueryDeepPartialEntity } from 'typeorm/query-builder/QueryPartialEntity.js'; @Injectable() @@ -32,6 +33,7 @@ export class FederatedInstanceService implements OnApplicationShutdown { private utilityService: UtilityService, private idService: IdService, private readonly internalEventService: InternalEventService, + private readonly timeService: TimeService, cacheManagementService: CacheManagementService, ) { @@ -47,7 +49,7 @@ export class FederatedInstanceService implements OnApplicationShutdown { .values({ id: this.idService.gen(), host, - firstRetrievedAt: new Date(), + firstRetrievedAt: this.timeService.date, isBlocked: this.utilityService.isBlockedHost(host), isSilenced: this.utilityService.isSilencedHost(host), isMediaSilenced: this.utilityService.isMediaSilencedHost(host), @@ -87,7 +89,7 @@ export class FederatedInstanceService implements OnApplicationShutdown { .values({ id: this.idService.gen(), host, - firstRetrievedAt: new Date(), + firstRetrievedAt: this.timeService.date, isBlocked: this.utilityService.isBlockedHost(host), isSilenced: this.utilityService.isSilencedHost(host), isMediaSilenced: this.utilityService.isMediaSilencedHost(host), diff --git a/packages/backend/src/core/HashtagService.ts b/packages/backend/src/core/HashtagService.ts index 5afeb1131b..ef983a084d 100644 --- a/packages/backend/src/core/HashtagService.ts +++ b/packages/backend/src/core/HashtagService.ts @@ -15,6 +15,7 @@ import type { HashtagsRepository, MiMeta } from '@/models/_.js'; import { bindThis } from '@/decorators.js'; import { FeaturedService } from '@/core/FeaturedService.js'; import { UtilityService } from '@/core/UtilityService.js'; +import { TimeService } from '@/core/TimeService.js'; @Injectable() export class HashtagService { @@ -31,6 +32,7 @@ export class HashtagService { private featuredService: FeaturedService, private idService: IdService, private utilityService: UtilityService, + private readonly timeService: TimeService, ) { } @@ -165,7 +167,7 @@ export class HashtagService { if (this.utilityService.isKeyWordIncluded(hashtag, this.meta.sensitiveWords)) return; // YYYYMMDDHHmm (10分間隔) - const now = new Date(); + const now = this.timeService.date; now.setMinutes(Math.floor(now.getMinutes() / 10) * 10, 0, 0); const window = `${now.getUTCFullYear()}${(now.getUTCMonth() + 1).toString().padStart(2, '0')}${now.getUTCDate().toString().padStart(2, '0')}${now.getUTCHours().toString().padStart(2, '0')}${now.getUTCMinutes().toString().padStart(2, '0')}`; @@ -196,7 +198,7 @@ export class HashtagService { @bindThis public async getChart(hashtag: string, range: number): Promise { - const now = new Date(); + const now = this.timeService.date; now.setMinutes(Math.floor(now.getMinutes() / 10) * 10, 0, 0); const redisPipeline = this.redisClient.pipeline(); @@ -216,7 +218,7 @@ export class HashtagService { @bindThis public async getCharts(hashtags: string[], range: number): Promise> { - const now = new Date(); + const now = this.timeService.date; now.setMinutes(Math.floor(now.getMinutes() / 10) * 10, 0, 0); const redisPipeline = this.redisClient.pipeline(); diff --git a/packages/backend/src/core/HttpRequestService.ts b/packages/backend/src/core/HttpRequestService.ts index bd72fefe4f..3b3e72d2e3 100644 --- a/packages/backend/src/core/HttpRequestService.ts +++ b/packages/backend/src/core/HttpRequestService.ts @@ -19,6 +19,7 @@ import { validateContentTypeSetAsActivityPub } from '@/core/activitypub/misc/val import type { IObject, IObjectWithId } from '@/core/activitypub/type.js'; import { UtilityService } from '@/core/UtilityService.js'; import { ApUtilityService } from '@/core/activitypub/ApUtilityService.js'; +import { TimeService } from '@/core/TimeService.js'; import type { Response } from 'node-fetch'; import type { Socket } from 'node:net'; @@ -156,8 +157,10 @@ export class HttpRequestService { constructor( @Inject(DI.config) private config: Config, + private readonly apUtilityService: ApUtilityService, private readonly utilityService: UtilityService, + private readonly timeService: TimeService, ) { const cache = new CacheableLookup({ maxTtl: 3600, // 1hours @@ -343,7 +346,7 @@ export class HttpRequestService { this.utilityService.assertUrl(parsedUrl, allowHttp); const controller = new AbortController(); - setTimeout(() => { + this.timeService.startTimer(() => { controller.abort(); }, timeout); diff --git a/packages/backend/src/core/LoggerService.ts b/packages/backend/src/core/LoggerService.ts index 25721f0630..a8c2f3a1ea 100644 --- a/packages/backend/src/core/LoggerService.ts +++ b/packages/backend/src/core/LoggerService.ts @@ -5,6 +5,7 @@ import { Inject, Injectable } from '@nestjs/common'; import Logger from '@/logger.js'; +import { TimeService } from '@/core/TimeService.js'; import { bindThis } from '@/decorators.js'; import type { KEYWORD } from 'color-convert/conversions.js'; import { envOption } from '@/env.js'; @@ -16,12 +17,13 @@ export class LoggerService { constructor( @Inject(DI.config) private config: Config, + private readonly timeService: TimeService, ) { } @bindThis public getLogger(domain: string, color?: KEYWORD | undefined) { const verbose = this.config.logging?.verbose || envOption.verbose; - return new Logger(domain, color, verbose); + return new Logger(domain, color, verbose, undefined, this.timeService); } } diff --git a/packages/backend/src/core/MetaService.ts b/packages/backend/src/core/MetaService.ts index 07f82dc23e..ef8cf270a2 100644 --- a/packages/backend/src/core/MetaService.ts +++ b/packages/backend/src/core/MetaService.ts @@ -12,6 +12,7 @@ import { GlobalEventService } from '@/core/GlobalEventService.js'; import { bindThis } from '@/decorators.js'; import type { GlobalEvents } from '@/core/GlobalEventService.js'; import { FeaturedService } from '@/core/FeaturedService.js'; +import { TimeService, type TimerHandle } from '@/core/TimeService.js'; import { MiInstance } from '@/models/Instance.js'; import { diffArrays } from '@/misc/diff-arrays.js'; import type { MetasRepository } from '@/models/_.js'; @@ -20,7 +21,7 @@ import type { OnApplicationShutdown } from '@nestjs/common'; @Injectable() export class MetaService implements OnApplicationShutdown { private cache: MiMeta | undefined; - private intervalId: NodeJS.Timeout; + private intervalId: TimerHandle; constructor( @Inject(DI.redisForSub) @@ -34,16 +35,17 @@ export class MetaService implements OnApplicationShutdown { private featuredService: FeaturedService, private globalEventService: GlobalEventService, + private readonly timeService: TimeService, ) { //this.onMessage = this.onMessage.bind(this); if (process.env.NODE_ENV !== 'test') { - this.intervalId = setInterval(() => { + this.intervalId = this.timeService.startTimer(() => { this.fetch(true).then(meta => { // fetch内でもセットしてるけど仕様変更の可能性もあるため一応 this.cache = meta; }); - }, 1000 * 60 * 5); + }, 1000 * 60 * 5, { repeated: true }); } this.redisForSub.on('message', this.onMessage); @@ -161,7 +163,7 @@ export class MetaService implements OnApplicationShutdown { @bindThis public dispose(): void { - clearInterval(this.intervalId); + this.timeService.stopTimer(this.intervalId); this.redisForSub.off('message', this.onMessage); } diff --git a/packages/backend/src/core/NoteCreateService.ts b/packages/backend/src/core/NoteCreateService.ts index 7771c08544..ef75644ae7 100644 --- a/packages/backend/src/core/NoteCreateService.ts +++ b/packages/backend/src/core/NoteCreateService.ts @@ -57,6 +57,7 @@ import { IdentifiableError } from '@/misc/identifiable-error.js'; import { LatestNoteService } from '@/core/LatestNoteService.js'; import { CollapsedQueue } from '@/misc/collapsed-queue.js'; import { CacheService } from '@/core/CacheService.js'; +import { TimeService } from '@/core/TimeService.js'; import { NoteVisibilityService } from '@/core/NoteVisibilityService.js'; import { isPureRenote } from '@/misc/is-renote.js'; @@ -224,9 +225,10 @@ export class NoteCreateService implements OnApplicationShutdown { private userBlockingService: UserBlockingService, private cacheService: CacheService, private latestNoteService: LatestNoteService, + private readonly timeService: TimeService, private readonly noteVisibilityService: NoteVisibilityService, ) { - this.updateNotesCountQueue = new CollapsedQueue(process.env.NODE_ENV !== 'test' ? 60 * 1000 * 5 : 0, this.collapseNotesCount, this.performUpdateNotesCount); + this.updateNotesCountQueue = new CollapsedQueue(this.timeService, process.env.NODE_ENV !== 'test' ? 60 * 1000 * 5 : 0, this.collapseNotesCount, this.performUpdateNotesCount); } @bindThis @@ -253,7 +255,7 @@ export class NoteCreateService implements OnApplicationShutdown { data.channel = await this.channelsRepository.findOneBy({ id: data.reply.channelId }); } - if (data.createdAt == null) data.createdAt = new Date(); + if (data.createdAt == null) data.createdAt = this.timeService.date; if (data.visibility == null) data.visibility = 'public'; if (data.localOnly == null) data.localOnly = false; if (data.channel != null) data.visibility = 'public'; @@ -613,7 +615,7 @@ export class NoteCreateService implements OnApplicationShutdown { // Increment notes count (user) this.incNotesCountOfUser(user); } else { - this.usersRepository.update({ id: user.id }, { updatedAt: new Date() }); + this.usersRepository.update({ id: user.id }, { updatedAt: this.timeService.date }); } this.pushToTl(note, user); @@ -657,7 +659,7 @@ export class NoteCreateService implements OnApplicationShutdown { } if (data.poll && data.poll.expiresAt) { - const delay = data.poll.expiresAt.getTime() - Date.now(); + const delay = data.poll.expiresAt.getTime() - this.timeService.now; this.queueService.endedPollNotificationQueue.add(note.id, { noteId: note.id, }, { @@ -791,7 +793,7 @@ export class NoteCreateService implements OnApplicationShutdown { if (data.channel) { this.channelsRepository.increment({ id: data.channel.id }, 'notesCount', 1); this.channelsRepository.update(data.channel.id, { - lastNotedAt: new Date(), + lastNotedAt: this.timeService.date, }); this.notesRepository.countBy({ @@ -838,7 +840,7 @@ export class NoteCreateService implements OnApplicationShutdown { .execute(); // 30%の確率、3日以内に投稿されたノートの場合ハイライト用ランキング更新 - if (user.isExplorable && Math.random() < 0.3 && (Date.now() - this.idService.parse(renote.id).date.getTime()) < 1000 * 60 * 60 * 24 * 3) { + if (user.isExplorable && Math.random() < 0.3 && (this.timeService.now - this.idService.parse(renote.id).date.getTime()) < 1000 * 60 * 60 * 24 * 3) { const policies = await this.roleService.getUserPolicies(user); if (policies.canTrend) { if (renote.channelId != null) { @@ -905,7 +907,7 @@ export class NoteCreateService implements OnApplicationShutdown { private incNotesCountOfUser(user: { id: MiUser['id']; }) { this.usersRepository.createQueryBuilder().update() .set({ - updatedAt: new Date(), + updatedAt: this.timeService.date, notesCount: () => '"notesCount" + 1', }) .where('id = :id', { id: user.id }) @@ -1064,7 +1066,7 @@ export class NoteCreateService implements OnApplicationShutdown { const hibernatedUsers = await this.usersRepository.find({ where: { id: In(samples.map(x => x.followerId)), - lastActiveDate: LessThan(new Date(Date.now() - (1000 * 60 * 60 * 24 * 50))), + lastActiveDate: LessThan(new Date(this.timeService.now - (1000 * 60 * 60 * 24 * 50))), }, select: ['id'], }); diff --git a/packages/backend/src/core/NoteDeleteService.ts b/packages/backend/src/core/NoteDeleteService.ts index 69811e6197..8b50e1c882 100644 --- a/packages/backend/src/core/NoteDeleteService.ts +++ b/packages/backend/src/core/NoteDeleteService.ts @@ -26,7 +26,8 @@ import { isQuote, isRenote } from '@/misc/is-renote.js'; import { LatestNoteService } from '@/core/LatestNoteService.js'; import { ApLogService } from '@/core/ApLogService.js'; import type Logger from '@/logger.js'; -import { LoggerService } from './LoggerService.js'; +import { TimeService } from '@/core/TimeService.js'; +import { LoggerService } from '@/core/LoggerService.js'; @Injectable() export class NoteDeleteService { @@ -60,6 +61,8 @@ export class NoteDeleteService { private instanceChart: InstanceChart, private latestNoteService: LatestNoteService, private readonly apLogService: ApLogService, + private readonly timeService: TimeService, + loggerService: LoggerService, ) { this.logger = loggerService.getLogger('note-delete-service'); @@ -71,7 +74,7 @@ export class NoteDeleteService { * @param note 投稿 */ async delete(user: { id: MiUser['id']; uri: MiUser['uri']; host: MiUser['host']; isBot: MiUser['isBot']; }, note: MiNote, quiet = false, deleter?: MiUser) { - const deletedAt = new Date(); + const deletedAt = this.timeService.date; const cascadingNotes = await this.findCascadingNotes(note); if (note.replyId) { @@ -127,7 +130,7 @@ export class NoteDeleteService { // Decrement notes count (user) this.decNotesCountOfUser(user); } else { - this.usersRepository.update({ id: user.id }, { updatedAt: new Date() }); + this.usersRepository.update({ id: user.id }, { updatedAt: this.timeService.date }); } if (this.meta.enableStatsForFederatedInstances) { @@ -179,7 +182,7 @@ export class NoteDeleteService { private decNotesCountOfUser(user: { id: MiUser['id']; }) { this.usersRepository.createQueryBuilder().update() .set({ - updatedAt: new Date(), + updatedAt: this.timeService.date, notesCount: () => '"notesCount" - 1', }) .where('id = :id', { id: user.id }) diff --git a/packages/backend/src/core/NoteEditService.ts b/packages/backend/src/core/NoteEditService.ts index 8857e022c3..3a1064c95f 100644 --- a/packages/backend/src/core/NoteEditService.ts +++ b/packages/backend/src/core/NoteEditService.ts @@ -52,6 +52,7 @@ import { IdentifiableError } from '@/misc/identifiable-error.js'; import { LatestNoteService } from '@/core/LatestNoteService.js'; import { CollapsedQueue } from '@/misc/collapsed-queue.js'; import { NoteCreateService } from '@/core/NoteCreateService.js'; +import { TimeService } from '@/core/TimeService.js'; import { NoteVisibilityService } from '@/core/NoteVisibilityService.js'; import { isPureRenote } from '@/misc/is-renote.js'; @@ -221,9 +222,10 @@ export class NoteEditService implements OnApplicationShutdown { private cacheService: CacheService, private latestNoteService: LatestNoteService, private noteCreateService: NoteCreateService, + private readonly timeService: TimeService, private readonly noteVisibilityService: NoteVisibilityService, ) { - this.updateNotesCountQueue = new CollapsedQueue(process.env.NODE_ENV !== 'test' ? 60 * 1000 * 5 : 0, this.collapseNotesCount, this.performUpdateNotesCount); + this.updateNotesCountQueue = new CollapsedQueue(this.timeService, process.env.NODE_ENV !== 'test' ? 60 * 1000 * 5 : 0, this.collapseNotesCount, this.performUpdateNotesCount); } @bindThis @@ -272,7 +274,7 @@ export class NoteEditService implements OnApplicationShutdown { data.channel = await this.channelsRepository.findOneBy({ id: data.reply.channelId }); } - if (data.updatedAt == null) data.updatedAt = new Date(); + if (data.updatedAt == null) data.updatedAt = this.timeService.date; if (data.visibility == null) data.visibility = 'public'; if (data.localOnly == null) data.localOnly = false; if (data.channel != null) data.visibility = 'public'; @@ -493,12 +495,12 @@ export class NoteEditService implements OnApplicationShutdown { cw: update.cw || undefined, fileIds: undefined, oldDate: exists ? oldnote.updatedAt as Date : this.idService.parse(oldnote.id).date, - updatedAt: new Date(), + updatedAt: this.timeService.date, }); const note = new MiNote({ id: oldnote.id, - updatedAt: data.updatedAt ? data.updatedAt : new Date(), + updatedAt: data.updatedAt ? data.updatedAt : this.timeService.date, fileIds: data.files ? data.files.map(file => file.id) : [], replyId: oldnote.replyId, renoteId: data.renote ? data.renote.id : null, @@ -619,13 +621,13 @@ export class NoteEditService implements OnApplicationShutdown { } } - this.usersRepository.update({ id: user.id }, { updatedAt: new Date() }); + this.usersRepository.update({ id: user.id }, { updatedAt: this.timeService.date }); // ハッシュタグ更新 this.pushToTl(note, user); if (data.poll && data.poll.expiresAt) { - const delay = data.poll.expiresAt.getTime() - Date.now(); + const delay = data.poll.expiresAt.getTime() - this.timeService.now; this.queueService.endedPollNotificationQueue.remove(`pollEnd:${note.id}`); this.queueService.endedPollNotificationQueue.add(note.id, { noteId: note.id, @@ -737,7 +739,7 @@ export class NoteEditService implements OnApplicationShutdown { if (data.channel) { this.channelsRepository.increment({ id: data.channel.id }, 'notesCount', 1); this.channelsRepository.update(data.channel.id, { - lastNotedAt: new Date(), + lastNotedAt: this.timeService.date, }); this.notesRepository.countBy({ @@ -935,7 +937,7 @@ export class NoteEditService implements OnApplicationShutdown { const hibernatedUsers = await this.usersRepository.find({ where: { id: In(samples.map(x => x.followerId)), - lastActiveDate: LessThan(new Date(Date.now() - (1000 * 60 * 60 * 24 * 50))), + lastActiveDate: LessThan(new Date(this.timeService.now - (1000 * 60 * 60 * 24 * 50))), }, select: ['id'], }); diff --git a/packages/backend/src/core/NoteVisibilityService.ts b/packages/backend/src/core/NoteVisibilityService.ts index c43af9f94a..c6180b7de9 100644 --- a/packages/backend/src/core/NoteVisibilityService.ts +++ b/packages/backend/src/core/NoteVisibilityService.ts @@ -14,6 +14,7 @@ import type { Packed } from '@/misc/json-schema.js'; import { FederatedInstanceService } from '@/core/FederatedInstanceService.js'; import { CacheService } from '@/core/CacheService.js'; import { IdService } from '@/core/IdService.js'; +import { TimeService } from '@/core/TimeService.js'; import { bindThis } from '@/decorators.js'; import { awaitAll } from '@/misc/prelude/await-all.js'; import { DI } from '@/di-symbols.js'; @@ -70,6 +71,7 @@ export class NoteVisibilityService { private readonly cacheService: CacheService, private readonly idService: IdService, private readonly federatedInstanceService: FederatedInstanceService, + private readonly timeService: TimeService, ) {} @bindThis @@ -293,7 +295,7 @@ export class NoteVisibilityService { const createdAt = new Date(note.createdAt).valueOf(); // I don't understand this logic, but I tried to break it out for readability - const followersOnlyOpt1 = followersOnlyBefore <= 0 && (Date.now() - createdAt > 0 - followersOnlyBefore); + const followersOnlyOpt1 = followersOnlyBefore <= 0 && (this.timeService.now - createdAt > 0 - followersOnlyBefore); const followersOnlyOpt2 = followersOnlyBefore > 0 && (createdAt < followersOnlyBefore); if (followersOnlyOpt1 || followersOnlyOpt2) { note.visibility = 'followers'; @@ -323,7 +325,7 @@ export class NoteVisibilityService { const createdAt = note.createdAt.valueOf(); // I don't understand this logic, but I tried to break it out for readability - const hiddenOpt1 = hiddenBefore <= 0 && (Date.now() - createdAt > 0 - hiddenBefore); + const hiddenOpt1 = hiddenBefore <= 0 && (this.timeService.now - createdAt > 0 - hiddenBefore); const hiddenOpt2 = hiddenBefore > 0 && (createdAt < hiddenBefore); if (hiddenOpt1 || hiddenOpt2) return true; } diff --git a/packages/backend/src/core/NotificationService.ts b/packages/backend/src/core/NotificationService.ts index 89b9e9dfed..0f66087602 100644 --- a/packages/backend/src/core/NotificationService.ts +++ b/packages/backend/src/core/NotificationService.ts @@ -22,6 +22,7 @@ import type { Config } from '@/config.js'; import { UserListService } from '@/core/UserListService.js'; import { FilterUnionByProperty, groupedNotificationTypes, obsoleteNotificationTypes } from '@/types.js'; import { trackPromise } from '@/misc/promise-tracker.js'; +import { TimeService } from '@/core/TimeService.js'; @Injectable() export class NotificationService implements OnApplicationShutdown { @@ -43,6 +44,7 @@ export class NotificationService implements OnApplicationShutdown { private pushNotificationService: PushNotificationService, private cacheService: CacheService, private userListService: UserListService, + private readonly timeService: TimeService, ) { } @@ -146,7 +148,7 @@ export class NotificationService implements OnApplicationShutdown { } } - const createdAt = new Date(); + const createdAt = this.timeService.date; let notification: FilterUnionByProperty; let redisId: string; @@ -187,7 +189,7 @@ export class NotificationService implements OnApplicationShutdown { // 2秒経っても(今回作成した)通知が既読にならなかったら「未読の通知がありますよ」イベントを発行する // テスト通知の場合は即時発行 const interval = notification.type === 'test' ? 0 : 2000; - setTimeout(interval, 'unread notification', { signal: this.#shutdownController.signal }).then(async () => { + this.timeService.startPromiseTimer(interval, 'unread notification', { signal: this.#shutdownController.signal }).then(async () => { const latestReadNotificationId = await this.redisClient.get(`latestReadNotification:${notifieeId}`); if (latestReadNotificationId && (latestReadNotificationId >= redisId)) return; diff --git a/packages/backend/src/core/PushNotificationService.ts b/packages/backend/src/core/PushNotificationService.ts index cbb05ce931..8ead513671 100644 --- a/packages/backend/src/core/PushNotificationService.ts +++ b/packages/backend/src/core/PushNotificationService.ts @@ -13,6 +13,7 @@ import { getNoteSummary } from '@/misc/get-note-summary.js'; import type { MiMeta, MiSwSubscription, SwSubscriptionsRepository } from '@/models/_.js'; import { bindThis } from '@/decorators.js'; import { CacheManagementService, type ManagedQuantumKVCache } from '@/core/CacheManagementService.js'; +import { TimeService } from '@/core/TimeService.js'; // Defined also packages/sw/types.ts#L13 type PushNotificationsTypes = { @@ -62,6 +63,9 @@ export class PushNotificationService { @Inject(DI.swSubscriptionsRepository) private swSubscriptionsRepository: SwSubscriptionsRepository, + + private readonly timeService: TimeService, + cacheManagementService: CacheManagementService, ) { this.subscriptionsCache = cacheManagementService.createQuantumKVCache('userSwSubscriptions', { @@ -98,7 +102,7 @@ export class PushNotificationService { type, body: (type === 'notification' || type === 'unreadAntennaNote') ? truncateBody(type, body) : body, userId, - dateTime: Date.now(), + dateTime: this.timeService.now, }), { proxy: this.config.proxy, }).catch((err: any) => { diff --git a/packages/backend/src/core/QueueService.ts b/packages/backend/src/core/QueueService.ts index f80c968cb8..486af69f3a 100644 --- a/packages/backend/src/core/QueueService.ts +++ b/packages/backend/src/core/QueueService.ts @@ -16,6 +16,7 @@ import { DI } from '@/di-symbols.js'; import { bindThis } from '@/decorators.js'; import type { Antenna } from '@/server/api/endpoints/i/import-antennas.js'; import { ApRequestCreator } from '@/core/activitypub/ApRequestService.js'; +import { TimeService } from '@/core/TimeService.js'; import type { SystemWebhookPayload } from '@/core/SystemWebhookService.js'; import type { MiNote } from '@/models/Note.js'; import { type UserWebhookPayload } from './UserWebhookService.js'; @@ -71,6 +72,8 @@ export class QueueService implements OnModuleInit { @Inject('queue:userWebhookDeliver') public userWebhookDeliverQueue: UserWebhookDeliverQueue, @Inject('queue:systemWebhookDeliver') public systemWebhookDeliverQueue: SystemWebhookDeliverQueue, @Inject('queue:scheduleNotePost') public ScheduleNotePostQueue: ScheduleNotePostQueue, + + private readonly timeService: TimeService, ) {} @bindThis @@ -791,7 +794,7 @@ export class QueueService implements OnModuleInit { userId: webhook.userId, to: webhook.url, secret: webhook.secret, - createdAt: Date.now(), + createdAt: this.timeService.now, eventId: randomUUID(), }; @@ -828,7 +831,7 @@ export class QueueService implements OnModuleInit { webhookId: webhook.id, to: webhook.url, secret: webhook.secret, - createdAt: Date.now(), + createdAt: this.timeService.now, eventId: randomUUID(), }; diff --git a/packages/backend/src/core/ReactionService.ts b/packages/backend/src/core/ReactionService.ts index 4abc953356..b4ed595095 100644 --- a/packages/backend/src/core/ReactionService.ts +++ b/packages/backend/src/core/ReactionService.ts @@ -32,6 +32,7 @@ import { ReactionsBufferingService } from '@/core/ReactionsBufferingService.js'; import { PER_NOTE_REACTION_USER_PAIR_CACHE_MAX } from '@/const.js'; import { CacheService } from '@/core/CacheService.js'; import { NoteVisibilityService } from '@/core/NoteVisibilityService.js'; +import { TimeService } from '@/core/TimeService.js'; import type { DataSource } from 'typeorm'; const FALLBACK = '\u2764'; @@ -108,6 +109,7 @@ export class ReactionService implements OnModuleInit { private perUserReactionsChart: PerUserReactionsChart, private readonly cacheService: CacheService, private readonly noteVisibilityService: NoteVisibilityService, + private readonly timeService: TimeService, ) { } @@ -222,13 +224,13 @@ export class ReactionService implements OnModuleInit { .execute(); } - this.usersRepository.update({ id: user.id }, { updatedAt: new Date() }); + this.usersRepository.update({ id: user.id }, { updatedAt: this.timeService.date }); // 30%の確率、セルフではない、3日以内に投稿されたノートの場合ハイライト用ランキング更新 if ( Math.random() < 0.3 && note.userId !== user.id && - (Date.now() - this.idService.parse(note.id).date.getTime()) < 1000 * 60 * 60 * 24 * 3 + (this.timeService.now - this.idService.parse(note.id).date.getTime()) < 1000 * 60 * 60 * 24 * 3 ) { const author = await this.cacheService.findUserById(note.userId); if (author.isExplorable) { @@ -338,7 +340,7 @@ export class ReactionService implements OnModuleInit { .execute(); } - this.usersRepository.update({ id: user.id }, { updatedAt: new Date() }); + this.usersRepository.update({ id: user.id }, { updatedAt: this.timeService.date }); this.globalEventService.publishNoteStream(note.id, 'unreacted', { reaction: this.decodeReaction(exist.reaction).reaction, diff --git a/packages/backend/src/core/ReactionsBufferingService.ts b/packages/backend/src/core/ReactionsBufferingService.ts index b4207c5106..d1f13d6304 100644 --- a/packages/backend/src/core/ReactionsBufferingService.ts +++ b/packages/backend/src/core/ReactionsBufferingService.ts @@ -6,6 +6,7 @@ import { Inject, Injectable } from '@nestjs/common'; import * as Redis from 'ioredis'; import { DI } from '@/di-symbols.js'; +import { TimeService } from '@/core/TimeService.js'; import type { MiNote } from '@/models/Note.js'; import { bindThis } from '@/decorators.js'; import type { MiUser, NotesRepository } from '@/models/_.js'; @@ -31,6 +32,8 @@ export class ReactionsBufferingService implements OnApplicationShutdown { @Inject(DI.notesRepository) private notesRepository: NotesRepository, + + private readonly timeService: TimeService, ) { this.redisForSub.on('message', this.onMessage); } @@ -62,7 +65,7 @@ export class ReactionsBufferingService implements OnApplicationShutdown { for (let i = 0; i < currentPairs.length; i++) { pipeline.zadd(`${REDIS_PAIR_PREFIX}:${noteId}`, i, currentPairs[i]); } - pipeline.zadd(`${REDIS_PAIR_PREFIX}:${noteId}`, Date.now(), `${userId}/${reaction}`); + pipeline.zadd(`${REDIS_PAIR_PREFIX}:${noteId}`, this.timeService.now, `${userId}/${reaction}`); pipeline.zremrangebyrank(`${REDIS_PAIR_PREFIX}:${noteId}`, 0, -(PER_NOTE_REACTION_USER_PAIR_CACHE_MAX + 1)); await pipeline.exec(); } diff --git a/packages/backend/src/core/RegistryApiService.ts b/packages/backend/src/core/RegistryApiService.ts index 2c7ad4026d..12cf386199 100644 --- a/packages/backend/src/core/RegistryApiService.ts +++ b/packages/backend/src/core/RegistryApiService.ts @@ -11,6 +11,7 @@ import type { MiUser } from '@/models/User.js'; import { IdService } from '@/core/IdService.js'; import { GlobalEventService } from '@/core/GlobalEventService.js'; import { bindThis } from '@/decorators.js'; +import { TimeService } from '@/core/TimeService.js'; @Injectable() export class RegistryApiService { @@ -20,6 +21,7 @@ export class RegistryApiService { private idService: IdService, private globalEventService: GlobalEventService, + private readonly timeService: TimeService, ) { } @@ -31,7 +33,7 @@ export class RegistryApiService { .insert() .values({ id: this.idService.gen(), - updatedAt: new Date(), + updatedAt: this.timeService.date, userId: userId, domain: domain, scope: scope, diff --git a/packages/backend/src/core/RemoteUserResolveService.ts b/packages/backend/src/core/RemoteUserResolveService.ts index 4dbc9d6a36..dcea9c4f98 100644 --- a/packages/backend/src/core/RemoteUserResolveService.ts +++ b/packages/backend/src/core/RemoteUserResolveService.ts @@ -16,6 +16,7 @@ import { ILink, WebfingerService } from '@/core/WebfingerService.js'; import { RemoteLoggerService } from '@/core/RemoteLoggerService.js'; import { ApDbResolverService } from '@/core/activitypub/ApDbResolverService.js'; import { ApPersonService } from '@/core/activitypub/models/ApPersonService.js'; +import { TimeService } from '@/core/TimeService.js'; import { bindThis } from '@/decorators.js'; import { renderInlineError } from '@/misc/render-inline-error.js'; @@ -35,6 +36,7 @@ export class RemoteUserResolveService { private remoteLoggerService: RemoteLoggerService, private apDbResolverService: ApDbResolverService, private apPersonService: ApPersonService, + private readonly timeService: TimeService, ) { this.logger = this.remoteLoggerService.logger.createSubLogger('resolve-user'); } @@ -81,10 +83,10 @@ export class RemoteUserResolveService { } // ユーザー情報が古い場合は、WebFingerからやりなおして返す - if (user.lastFetchedAt == null || Date.now() - user.lastFetchedAt.getTime() > 1000 * 60 * 60 * 24) { + if (user.lastFetchedAt == null || this.timeService.now - user.lastFetchedAt.getTime() > 1000 * 60 * 60 * 24) { // 繋がらないインスタンスに何回も試行するのを防ぐ, 後続の同様処理の連続試行を防ぐ ため 試行前にも更新する await this.usersRepository.update(user.id, { - lastFetchedAt: new Date(), + lastFetchedAt: this.timeService.date, }); const self = await this.resolveSelf(acctLower); diff --git a/packages/backend/src/core/ReversiService.ts b/packages/backend/src/core/ReversiService.ts index a55d96086f..6a1a0c009b 100644 --- a/packages/backend/src/core/ReversiService.ts +++ b/packages/backend/src/core/ReversiService.ts @@ -19,6 +19,7 @@ import { bindThis } from '@/decorators.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; import { GlobalEventService } from '@/core/GlobalEventService.js'; import { IdService } from '@/core/IdService.js'; +import { TimeService } from '@/core/TimeService.js'; import type { NotificationService } from '@/core/NotificationService.js'; import { Serialized } from '@/types.js'; import { ReversiGameEntityService } from './entities/ReversiGameEntityService.js'; @@ -43,6 +44,7 @@ export class ReversiService implements OnApplicationShutdown, OnModuleInit { private globalEventService: GlobalEventService, private reversiGameEntityService: ReversiGameEntityService, private idService: IdService, + private readonly timeService: TimeService, ) { } @@ -99,8 +101,8 @@ export class ReversiService implements OnApplicationShutdown, OnModuleInit { // 既にマッチしている対局が無いか探す(3分以内) const games = await this.reversiGamesRepository.find({ where: [ - { id: MoreThan(this.idService.gen(Date.now() - 1000 * 60 * 3)), user1Id: me.id, user2Id: targetUser.id, isStarted: false }, - { id: MoreThan(this.idService.gen(Date.now() - 1000 * 60 * 3)), user1Id: targetUser.id, user2Id: me.id, isStarted: false }, + { id: MoreThan(this.idService.gen(this.timeService.now - 1000 * 60 * 3)), user1Id: me.id, user2Id: targetUser.id, isStarted: false }, + { id: MoreThan(this.idService.gen(this.timeService.now - 1000 * 60 * 3)), user1Id: targetUser.id, user2Id: me.id, isStarted: false }, ], relations: ['user1', 'user2'], order: { id: 'DESC' }, @@ -113,7 +115,7 @@ export class ReversiService implements OnApplicationShutdown, OnModuleInit { //#region 相手から既に招待されてないか確認 const invitations = await this.redisClient.zrange( `reversi:matchSpecific:${me.id}`, - Date.now() - INVITATION_TIMEOUT_MS, + this.timeService.now - INVITATION_TIMEOUT_MS, '+inf', 'BYSCORE'); @@ -129,7 +131,7 @@ export class ReversiService implements OnApplicationShutdown, OnModuleInit { //#endregion const redisPipeline = this.redisClient.pipeline(); - redisPipeline.zadd(`reversi:matchSpecific:${targetUser.id}`, Date.now(), me.id); + redisPipeline.zadd(`reversi:matchSpecific:${targetUser.id}`, this.timeService.now, me.id); redisPipeline.expire(`reversi:matchSpecific:${targetUser.id}`, 120, 'NX'); await redisPipeline.exec(); @@ -146,8 +148,8 @@ export class ReversiService implements OnApplicationShutdown, OnModuleInit { // 既にマッチしている対局が無いか探す(3分以内) const games = await this.reversiGamesRepository.find({ where: [ - { id: MoreThan(this.idService.gen(Date.now() - 1000 * 60 * 3)), user1Id: me.id, isStarted: false }, - { id: MoreThan(this.idService.gen(Date.now() - 1000 * 60 * 3)), user2Id: me.id, isStarted: false }, + { id: MoreThan(this.idService.gen(this.timeService.now - 1000 * 60 * 3)), user1Id: me.id, isStarted: false }, + { id: MoreThan(this.idService.gen(this.timeService.now - 1000 * 60 * 3)), user2Id: me.id, isStarted: false }, ], relations: ['user1', 'user2'], order: { id: 'DESC' }, @@ -160,7 +162,7 @@ export class ReversiService implements OnApplicationShutdown, OnModuleInit { //#region まず自分宛ての招待を探す const invitations = await this.redisClient.zrange( `reversi:matchSpecific:${me.id}`, - Date.now() - INVITATION_TIMEOUT_MS, + this.timeService.now - INVITATION_TIMEOUT_MS, '+inf', 'BYSCORE'); @@ -201,9 +203,9 @@ export class ReversiService implements OnApplicationShutdown, OnModuleInit { } else { const redisPipeline = this.redisClient.pipeline(); if (options.noIrregularRules) { - redisPipeline.zadd('reversi:matchAny', Date.now(), me.id + ':noIrregularRules'); + redisPipeline.zadd('reversi:matchAny', this.timeService.now, me.id + ':noIrregularRules'); } else { - redisPipeline.zadd('reversi:matchAny', Date.now(), me.id); + redisPipeline.zadd('reversi:matchAny', this.timeService.now, me.id); } redisPipeline.expire('reversi:matchAny', 15, 'NX'); await redisPipeline.exec(); @@ -224,7 +226,7 @@ export class ReversiService implements OnApplicationShutdown, OnModuleInit { @bindThis public async cleanOutdatedGames() { await this.reversiGamesRepository.delete({ - id: LessThan(this.idService.gen(Date.now() - 1000 * 60 * 10)), + id: LessThan(this.idService.gen(this.timeService.now - 1000 * 60 * 10)), isStarted: false, }); } @@ -269,7 +271,7 @@ export class ReversiService implements OnApplicationShutdown, OnModuleInit { if (isBothReady) { // 3秒後、両者readyならゲーム開始 - setTimeout(async () => { + this.timeService.startTimer(async () => { const freshGame = await this.get(game.id); if (freshGame == null || freshGame.isStarted || freshGame.isEnded) return; if (!freshGame.user1Ready || !freshGame.user2Ready) return; @@ -323,7 +325,7 @@ export class ReversiService implements OnApplicationShutdown, OnModuleInit { const updatedGame = await this.reversiGamesRepository.createQueryBuilder().update() .set({ ...this.getBakeProps(game), - startedAt: new Date(), + startedAt: this.timeService.date, isStarted: true, black: bw, map: game.map, @@ -368,7 +370,7 @@ export class ReversiService implements OnApplicationShutdown, OnModuleInit { .set({ ...this.getBakeProps(game), isEnded: true, - endedAt: new Date(), + endedAt: this.timeService.date, winnerId: winnerId, surrenderedUserId: reason === 'surrender' ? (winnerId === game.user1Id ? game.user2Id : game.user1Id) : null, timeoutUserId: reason === 'timeout' ? (winnerId === game.user1Id ? game.user2Id : game.user1Id) : null, @@ -392,7 +394,7 @@ export class ReversiService implements OnApplicationShutdown, OnModuleInit { public async getInvitations(user: MiUser): Promise { const invitations = await this.redisClient.zrange( `reversi:matchSpecific:${user.id}`, - Date.now() - INVITATION_TIMEOUT_MS, + this.timeService.now - INVITATION_TIMEOUT_MS, '+inf', 'BYSCORE'); return invitations; @@ -475,7 +477,7 @@ export class ReversiService implements OnApplicationShutdown, OnModuleInit { const logs = Reversi.Serializer.deserializeLogs(game.logs); const log = { - time: Date.now(), + time: this.timeService.now, player: myColor, operation: 'put', pos, diff --git a/packages/backend/src/core/SignupService.ts b/packages/backend/src/core/SignupService.ts index f1dd0f0503..bd2357fdac 100644 --- a/packages/backend/src/core/SignupService.ts +++ b/packages/backend/src/core/SignupService.ts @@ -23,6 +23,7 @@ import { UtilityService } from '@/core/UtilityService.js'; import { UserService } from '@/core/UserService.js'; import { SystemAccountService } from '@/core/SystemAccountService.js'; import { MetaService } from '@/core/MetaService.js'; +import { TimeService } from '@/core/TimeService.js'; @Injectable() export class SignupService { @@ -46,6 +47,7 @@ export class SignupService { private systemAccountService: SystemAccountService, private metaService: MetaService, private usersChart: UsersChart, + private readonly timeService: TimeService, ) { } @@ -150,7 +152,7 @@ export class SignupService { })); await transactionalEntityManager.save(new MiUsedUsername({ - createdAt: new Date(), + createdAt: this.timeService.date, username: username.toLowerCase(), })); }); diff --git a/packages/backend/src/core/SystemAccountService.ts b/packages/backend/src/core/SystemAccountService.ts index 38ad186f62..70ecd38db2 100644 --- a/packages/backend/src/core/SystemAccountService.ts +++ b/packages/backend/src/core/SystemAccountService.ts @@ -22,6 +22,7 @@ import { genRsaKeyPair } from '@/misc/gen-key-pair.js'; import { CacheManagementService, type ManagedMemoryKVCache } from '@/core/CacheManagementService.js'; import { CacheService } from '@/core/CacheService.js'; import { InternalEventService } from '@/core/InternalEventService.js'; +import { TimeService } from '@/core/TimeService.js'; export const SYSTEM_ACCOUNT_TYPES = ['actor', 'relay', 'proxy'] as const; @@ -51,6 +52,8 @@ export class SystemAccountService implements OnApplicationShutdown { private idService: IdService, private readonly cacheService: CacheService, private readonly internalEventService: InternalEventService, + private readonly timeService: TimeService, + cacheManagementService: CacheManagementService, ) { this.cache = cacheManagementService.createMemoryKVCache(1000 * 60 * 10); // 10m @@ -173,7 +176,7 @@ export class SystemAccountService implements OnApplicationShutdown { }); await transactionalEntityManager.insert(MiUsedUsername, { - createdAt: new Date(), + createdAt: this.timeService.date, username: extra.username.toLowerCase(), }); diff --git a/packages/backend/src/core/SystemWebhookService.ts b/packages/backend/src/core/SystemWebhookService.ts index bce1a6b41f..b10476c6ca 100644 --- a/packages/backend/src/core/SystemWebhookService.ts +++ b/packages/backend/src/core/SystemWebhookService.ts @@ -20,6 +20,7 @@ import { AbuseReportResolveType } from '@/models/AbuseUserReport.js'; import { ModeratorInactivityRemainingTime } from '@/queue/processors/CheckModeratorsActivityProcessorService.js'; import { CacheManagementService, type ManagedMemorySingleCache } from '@/core/CacheManagementService.js'; import { InternalEventService } from '@/core/InternalEventService.js'; +import { TimeService } from '@/core/TimeService.js'; import type { OnApplicationShutdown } from '@nestjs/common'; export type AbuseReportPayload = { @@ -64,6 +65,7 @@ export class SystemWebhookService implements OnApplicationShutdown { private moderationLogService: ModerationLogService, private globalEventService: GlobalEventService, private readonly internalEventService: InternalEventService, + private readonly timeService: TimeService, cacheManagementService: CacheManagementService, ) { @@ -156,7 +158,7 @@ export class SystemWebhookService implements OnApplicationShutdown { ): Promise { const beforeEntity = await this.systemWebhooksRepository.findOneByOrFail({ id: params.id }); await this.systemWebhooksRepository.update(beforeEntity.id, { - updatedAt: new Date(), + updatedAt: this.timeService.date, isActive: params.isActive, name: params.name, on: params.on, diff --git a/packages/backend/src/core/UpdateInstanceQueue.ts b/packages/backend/src/core/UpdateInstanceQueue.ts index 3fcd215ffa..dffe4a7b62 100644 --- a/packages/backend/src/core/UpdateInstanceQueue.ts +++ b/packages/backend/src/core/UpdateInstanceQueue.ts @@ -8,6 +8,7 @@ import { CollapsedQueue } from '@/misc/collapsed-queue.js'; import { bindThis } from '@/decorators.js'; import { MiNote } from '@/models/Note.js'; import { FederatedInstanceService } from '@/core/FederatedInstanceService.js'; +import { TimeService } from '@/core/TimeService.js'; type UpdateInstanceJob = { latestRequestReceivedAt: Date, @@ -19,8 +20,9 @@ type UpdateInstanceJob = { export class UpdateInstanceQueue extends CollapsedQueue implements OnApplicationShutdown { constructor( private readonly federatedInstanceService: FederatedInstanceService, + timeService: TimeService, ) { - super(process.env.NODE_ENV !== 'test' ? 60 * 1000 * 5 : 0, (id, job) => this.collapseUpdateInstanceJobs(id, job), (id, job) => this.performUpdateInstance(id, job)); + super(timeService, process.env.NODE_ENV !== 'test' ? 60 * 1000 * 5 : 0, (id, job) => this.collapseUpdateInstanceJobs(id, job), (id, job) => this.performUpdateInstance(id, job)); } @bindThis @@ -38,7 +40,7 @@ export class UpdateInstanceQueue extends CollapsedQueue { @@ -218,7 +220,7 @@ export class UserSearchService { offset: number; origin: 'local' | 'remote' | 'combined'; }> = {}) { - const activeThreshold = new Date(Date.now() - (1000 * 60 * 60 * 24 * 30)); // 30日 + const activeThreshold = new Date(this.timeService.now - (1000 * 60 * 60 * 24 * 30)); // 30日 const isUsername = query.startsWith('@') && !query.includes(' ') && query.indexOf('@', 1) === -1; diff --git a/packages/backend/src/core/UserService.ts b/packages/backend/src/core/UserService.ts index 4a04910105..bdfbb5fe4a 100644 --- a/packages/backend/src/core/UserService.ts +++ b/packages/backend/src/core/UserService.ts @@ -11,6 +11,7 @@ import { bindThis } from '@/decorators.js'; import { SystemWebhookService } from '@/core/SystemWebhookService.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; import { CacheService } from '@/core/CacheService.js'; +import { TimeService } from '@/core/TimeService.js'; @Injectable() export class UserService { @@ -22,6 +23,7 @@ export class UserService { private systemWebhookService: SystemWebhookService, private userEntityService: UserEntityService, private readonly cacheService: CacheService, + private readonly timeService: TimeService, ) { } @@ -30,7 +32,7 @@ export class UserService { if (user.isHibernated) { const result = await this.usersRepository.createQueryBuilder().update() .set({ - lastActiveDate: new Date(), + lastActiveDate: this.timeService.date, }) .where('id = :id', { id: user.id }) .returning('*') @@ -54,7 +56,7 @@ export class UserService { } } else { this.usersRepository.update(user.id, { - lastActiveDate: new Date(), + lastActiveDate: this.timeService.date, }); } } diff --git a/packages/backend/src/core/WebAuthnService.ts b/packages/backend/src/core/WebAuthnService.ts index afd1d68ce4..f5db1c8721 100644 --- a/packages/backend/src/core/WebAuthnService.ts +++ b/packages/backend/src/core/WebAuthnService.ts @@ -18,6 +18,7 @@ import { bindThis } from '@/decorators.js'; import { MiUser } from '@/models/_.js'; import { IdentifiableError } from '@/misc/identifiable-error.js'; import { LoggerService } from '@/core/LoggerService.js'; +import { TimeService } from '@/core/TimeService.js'; import Logger from '@/logger.js'; import type { AuthenticationResponseJSON, @@ -44,6 +45,9 @@ export class WebAuthnService { @Inject(DI.userSecurityKeysRepository) private userSecurityKeysRepository: UserSecurityKeysRepository, + + private readonly timeService: TimeService, + loggerService: LoggerService, ) { this.logger = loggerService.getLogger('web-authn'); @@ -239,7 +243,7 @@ export class WebAuthnService { await this.userSecurityKeysRepository.update({ id: response.id, }, { - lastUsed: new Date(), + lastUsed: this.timeService.date, counter: authenticationInfo.newCounter, credentialDeviceType: authenticationInfo.credentialDeviceType, credentialBackedUp: authenticationInfo.credentialBackedUp, @@ -321,7 +325,7 @@ export class WebAuthnService { id: response.id, userId: userId, }, { - lastUsed: new Date(), + lastUsed: this.timeService.date, counter: authenticationInfo.newCounter, credentialDeviceType: authenticationInfo.credentialDeviceType, credentialBackedUp: authenticationInfo.credentialBackedUp, diff --git a/packages/backend/src/core/activitypub/ApInboxService.ts b/packages/backend/src/core/activitypub/ApInboxService.ts index 8aaa229466..6c6d576114 100644 --- a/packages/backend/src/core/activitypub/ApInboxService.ts +++ b/packages/backend/src/core/activitypub/ApInboxService.ts @@ -39,6 +39,7 @@ import { FetchInstanceMetadataService } from '@/core/FetchInstanceMetadataServic import { UpdateInstanceQueue } from '@/core/UpdateInstanceQueue.js'; import { CacheService } from '@/core/CacheService.js'; import { NoteVisibilityService } from '@/core/NoteVisibilityService.js'; +import { TimeService } from '@/core/TimeService.js'; import { getApHrefNullable, getApId, getApIds, getApType, getNullableApId, isAccept, isActor, isAdd, isAnnounce, isApObject, isBlock, isCollectionOrOrderedCollection, isCreate, isDelete, isFlag, isFollow, isLike, isDislike, isMove, isPost, isReject, isRemove, isTombstone, isUndo, isUpdate, validActor, validPost, isActivity, IObjectWithId } from './type.js'; import { ApNoteService } from './models/ApNoteService.js'; import { ApLoggerService } from './ApLoggerService.js'; @@ -102,6 +103,7 @@ export class ApInboxService { private readonly updateInstanceQueue: UpdateInstanceQueue, private readonly cacheService: CacheService, private readonly noteVisibilityService: NoteVisibilityService, + private readonly timeService: TimeService, ) { this.logger = this.apLoggerService.logger; } @@ -150,7 +152,7 @@ export class ApInboxService { // ついでにリモートユーザーの情報が古かったら更新しておく if (actor.uri) { - if (actor.lastFetchedAt == null || Date.now() - actor.lastFetchedAt.getTime() > 1000 * 60 * 60 * 24) { + if (actor.lastFetchedAt == null || this.timeService.now - actor.lastFetchedAt.getTime() > 1000 * 60 * 60 * 24) { setImmediate(() => { // 同一ユーザーの情報を再度処理するので、使用済みのresolverを再利用してはいけない this.apPersonService.updatePerson(actor.uri) @@ -431,7 +433,7 @@ export class ApInboxService { if (i == null) return; this.updateInstanceQueue.enqueue(i.id, { - latestRequestReceivedAt: new Date(), + latestRequestReceivedAt: this.timeService.date, shouldUnsuspend: i.suspensionState === 'autoSuspendedForNotResponding', }); @@ -446,7 +448,7 @@ export class ApInboxService { return await this.performOneActivity(actor, activity, resolver) .finally(() => { // Update user (adapted from performActivity) - if (actor.lastFetchedAt == null || Date.now() - actor.lastFetchedAt.getTime() > 1000 * 60 * 60 * 24) { + if (actor.lastFetchedAt == null || this.timeService.now - actor.lastFetchedAt.getTime() > 1000 * 60 * 60 * 24) { setImmediate(() => { // Don't re-use the resolver, or it may throw recursion errors. // Instead, create a new resolver with an appropriately-reduced recursion limit. diff --git a/packages/backend/src/core/activitypub/ApRendererService.ts b/packages/backend/src/core/activitypub/ApRendererService.ts index 21d05dc369..3eba101e1b 100644 --- a/packages/backend/src/core/activitypub/ApRendererService.ts +++ b/packages/backend/src/core/activitypub/ApRendererService.ts @@ -35,6 +35,7 @@ import { UtilityService } from '@/core/UtilityService.js'; import { CacheService } from '@/core/CacheService.js'; import { isPureRenote, isQuote, isRenote } from '@/misc/is-renote.js'; import { FederatedInstanceService } from '@/core/FederatedInstanceService.js'; +import { TimeService } from '@/core/TimeService.js'; import { JsonLdService } from './JsonLdService.js'; import { ApMfmService } from './ApMfmService.js'; import { CONTEXT } from './misc/contexts.js'; @@ -80,6 +81,7 @@ export class ApRendererService { private readonly queryService: QueryService, private readonly cacheService: CacheService, private readonly federatedInstanceService: FederatedInstanceService, + private readonly timeService: TimeService, ) { } @@ -174,7 +176,7 @@ export class ApRendererService { type: 'Delete', actor: this.userEntityService.genLocalUserUri(user.id), object, - published: new Date().toISOString(), + published: this.timeService.date.toISOString(), }; } @@ -196,7 +198,7 @@ export class ApRendererService { id: `${this.config.url}/emojis/${emoji.name}`, type: 'Emoji', name: `:${emoji.name}:`, - updated: emoji.updatedAt != null ? emoji.updatedAt.toISOString() : new Date().toISOString(), + updated: emoji.updatedAt != null ? emoji.updatedAt.toISOString() : this.timeService.date.toISOString(), icon: { type: 'Image', mediaType: emoji.type ?? 'image/png', @@ -534,7 +536,7 @@ export class ApRendererService { const asPoll = poll ? { type: 'Question', - [poll.expiresAt && poll.expiresAt < new Date() ? 'closed' : 'endTime']: poll.expiresAt, + [poll.expiresAt && poll.expiresAt < this.timeService.date ? 'closed' : 'endTime']: poll.expiresAt, [poll.multiple ? 'anyOf' : 'oneOf']: poll.choices.map((text, i) => ({ type: 'Note', name: text, @@ -755,21 +757,21 @@ export class ApRendererService { ...(id ? { id } : {}), actor: this.userEntityService.genLocalUserUri(user.id), object, - published: new Date().toISOString(), + published: this.timeService.date.toISOString(), }; } @bindThis public renderUpdate(object: IObject, user: { id: MiUser['id'] }): IUpdate { // Deterministic activity IDs to allow de-duplication by remote instances - const updatedAt = object.updated ? new Date(object.updated).getTime() : Date.now(); + const updatedAt = object.updated ? new Date(object.updated).getTime() : this.timeService.now; return { id: `${this.config.url}/users/${user.id}#updates/${updatedAt}`, actor: this.userEntityService.genLocalUserUri(user.id), type: 'Update', to: ['https://www.w3.org/ns/activitystreams#Public'], object, - published: new Date().toISOString(), + published: this.timeService.date.toISOString(), }; } @@ -780,7 +782,7 @@ export class ApRendererService { actor: this.userEntityService.genLocalUserUri(user.id), type: 'Create', to: [pollOwner.uri], - published: new Date().toISOString(), + published: this.timeService.date.toISOString(), object: { id: `${this.config.url}/users/${user.id}#votes/${vote.id}`, type: 'Note', diff --git a/packages/backend/src/core/activitypub/JsonLdService.ts b/packages/backend/src/core/activitypub/JsonLdService.ts index 8f150ab201..0ab37bc148 100644 --- a/packages/backend/src/core/activitypub/JsonLdService.ts +++ b/packages/backend/src/core/activitypub/JsonLdService.ts @@ -11,6 +11,7 @@ import { bindThis } from '@/decorators.js'; import Logger from '@/logger.js'; import { LoggerService } from '@/core/LoggerService.js'; import { StatusError } from '@/misc/status-error.js'; +import { TimeService } from '@/core/TimeService.js'; import { CONTEXT, PRELOADED_CONTEXTS } from './misc/contexts.js'; import { validateContentTypeSetAsJsonLD } from './misc/validator.js'; import type { ContextDefinition, JsonLdDocument } from 'jsonld'; @@ -56,6 +57,8 @@ export class JsonLdService { constructor( private httpRequestService: HttpRequestService, + private readonly timeService: TimeService, + loggerService: LoggerService, ) { this.logger = loggerService.getLogger('json-ld'); @@ -73,7 +76,7 @@ export class JsonLdService { type: 'RsaSignature2017', creator, nonce: crypto.randomBytes(16).toString('hex'), - created: (created ?? new Date()).toISOString(), + created: (created ?? this.timeService.date).toISOString(), }; if (domain) { diff --git a/packages/backend/src/core/activitypub/models/ApNoteService.ts b/packages/backend/src/core/activitypub/models/ApNoteService.ts index a2f53a787e..6afe15b1f3 100644 --- a/packages/backend/src/core/activitypub/models/ApNoteService.ts +++ b/packages/backend/src/core/activitypub/models/ApNoteService.ts @@ -33,6 +33,7 @@ import { extractMediaFromHtml } from '@/core/activitypub/misc/extract-media-from import { extractMediaFromMfm } from '@/core/activitypub/misc/extract-media-from-mfm.js'; import { getContentByType } from '@/core/activitypub/misc/get-content-by-type.js'; import { CustomEmojiService, encodeEmojiKey, isValidEmojiName } from '@/core/CustomEmojiService.js'; +import { TimeService } from '@/core/TimeService.js'; import { getOneApId, getApId, validPost, isEmoji, getApType, isApObject, isDocument, IApDocument, isLink } from '../type.js'; import { ApLoggerService } from '../ApLoggerService.js'; import { ApMfmService } from '../ApMfmService.js'; @@ -91,6 +92,7 @@ export class ApNoteService implements OnModuleInit { private apLoggerService: ApLoggerService, private readonly apUtilityService: ApUtilityService, private readonly customEmojiService: CustomEmojiService, + private readonly timeService: TimeService, ) { this.logger = this.apLoggerService.logger; } @@ -291,7 +293,7 @@ export class ApNoteService implements OnModuleInit { const poll = await this.pollsRepository.findOneByOrFail({ noteId: reply.id }); const tryCreateVote = async (name: string, index: number): Promise => { - if (poll.expiresAt && Date.now() > new Date(poll.expiresAt).getTime()) { + if (poll.expiresAt && this.timeService.now > new Date(poll.expiresAt).getTime()) { this.logger.warn(`vote to expired poll from AP: actor=${actor.username}@${actor.host}, note=${note.id}, choice=${name}`); } else if (index >= 0) { this.logger.info(`vote from AP: actor=${actor.username}@${actor.host}, note=${note.id}, choice=${name}`); @@ -470,7 +472,7 @@ export class ApNoteService implements OnModuleInit { const poll = await this.pollsRepository.findOneByOrFail({ noteId: reply.id }); const tryCreateVote = async (name: string, index: number): Promise => { - if (poll.expiresAt && Date.now() > new Date(poll.expiresAt).getTime()) { + if (poll.expiresAt && this.timeService.now > new Date(poll.expiresAt).getTime()) { this.logger.warn(`vote to expired poll from AP: actor=${actor.username}@${actor.host}, note=${note.id}, choice=${name}`); } else if (index >= 0) { this.logger.info(`vote from AP: actor=${actor.username}@${actor.host}, note=${note.id}, choice=${name}`); @@ -601,7 +603,7 @@ export class ApNoteService implements OnModuleInit { uri: tag.id, originalUrl: tag.icon.url, publicUrl: tag.icon.url, - updatedAt: new Date(), + updatedAt: this.timeService.date, // _misskey_license が存在しなければ `null` license: (tag._misskey_license?.freeText ?? null), }); @@ -617,7 +619,7 @@ export class ApNoteService implements OnModuleInit { uri: tag.id, originalUrl: tag.icon.url, publicUrl: tag.icon.url, - updatedAt: new Date(), + updatedAt: this.timeService.date, aliases: [], localOnly: false, isSensitive: tag.sensitive === true, diff --git a/packages/backend/src/core/activitypub/models/ApPersonService.ts b/packages/backend/src/core/activitypub/models/ApPersonService.ts index b5564575b1..a991516577 100644 --- a/packages/backend/src/core/activitypub/models/ApPersonService.ts +++ b/packages/backend/src/core/activitypub/models/ApPersonService.ts @@ -41,6 +41,7 @@ import type { AccountMoveService } from '@/core/AccountMoveService.js'; import { ApUtilityService } from '@/core/activitypub/ApUtilityService.js'; import { AppLockService } from '@/core/AppLockService.js'; import { HttpRequestService } from '@/core/HttpRequestService.js'; +import { TimeService } from '@/core/TimeService.js'; import { verifyFieldLinks } from '@/misc/verify-field-link.js'; import { isRetryableError } from '@/misc/is-retryable-error.js'; import { renderInlineError } from '@/misc/render-inline-error.js'; @@ -116,6 +117,7 @@ export class ApPersonService implements OnModuleInit { private readonly utilityService: UtilityService, private readonly apUtilityService: ApUtilityService, private readonly idService: IdService, + private readonly timeService: TimeService, apLoggerService: ApLoggerService, ) { @@ -419,13 +421,13 @@ export class ApPersonService implements OnModuleInit { avatarId: null, bannerId: null, backgroundId: null, - lastFetchedAt: new Date(), + lastFetchedAt: this.timeService.date, name: truncate(person.name, nameLength), noindex: (person as any).noindex ?? false, enableRss: person.enableRss === true, isLocked: person.manuallyApprovesFollowers, movedToUri: person.movedTo, - movedAt: person.movedTo ? new Date() : null, + movedAt: person.movedTo ? this.timeService.date : null, alsoKnownAs: person.alsoKnownAs, // We use "!== false" to handle incorrect types, missing / null values, and "default to true" logic. hideOnlineStatus: person.hideOnlineStatus !== false, @@ -634,7 +636,7 @@ export class ApPersonService implements OnModuleInit { const verifiedLinks = await verifyFieldLinks(fields, profileUrls, this.httpRequestService); const updates = { - lastFetchedAt: new Date(), + lastFetchedAt: this.timeService.date, inbox: person.inbox, sharedInbox: person.sharedInbox ?? person.endpoints?.sharedInbox ?? null, followersUri: person.followers ? getApId(person.followers) : undefined, @@ -688,7 +690,7 @@ export class ApPersonService implements OnModuleInit { return false; })(); - if (moving) updates.movedAt = new Date(); + if (moving) updates.movedAt = this.timeService.date; // Update user if (!(await this.usersRepository.update({ id: exist.id, isDeleted: false }, updates)).affected) { @@ -894,7 +896,7 @@ export class ApPersonService implements OnModuleInit { for (const note of featuredNotes.filter(x => x != null)) { td -= 1000; transactionalEntityManager.insert(MiUserNotePining, { - id: this.idService.gen(Date.now() + td), + id: this.idService.gen(this.timeService.now + td), userId: user.id, noteId: note.id, }); diff --git a/packages/backend/src/core/chart/ChartManagementService.ts b/packages/backend/src/core/chart/ChartManagementService.ts index 413c888455..8919307831 100644 --- a/packages/backend/src/core/chart/ChartManagementService.ts +++ b/packages/backend/src/core/chart/ChartManagementService.ts @@ -7,6 +7,7 @@ import { Injectable } from '@nestjs/common'; import { bindThis } from '@/decorators.js'; import { ChartLoggerService } from '@/core/chart/ChartLoggerService.js'; +import { TimeService, type TimerHandle } from '@/core/TimeService.js'; import Logger from '@/logger.js'; import { renderInlineError } from '@/misc/render-inline-error.js'; import FederationChart from './charts/federation.js'; @@ -26,7 +27,7 @@ import type { OnApplicationShutdown } from '@nestjs/common'; @Injectable() export class ChartManagementService implements OnApplicationShutdown { private charts; - private saveIntervalId: NodeJS.Timeout; + private saveIntervalId: TimerHandle; private readonly logger: Logger; constructor( @@ -42,6 +43,8 @@ export class ChartManagementService implements OnApplicationShutdown { private perUserFollowingChart: PerUserFollowingChart, private perUserDriveChart: PerUserDriveChart, private apRequestChart: ApRequestChart, + private readonly timeService: TimeService, + chartLoggerService: ChartLoggerService, ) { this.charts = [ @@ -64,17 +67,17 @@ export class ChartManagementService implements OnApplicationShutdown { @bindThis public async start() { // 20分おきにメモリ情報をDBに書き込み - this.saveIntervalId = setInterval(async () => { + this.saveIntervalId = this.timeService.startTimer(async () => { for (const chart of this.charts) { await chart.save(); } this.logger.info('All charts saved'); - }, 1000 * 60 * 20); + }, 1000 * 60 * 20, { repeated: true }); } @bindThis public async dispose(): Promise { - clearInterval(this.saveIntervalId); + this.timeService.stopTimer(this.saveIntervalId); if (process.env.NODE_ENV !== 'test') { this.logger.info('Saving charts for shutdown...'); for (const chart of this.charts) { diff --git a/packages/backend/src/core/chart/charts/active-users.ts b/packages/backend/src/core/chart/charts/active-users.ts index 4e9761e135..c4b4a434c1 100644 --- a/packages/backend/src/core/chart/charts/active-users.ts +++ b/packages/backend/src/core/chart/charts/active-users.ts @@ -54,12 +54,12 @@ export default class ActiveUsersChart extends Chart { // eslint-d const createdAt = this.idService.parse(user.id).date; await this.commit({ 'read': [user.id], - 'registeredWithinWeek': (Date.now() - createdAt.getTime() < week) ? [user.id] : [], - 'registeredWithinMonth': (Date.now() - createdAt.getTime() < month) ? [user.id] : [], - 'registeredWithinYear': (Date.now() - createdAt.getTime() < year) ? [user.id] : [], - 'registeredOutsideWeek': (Date.now() - createdAt.getTime() > week) ? [user.id] : [], - 'registeredOutsideMonth': (Date.now() - createdAt.getTime() > month) ? [user.id] : [], - 'registeredOutsideYear': (Date.now() - createdAt.getTime() > year) ? [user.id] : [], + 'registeredWithinWeek': (this.timeService.now - createdAt.getTime() < week) ? [user.id] : [], + 'registeredWithinMonth': (this.timeService.now - createdAt.getTime() < month) ? [user.id] : [], + 'registeredWithinYear': (this.timeService.now - createdAt.getTime() < year) ? [user.id] : [], + 'registeredOutsideWeek': (this.timeService.now - createdAt.getTime() > week) ? [user.id] : [], + 'registeredOutsideMonth': (this.timeService.now - createdAt.getTime() > month) ? [user.id] : [], + 'registeredOutsideYear': (this.timeService.now - createdAt.getTime() > year) ? [user.id] : [], }); } diff --git a/packages/backend/src/core/entities/MetaEntityService.ts b/packages/backend/src/core/entities/MetaEntityService.ts index d7ab20db2c..05a4a80862 100644 --- a/packages/backend/src/core/entities/MetaEntityService.ts +++ b/packages/backend/src/core/entities/MetaEntityService.ts @@ -14,6 +14,7 @@ import { SystemAccountService } from '@/core/SystemAccountService.js'; import type { Config } from '@/config.js'; import { DI } from '@/di-symbols.js'; import { DEFAULT_POLICIES } from '@/core/RoleService.js'; +import { TimeService } from '@/core/TimeService.js'; @Injectable() export class MetaEntityService { @@ -28,6 +29,7 @@ export class MetaEntityService { private adsRepository: AdsRepository, private systemAccountService: SystemAccountService, + private readonly timeService: TimeService, ) { } @bindThis @@ -39,11 +41,11 @@ export class MetaEntityService { } const ads = await this.adsRepository.createQueryBuilder('ads') - .where('ads.expiresAt > :now', { now: new Date() }) - .andWhere('ads.startsAt <= :now', { now: new Date() }) + .where('ads.expiresAt > :now', { now: this.timeService.date }) + .andWhere('ads.startsAt <= :now', { now: this.timeService.date }) .andWhere(new Brackets(qb => { // 曜日のビットフラグを確認する - qb.where('ads.dayOfWeek & :dayOfWeek > 0', { dayOfWeek: 1 << new Date().getDay() }) + qb.where('ads.dayOfWeek & :dayOfWeek > 0', { dayOfWeek: 1 << this.timeService.date.getDay() }) .orWhere('ads.dayOfWeek = 0'); })) .getMany(); diff --git a/packages/backend/src/core/entities/NoteEntityService.ts b/packages/backend/src/core/entities/NoteEntityService.ts index c715e8e0cb..2778967aa0 100644 --- a/packages/backend/src/core/entities/NoteEntityService.ts +++ b/packages/backend/src/core/entities/NoteEntityService.ts @@ -17,6 +17,7 @@ import { DebounceLoader } from '@/misc/loader.js'; import type { IdService } from '@/core/IdService.js'; import type { ReactionsBufferingService } from '@/core/ReactionsBufferingService.js'; import { QueryService } from '@/core/QueryService.js'; +import { TimeService } from '@/core/TimeService.js'; import type { Config } from '@/config.js'; import { NoteVisibilityService } from '@/core/NoteVisibilityService.js'; import type { NoteVisibilityData } from '@/core/NoteVisibilityService.js'; @@ -106,6 +107,7 @@ export class NoteEntityService implements OnModuleInit { public readonly noteVisibilityService: NoteVisibilityService, private readonly queryService: QueryService, + private readonly timeService: TimeService, //private userEntityService: UserEntityService, //private driveFileEntityService: DriveFileEntityService, //private customEmojiService: CustomEmojiService, @@ -134,7 +136,7 @@ export class NoteEntityService implements OnModuleInit { const followersOnlyBefore = packedNote.user.makeNotesFollowersOnlyBefore; if ((followersOnlyBefore != null) && ( - (followersOnlyBefore <= 0 && (Date.now() - new Date(packedNote.createdAt).getTime() > 0 - (followersOnlyBefore * 1000))) + (followersOnlyBefore <= 0 && (this.timeService.now - new Date(packedNote.createdAt).getTime() > 0 - (followersOnlyBefore * 1000))) || (followersOnlyBefore > 0 && (new Date(packedNote.createdAt).getTime() < followersOnlyBefore * 1000)) ) ) { @@ -388,7 +390,7 @@ export class NoteEntityService implements OnModuleInit { } // パフォーマンスのためノートが作成されてから2秒以上経っていない場合はリアクションを取得しない - if (this.idService.parse(note.id).date.getTime() + 2000 > Date.now()) { + if (this.idService.parse(note.id).date.getTime() + 2000 > this.timeService.now) { return undefined; } diff --git a/packages/backend/src/core/entities/RoleEntityService.ts b/packages/backend/src/core/entities/RoleEntityService.ts index 3fa38c9521..14d3bc9250 100644 --- a/packages/backend/src/core/entities/RoleEntityService.ts +++ b/packages/backend/src/core/entities/RoleEntityService.ts @@ -14,6 +14,7 @@ import { bindThis } from '@/decorators.js'; import { DEFAULT_POLICIES } from '@/core/RoleService.js'; import { IdService } from '@/core/IdService.js'; import { Packed } from '@/misc/json-schema.js'; +import { TimeService } from '@/core/TimeService.js'; @Injectable() export class RoleEntityService { @@ -25,6 +26,7 @@ export class RoleEntityService { private roleAssignmentsRepository: RoleAssignmentsRepository, private idService: IdService, + private readonly timeService: TimeService, ) { } @@ -40,7 +42,7 @@ export class RoleEntityService { .andWhere(new Brackets(qb => { qb .where('assign.expiresAt IS NULL') - .orWhere('assign.expiresAt > :now', { now: new Date() }); + .orWhere('assign.expiresAt > :now', { now: this.timeService.date }); })) .getCount(); diff --git a/packages/backend/src/core/entities/UserEntityService.ts b/packages/backend/src/core/entities/UserEntityService.ts index ce3b52011c..4fe97c382c 100644 --- a/packages/backend/src/core/entities/UserEntityService.ts +++ b/packages/backend/src/core/entities/UserEntityService.ts @@ -49,6 +49,7 @@ import type { RolePolicies, RoleService } from '@/core/RoleService.js'; import type { ApPersonService } from '@/core/activitypub/models/ApPersonService.js'; import type { FederatedInstanceService } from '@/core/FederatedInstanceService.js'; import type { IdService } from '@/core/IdService.js'; +import { TimeService } from '@/core/TimeService.js'; import type { AnnouncementService } from '@/core/AnnouncementService.js'; import type { CustomEmojiService } from '@/core/CustomEmojiService.js'; import type { AvatarDecorationService } from '@/core/AvatarDecorationService.js'; @@ -153,6 +154,8 @@ export class UserEntityService implements OnModuleInit { @Inject(DI.userMemosRepository) private userMemosRepository: UserMemoRepository, + + private readonly timeService: TimeService, ) { } @@ -398,7 +401,7 @@ export class UserEntityService implements OnModuleInit { public getOnlineStatus(user: MiUser): 'unknown' | 'online' | 'active' | 'offline' { if (user.hideOnlineStatus) return 'unknown'; if (user.lastActiveDate == null) return 'unknown'; - const elapsed = Date.now() - user.lastActiveDate.getTime(); + const elapsed = this.timeService.now - user.lastActiveDate.getTime(); return ( elapsed < USER_ONLINE_THRESHOLD ? 'online' : elapsed < USER_ACTIVE_THRESHOLD ? 'active' : diff --git a/packages/backend/src/daemons/ApLogCleanupService.ts b/packages/backend/src/daemons/ApLogCleanupService.ts index 61f76b4e2c..6021270293 100644 --- a/packages/backend/src/daemons/ApLogCleanupService.ts +++ b/packages/backend/src/daemons/ApLogCleanupService.ts @@ -8,6 +8,7 @@ import { bindThis } from '@/decorators.js'; import { LoggerService } from '@/core/LoggerService.js'; import Logger from '@/logger.js'; import { ApLogService } from '@/core/ApLogService.js'; +import { TimeService, type TimerHandle } from '@/core/TimeService.js'; // 10 minutes export const scanInterval = 1000 * 60 * 10; @@ -15,10 +16,12 @@ export const scanInterval = 1000 * 60 * 10; @Injectable() export class ApLogCleanupService implements OnApplicationShutdown { private readonly logger: Logger; - private scanTimer: NodeJS.Timeout | null = null; + private scanTimer: TimerHandle | null = null; constructor( private readonly apLogService: ApLogService, + private readonly timeService: TimeService, + loggerService: LoggerService, ) { this.logger = loggerService.getLogger('activity-log-cleanup'); @@ -34,7 +37,7 @@ export class ApLogCleanupService implements OnApplicationShutdown { this.tick(); // Prune on a regular interval for the lifetime of the server. - this.scanTimer = setInterval(this.tick, scanInterval); + this.scanTimer = this.timeService.startTimer(this.tick, scanInterval, { repeated: true }); } @bindThis @@ -55,7 +58,7 @@ export class ApLogCleanupService implements OnApplicationShutdown { @bindThis public dispose(): void { if (this.scanTimer) { - clearInterval(this.scanTimer); + this.timeService.stopTimer(this.scanTimer); this.scanTimer = null; } } diff --git a/packages/backend/src/daemons/QueueStatsService.ts b/packages/backend/src/daemons/QueueStatsService.ts index 9888ae3942..eb4edc4ebf 100644 --- a/packages/backend/src/daemons/QueueStatsService.ts +++ b/packages/backend/src/daemons/QueueStatsService.ts @@ -7,6 +7,7 @@ import { Inject, Injectable } from '@nestjs/common'; import Xev from 'xev'; import * as Bull from 'bullmq'; import { QueueService } from '@/core/QueueService.js'; +import { TimeService, type TimerHandle } from '@/core/TimeService.js'; import { bindThis } from '@/decorators.js'; import { DI } from '@/di-symbols.js'; import type { Config } from '@/config.js'; @@ -31,7 +32,7 @@ const interval = 10000; @Injectable() export class QueueStatsService implements OnApplicationShutdown { - private intervalId?: NodeJS.Timeout; + private intervalId?: TimerHandle; private activeDeliverJobs = 0; private activeInboxJobs = 0; @@ -45,6 +46,7 @@ export class QueueStatsService implements OnApplicationShutdown { private config: Config, private queueService: QueueService, + private readonly timeService: TimeService, ) { } @@ -114,13 +116,13 @@ export class QueueStatsService implements OnApplicationShutdown { tick(); - this.intervalId = setInterval(tick, interval); + this.intervalId = this.timeService.startTimer(tick, interval, { repeated: true }); } @bindThis public async stop() { if (this.intervalId) { - clearInterval(this.intervalId); + this.timeService.stopTimer(this.intervalId); } this.log = undefined; diff --git a/packages/backend/src/daemons/ServerStatsService.ts b/packages/backend/src/daemons/ServerStatsService.ts index 9fafa54b04..7368be1db5 100644 --- a/packages/backend/src/daemons/ServerStatsService.ts +++ b/packages/backend/src/daemons/ServerStatsService.ts @@ -11,6 +11,7 @@ import { bindThis } from '@/decorators.js'; import type { OnApplicationShutdown } from '@nestjs/common'; import { MiMeta } from '@/models/_.js'; import { DI } from '@/di-symbols.js'; +import { TimeService, type TimerHandle } from '@/core/TimeService.js'; export interface Stats { cpu: number, @@ -37,13 +38,14 @@ const round = (num: number) => Math.round(num * 10) / 10; @Injectable() export class ServerStatsService implements OnApplicationShutdown { - private intervalId: NodeJS.Timeout | null = null; + private intervalId: TimerHandle | null = null; private log: Stats[] = []; constructor( @Inject(DI.meta) private meta: MiMeta, + private readonly timeService: TimeService, ) { } @@ -90,13 +92,13 @@ export class ServerStatsService implements OnApplicationShutdown { tick(); - this.intervalId = setInterval(tick, interval); + this.intervalId = this.timeService.startTimer(tick, interval, { repeated: true }); } @bindThis public dispose(): void { if (this.intervalId) { - clearInterval(this.intervalId); + this.timeService.stopTimer(this.intervalId); } this.log = []; diff --git a/packages/backend/src/logger.ts b/packages/backend/src/logger.ts index 0799ec3e94..d9dc17d62f 100644 --- a/packages/backend/src/logger.ts +++ b/packages/backend/src/logger.ts @@ -8,6 +8,7 @@ import chalk from 'chalk'; import { default as convertColor } from 'color-convert'; import { format as dateFormat } from 'date-fns'; import { bindThis } from '@/decorators.js'; +import { TimeService, NativeTimeService } from '@/core/TimeService.js'; import { envOption } from './env.js'; import type { KEYWORD } from 'color-convert/conversions.js'; @@ -26,6 +27,8 @@ export type DataObject = Record | (object & { length?: never; } export type Console = Pick; export const nativeConsole: Console = global.console; +const fallbackTimeService = new NativeTimeService(); + const levelFuncs = { error: 'error', warning: 'warn', @@ -39,6 +42,7 @@ export default class Logger { private context: Context; private parentLogger: Logger | null = null; public readonly verbose: boolean; + private readonly timeService: TimeService; /** * Where to send the actual log strings. @@ -46,18 +50,19 @@ export default class Logger { */ private readonly console: Console; - constructor(context: string, color?: KEYWORD, verbose?: boolean, console?: Console) { + constructor(context: string, color?: KEYWORD, verbose?: boolean, console?: Console, timeService?: TimeService) { this.context = { name: context, color: color, }; this.verbose = verbose ?? envOption.verbose; this.console = console ?? nativeConsole; + this.timeService = timeService ?? fallbackTimeService; } @bindThis public createSubLogger(context: string, color?: KEYWORD): Logger { - const logger = new Logger(context, color, this.verbose); + const logger = new Logger(context, color, this.verbose, this.console, this.timeService); logger.parentLogger = this; return logger; } @@ -71,7 +76,7 @@ export default class Logger { return; } - const time = dateFormat(new Date(), 'HH:mm:ss'); + const time = dateFormat(this.timeService.date, 'HH:mm:ss'); const worker = cluster.isPrimary ? '*' : cluster.worker!.id; const l = level === 'error' ? important ? chalk.bgRed.white('ERR ') : chalk.red('ERR ') : diff --git a/packages/backend/src/misc/collapsed-queue.ts b/packages/backend/src/misc/collapsed-queue.ts index 5bc20a78ae..515819305e 100644 --- a/packages/backend/src/misc/collapsed-queue.ts +++ b/packages/backend/src/misc/collapsed-queue.ts @@ -3,9 +3,11 @@ * SPDX-License-Identifier: AGPL-3.0-only */ +import type { TimeService, TimerHandle } from '@/core/TimeService.js'; + type Job = { value: V; - timer: NodeJS.Timeout; + timer: TimerHandle; }; // TODO: redis使えるようにする @@ -13,6 +15,7 @@ export class CollapsedQueue { private jobs: Map> = new Map(); constructor( + protected readonly timeService: TimeService, private timeout: number, private collapse: (oldValue: V, newValue: V) => V, private perform: (key: K, value: V) => Promise, @@ -24,7 +27,7 @@ export class CollapsedQueue { const merged = this.collapse(old.value, value); this.jobs.set(key, { ...old, value: merged }); } else { - const timer = setTimeout(() => { + const timer = this.timeService.startTimer(() => { const job = this.jobs.get(key)!; this.jobs.delete(key); this.perform(key, job.value); @@ -37,7 +40,7 @@ export class CollapsedQueue { const entries = [...this.jobs.entries()]; this.jobs.clear(); for (const [_key, job] of entries) { - clearTimeout(job.timer); + this.timeService.stopTimer(job.timer); } await Promise.allSettled(entries.map(([key, job]) => this.perform(key, job.value))); } diff --git a/packages/backend/src/queue/processors/AggregateRetentionProcessorService.ts b/packages/backend/src/queue/processors/AggregateRetentionProcessorService.ts index 30bdd6ccca..152c6ffc16 100644 --- a/packages/backend/src/queue/processors/AggregateRetentionProcessorService.ts +++ b/packages/backend/src/queue/processors/AggregateRetentionProcessorService.ts @@ -11,6 +11,7 @@ import { bindThis } from '@/decorators.js'; import type { RetentionAggregationsRepository, UsersRepository } from '@/models/_.js'; import { deepClone } from '@/misc/clone.js'; import { IdService } from '@/core/IdService.js'; +import { TimeService } from '@/core/TimeService.js'; import { isDuplicateKeyValueError } from '@/misc/is-duplicate-key-value-error.js'; import { QueueLoggerService } from '../QueueLoggerService.js'; import type * as Bull from 'bullmq'; @@ -28,6 +29,7 @@ export class AggregateRetentionProcessorService { private idService: IdService, private queueLoggerService: QueueLoggerService, + private readonly timeService: TimeService, ) { this.logger = this.queueLoggerService.logger.createSubLogger('aggregate-retention'); } @@ -36,18 +38,18 @@ export class AggregateRetentionProcessorService { public async process(): Promise { this.logger.info('Aggregating retention...'); - const now = new Date(); + const now = this.timeService.date; const dateKey = `${now.getFullYear()}-${now.getMonth() + 1}-${now.getDate()}`; // 過去(だいたい)30日分のレコードを取得 const pastRecords = await this.retentionAggregationsRepository.findBy({ - createdAt: MoreThan(new Date(Date.now() - (1000 * 60 * 60 * 24 * 31))), + createdAt: MoreThan(new Date(this.timeService.now - (1000 * 60 * 60 * 24 * 31))), }); // 今日登録したユーザーを全て取得 const targetUsers = await this.usersRepository.findBy({ host: IsNull(), - id: MoreThan(this.idService.gen(Date.now() - (1000 * 60 * 60 * 24))), + id: MoreThan(this.idService.gen(this.timeService.now - (1000 * 60 * 60 * 24))), }); const targetUserIds = targetUsers.map(u => u.id); @@ -71,7 +73,7 @@ export class AggregateRetentionProcessorService { // 今日活動したユーザーを全て取得 const activeUsers = await this.usersRepository.findBy({ host: IsNull(), - lastActiveDate: MoreThan(new Date(Date.now() - (1000 * 60 * 60 * 24))), + lastActiveDate: MoreThan(new Date(this.timeService.now - (1000 * 60 * 60 * 24))), }); const activeUsersIds = activeUsers.map(u => u.id); diff --git a/packages/backend/src/queue/processors/CheckExpiredMutingsProcessorService.ts b/packages/backend/src/queue/processors/CheckExpiredMutingsProcessorService.ts index 76d0cb4304..8cf88968c5 100644 --- a/packages/backend/src/queue/processors/CheckExpiredMutingsProcessorService.ts +++ b/packages/backend/src/queue/processors/CheckExpiredMutingsProcessorService.ts @@ -10,6 +10,7 @@ import type { MutingsRepository } from '@/models/_.js'; import type Logger from '@/logger.js'; import { bindThis } from '@/decorators.js'; import { UserMutingService } from '@/core/UserMutingService.js'; +import { TimeService } from '@/core/TimeService.js'; import { QueueLoggerService } from '../QueueLoggerService.js'; import type * as Bull from 'bullmq'; @@ -23,6 +24,7 @@ export class CheckExpiredMutingsProcessorService { private userMutingService: UserMutingService, private queueLoggerService: QueueLoggerService, + private readonly timeService: TimeService, ) { this.logger = this.queueLoggerService.logger.createSubLogger('check-expired-mutings'); } @@ -33,7 +35,7 @@ export class CheckExpiredMutingsProcessorService { const expired = await this.mutingsRepository.createQueryBuilder('muting') .where('muting.expiresAt IS NOT NULL') - .andWhere('muting.expiresAt < :now', { now: new Date() }) + .andWhere('muting.expiresAt < :now', { now: this.timeService.date }) .innerJoinAndSelect('muting.mutee', 'mutee') .getMany(); diff --git a/packages/backend/src/queue/processors/CleanProcessorService.ts b/packages/backend/src/queue/processors/CleanProcessorService.ts index 104d19103f..3fc39efe4c 100644 --- a/packages/backend/src/queue/processors/CleanProcessorService.ts +++ b/packages/backend/src/queue/processors/CleanProcessorService.ts @@ -12,6 +12,7 @@ import { bindThis } from '@/decorators.js'; import { IdService } from '@/core/IdService.js'; import type { Config } from '@/config.js'; import { ReversiService } from '@/core/ReversiService.js'; +import { TimeService } from '@/core/TimeService.js'; import { QueueLoggerService } from '../QueueLoggerService.js'; import type * as Bull from 'bullmq'; @@ -35,6 +36,7 @@ export class CleanProcessorService { private queueLoggerService: QueueLoggerService, private reversiService: ReversiService, private idService: IdService, + private readonly timeService: TimeService, ) { this.logger = this.queueLoggerService.logger.createSubLogger('clean'); } @@ -44,13 +46,13 @@ export class CleanProcessorService { this.logger.info('Cleaning...'); this.userIpsRepository.delete({ - createdAt: LessThan(new Date(Date.now() - (1000 * 60 * 60 * 24 * 90))), + createdAt: LessThan(new Date(this.timeService.now - (1000 * 60 * 60 * 24 * 90))), }); // 使われてないアンテナを停止 if (this.config.deactivateAntennaThreshold > 0) { this.antennasRepository.update({ - lastUsedAt: LessThan(new Date(Date.now() - this.config.deactivateAntennaThreshold)), + lastUsedAt: LessThan(new Date(this.timeService.now - this.config.deactivateAntennaThreshold)), }, { isActive: false, }); @@ -58,7 +60,7 @@ export class CleanProcessorService { const expiredRoleAssignments = await this.roleAssignmentsRepository.createQueryBuilder('assign') .where('assign.expiresAt IS NOT NULL') - .andWhere('assign.expiresAt < :now', { now: new Date() }) + .andWhere('assign.expiresAt < :now', { now: this.timeService.date }) .getMany(); if (expiredRoleAssignments.length > 0) { diff --git a/packages/backend/src/queue/processors/CleanRemoteFilesProcessorService.ts b/packages/backend/src/queue/processors/CleanRemoteFilesProcessorService.ts index 354f351358..9729918aae 100644 --- a/packages/backend/src/queue/processors/CleanRemoteFilesProcessorService.ts +++ b/packages/backend/src/queue/processors/CleanRemoteFilesProcessorService.ts @@ -10,11 +10,12 @@ import type { MiDriveFile, DriveFilesRepository } from '@/models/_.js'; import { MiUser } from '@/models/_.js'; import type Logger from '@/logger.js'; import { DriveService } from '@/core/DriveService.js'; +import { IdService } from '@/core/IdService.js'; +import { TimeService } from '@/core/TimeService.js'; import { bindThis } from '@/decorators.js'; import { QueueLoggerService } from '../QueueLoggerService.js'; import type * as Bull from 'bullmq'; import type { CleanRemoteFilesJobData } from '../types.js'; -import { IdService } from '@/core/IdService.js'; @Injectable() export class CleanRemoteFilesProcessorService { @@ -27,6 +28,7 @@ export class CleanRemoteFilesProcessorService { private driveService: DriveService, private queueLoggerService: QueueLoggerService, private idService: IdService, + private readonly timeService: TimeService, ) { this.logger = this.queueLoggerService.logger.createSubLogger('clean-remote-files'); } @@ -35,7 +37,7 @@ export class CleanRemoteFilesProcessorService { public async process(job: Bull.Job): Promise { this.logger.info('Deleting cached remote files...'); - const olderThanTimestamp = Date.now() - (job.data.olderThanSeconds ?? 0) * 1000; + const olderThanTimestamp = this.timeService.now - (job.data.olderThanSeconds ?? 0) * 1000; const olderThanDate = new Date(olderThanTimestamp); const keepFilesInUse = job.data.keepFilesInUse ?? false; let deletedCount = 0; diff --git a/packages/backend/src/queue/processors/DeliverProcessorService.ts b/packages/backend/src/queue/processors/DeliverProcessorService.ts index 1a5852067d..548b7775d8 100644 --- a/packages/backend/src/queue/processors/DeliverProcessorService.ts +++ b/packages/backend/src/queue/processors/DeliverProcessorService.ts @@ -19,6 +19,7 @@ import ApRequestChart from '@/core/chart/charts/ap-request.js'; import FederationChart from '@/core/chart/charts/federation.js'; import { StatusError } from '@/misc/status-error.js'; import { UtilityService } from '@/core/UtilityService.js'; +import { TimeService } from '@/core/TimeService.js'; import { bindThis } from '@/decorators.js'; import { QueueLoggerService } from '../QueueLoggerService.js'; import type { DeliverJobData } from '../types.js'; @@ -42,6 +43,7 @@ export class DeliverProcessorService { private apRequestChart: ApRequestChart, private federationChart: FederationChart, private queueLoggerService: QueueLoggerService, + private readonly timeService: TimeService, ) { this.logger = this.queueLoggerService.logger.createSubLogger('deliver'); } @@ -99,11 +101,11 @@ export class DeliverProcessorService { if (!i.isNotResponding) { this.federatedInstanceService.update(i.id, { isNotResponding: true, - notRespondingSince: new Date(), + notRespondingSince: this.timeService.date, }); } else if (i.notRespondingSince) { // 1週間以上不通ならサスペンド - if (i.suspensionState === 'none' && i.notRespondingSince.getTime() <= Date.now() - 1000 * 60 * 60 * 24 * 7) { + if (i.suspensionState === 'none' && i.notRespondingSince.getTime() <= this.timeService.now - 1000 * 60 * 60 * 24 * 7) { this.federatedInstanceService.update(i.id, { suspensionState: 'autoSuspendedForNotResponding', }); @@ -112,7 +114,7 @@ export class DeliverProcessorService { // isNotRespondingがtrueでnotRespondingSinceがnullの場合はnotRespondingSinceをセット // notRespondingSinceは新たな機能なので、それ以前のデータにはnotRespondingSinceがない場合がある this.federatedInstanceService.update(i.id, { - notRespondingSince: new Date(), + notRespondingSince: this.timeService.date, }); } diff --git a/packages/backend/src/queue/processors/ExportAccountDataProcessorService.ts b/packages/backend/src/queue/processors/ExportAccountDataProcessorService.ts index 58d542635f..e9a912a1be 100644 --- a/packages/backend/src/queue/processors/ExportAccountDataProcessorService.ts +++ b/packages/backend/src/queue/processors/ExportAccountDataProcessorService.ts @@ -22,6 +22,7 @@ import { Packed } from '@/misc/json-schema.js'; import { UtilityService } from '@/core/UtilityService.js'; import { DownloadService } from '@/core/DownloadService.js'; import { EmailService } from '@/core/EmailService.js'; +import { TimeService } from '@/core/TimeService.js'; import { renderInlineError } from '@/misc/render-inline-error.js'; import { QueueLoggerService } from '../QueueLoggerService.js'; import type * as Bull from 'bullmq'; @@ -80,6 +81,7 @@ export class ExportAccountDataProcessorService { private downloadService: DownloadService, private emailService: EmailService, private queueLoggerService: QueueLoggerService, + private readonly timeService: TimeService, ) { this.logger = this.queueLoggerService.logger.createSubLogger('export-account-data'); } @@ -125,7 +127,7 @@ export class ExportAccountDataProcessorService { }); }; - await writeUser(`{"metaVersion":1,"host":"${this.config.host}","exportedAt":"${new Date().toString()}","user":[`); + await writeUser(`{"metaVersion":1,"host":"${this.config.host}","exportedAt":"${this.timeService.date.toString()}","user":[`); // eslint-disable-next-line @typescript-eslint/no-unused-vars const { host, uri, sharedInbox, followersUri, lastFetchedAt, inbox, ...userTrimmed } = user; @@ -160,7 +162,7 @@ export class ExportAccountDataProcessorService { // eslint-disable-next-line @typescript-eslint/no-unused-vars const { emailVerifyCode, twoFactorBackupSecret, twoFactorSecret, password, twoFactorTempSecret, userHost, ...profileTrimmed } = profile; - await writeProfile(`{"metaVersion":1,"host":"${this.config.host}","exportedAt":"${new Date().toString()}","profile":[`); + await writeProfile(`{"metaVersion":1,"host":"${this.config.host}","exportedAt":"${this.timeService.date.toString()}","profile":[`); await writeProfile(JSON.stringify(profileTrimmed)); @@ -191,7 +193,7 @@ export class ExportAccountDataProcessorService { }); }; - await writeIPs(`{"metaVersion":1,"host":"${this.config.host}","exportedAt":"${new Date().toString()}","ips":[`); + await writeIPs(`{"metaVersion":1,"host":"${this.config.host}","exportedAt":"${this.timeService.date.toString()}","ips":[`); for (const signin of signins) { // eslint-disable-next-line @typescript-eslint/no-unused-vars @@ -226,7 +228,7 @@ export class ExportAccountDataProcessorService { }); }; - await writeNotes(`{"metaVersion":1,"host":"${this.config.host}","exportedAt":"${new Date().toString()}","notes":[`); + await writeNotes(`{"metaVersion":1,"host":"${this.config.host}","exportedAt":"${this.timeService.date.toString()}","notes":[`); let noteCursor: MiNote['id'] | null = null; let exportedNotesCount = 0; @@ -287,7 +289,7 @@ export class ExportAccountDataProcessorService { }); }; - await writeFollowing(`{"metaVersion":1,"host":"${this.config.host}","exportedAt":"${new Date().toString()}","followings":[`); + await writeFollowing(`{"metaVersion":1,"host":"${this.config.host}","exportedAt":"${this.timeService.date.toString()}","followings":[`); let followingsCursor: MiFollowing['id'] | null = null; let exportedFollowingsCount = 0; @@ -321,7 +323,7 @@ export class ExportAccountDataProcessorService { continue; } - if (u.updatedAt && (Date.now() - u.updatedAt.getTime() > 1000 * 60 * 60 * 24 * 90)) { + if (u.updatedAt && (this.timeService.now - u.updatedAt.getTime() > 1000 * 60 * 60 * 24 * 90)) { continue; } @@ -357,7 +359,7 @@ export class ExportAccountDataProcessorService { }); }; - await writeFollowers(`{"metaVersion":1,"host":"${this.config.host}","exportedAt":"${new Date().toString()}","followers":[`); + await writeFollowers(`{"metaVersion":1,"host":"${this.config.host}","exportedAt":"${this.timeService.date.toString()}","followers":[`); let followersCursor: MiFollowing['id'] | null = null; let exportedFollowersCount = 0; @@ -420,7 +422,7 @@ export class ExportAccountDataProcessorService { fs.mkdirSync(`${path}/files`); - await writeDrive(`{"metaVersion":1,"host":"${this.config.host}","exportedAt":"${new Date().toString()}","drive":[`); + await writeDrive(`{"metaVersion":1,"host":"${this.config.host}","exportedAt":"${this.timeService.date.toString()}","drive":[`); const driveFiles = await this.driveFilesRepository.find({ where: { userId: user.id } }); @@ -476,7 +478,7 @@ export class ExportAccountDataProcessorService { }); }; - await writeMuting(`{"metaVersion":1,"host":"${this.config.host}","exportedAt":"${new Date().toString()}","mutings":[`); + await writeMuting(`{"metaVersion":1,"host":"${this.config.host}","exportedAt":"${this.timeService.date.toString()}","mutings":[`); let exportedMutingCount = 0; let mutingCursor: MiMuting['id'] | null = null; @@ -539,7 +541,7 @@ export class ExportAccountDataProcessorService { }); }; - await writeBlocking(`{"metaVersion":1,"host":"${this.config.host}","exportedAt":"${new Date().toString()}","blockings":[`); + await writeBlocking(`{"metaVersion":1,"host":"${this.config.host}","exportedAt":"${this.timeService.date.toString()}","blockings":[`); let exportedBlockingCount = 0; let blockingCursor: MiBlocking['id'] | null = null; @@ -601,7 +603,7 @@ export class ExportAccountDataProcessorService { }); }; - await writeFavorite(`{"metaVersion":1,"host":"${this.config.host}","exportedAt":"${new Date().toString()}","favorites":[`); + await writeFavorite(`{"metaVersion":1,"host":"${this.config.host}","exportedAt":"${this.timeService.date.toString()}","favorites":[`); let exportedFavoritesCount = 0; let favoriteCursor: MiNoteFavorite['id'] | null = null; @@ -662,7 +664,7 @@ export class ExportAccountDataProcessorService { }); }; - await writeAntenna(`{"metaVersion":1,"host":"${this.config.host}","exportedAt":"${new Date().toString()}","antennas":[`); + await writeAntenna(`{"metaVersion":1,"host":"${this.config.host}","exportedAt":"${this.timeService.date.toString()}","antennas":[`); const antennas = await this.antennasRepository.findBy({ userId: user.id }); @@ -749,7 +751,7 @@ export class ExportAccountDataProcessorService { archiveStream.on('close', async () => { this.logger.debug(`Exported to path: ${archivePath}`); - const fileName = 'data-request-' + dateFormat(new Date(), 'yyyy-MM-dd-HH-mm-ss') + '.zip'; + const fileName = 'data-request-' + dateFormat(this.timeService.date, 'yyyy-MM-dd-HH-mm-ss') + '.zip'; const driveFile = await this.driveService.addFile({ user, path: archivePath, name: fileName, force: true }); this.logger.debug(`Exported to drive: ${driveFile.id}`); diff --git a/packages/backend/src/queue/processors/ExportAntennasProcessorService.ts b/packages/backend/src/queue/processors/ExportAntennasProcessorService.ts index 61d76da5ac..09e5a9cb00 100644 --- a/packages/backend/src/queue/processors/ExportAntennasProcessorService.ts +++ b/packages/backend/src/queue/processors/ExportAntennasProcessorService.ts @@ -15,6 +15,7 @@ import { bindThis } from '@/decorators.js'; import { createTemp } from '@/misc/create-temp.js'; import { UtilityService } from '@/core/UtilityService.js'; import { NotificationService } from '@/core/NotificationService.js'; +import { TimeService } from '@/core/TimeService.js'; import { QueueLoggerService } from '../QueueLoggerService.js'; import type { DBExportAntennasData } from '../types.js'; import type * as Bull from 'bullmq'; @@ -37,6 +38,7 @@ export class ExportAntennasProcessorService { private utilityService: UtilityService, private queueLoggerService: QueueLoggerService, private notificationService: NotificationService, + private readonly timeService: TimeService, ) { this.logger = this.queueLoggerService.logger.createSubLogger('export-antennas'); } @@ -98,7 +100,7 @@ export class ExportAntennasProcessorService { write(']'); stream.end(); - const fileName = 'antennas-' + DateFormat(new Date(), 'yyyy-MM-dd-HH-mm-ss') + '.json'; + const fileName = 'antennas-' + DateFormat(this.timeService.date, 'yyyy-MM-dd-HH-mm-ss') + '.json'; const driveFile = await this.driveService.addFile({ user, path, name: fileName, force: true, ext: 'json' }); this.logger.debug('Exported to: ' + driveFile.id); diff --git a/packages/backend/src/queue/processors/ExportBlockingProcessorService.ts b/packages/backend/src/queue/processors/ExportBlockingProcessorService.ts index 4c17c3f718..6716126b0e 100644 --- a/packages/backend/src/queue/processors/ExportBlockingProcessorService.ts +++ b/packages/backend/src/queue/processors/ExportBlockingProcessorService.ts @@ -14,6 +14,7 @@ import { DriveService } from '@/core/DriveService.js'; import { createTemp } from '@/misc/create-temp.js'; import { UtilityService } from '@/core/UtilityService.js'; import { NotificationService } from '@/core/NotificationService.js'; +import { TimeService } from '@/core/TimeService.js'; import { bindThis } from '@/decorators.js'; import { QueueLoggerService } from '../QueueLoggerService.js'; import type * as Bull from 'bullmq'; @@ -34,6 +35,7 @@ export class ExportBlockingProcessorService { private notificationService: NotificationService, private driveService: DriveService, private queueLoggerService: QueueLoggerService, + private readonly timeService: TimeService, ) { this.logger = this.queueLoggerService.logger.createSubLogger('export-blocking'); } @@ -108,7 +110,7 @@ export class ExportBlockingProcessorService { stream.end(); this.logger.debug(`Exported to: ${path}`); - const fileName = 'blocking-' + dateFormat(new Date(), 'yyyy-MM-dd-HH-mm-ss') + '.csv'; + const fileName = 'blocking-' + dateFormat(this.timeService.date, 'yyyy-MM-dd-HH-mm-ss') + '.csv'; const driveFile = await this.driveService.addFile({ user, path, name: fileName, force: true, ext: 'csv' }); this.logger.debug(`Exported to: ${driveFile.id}`); diff --git a/packages/backend/src/queue/processors/ExportClipsProcessorService.ts b/packages/backend/src/queue/processors/ExportClipsProcessorService.ts index 1d34d2b4e6..f2cfc88bde 100644 --- a/packages/backend/src/queue/processors/ExportClipsProcessorService.ts +++ b/packages/backend/src/queue/processors/ExportClipsProcessorService.ts @@ -20,6 +20,7 @@ import { DriveFileEntityService } from '@/core/entities/DriveFileEntityService.j import { Packed } from '@/misc/json-schema.js'; import { IdService } from '@/core/IdService.js'; import { NotificationService } from '@/core/NotificationService.js'; +import { TimeService } from '@/core/TimeService.js'; import { QueueLoggerService } from '../QueueLoggerService.js'; import type * as Bull from 'bullmq'; import type { DbJobDataWithUser } from '../types.js'; @@ -45,6 +46,7 @@ export class ExportClipsProcessorService { private queueLoggerService: QueueLoggerService, private idService: IdService, private notificationService: NotificationService, + private readonly timeService: TimeService, ) { this.logger = this.queueLoggerService.logger.createSubLogger('export-clips'); } @@ -78,7 +80,7 @@ export class ExportClipsProcessorService { this.logger.debug(`Exported to: ${path}`); - const fileName = 'clips-' + dateFormat(new Date(), 'yyyy-MM-dd-HH-mm-ss') + '.json'; + const fileName = 'clips-' + dateFormat(this.timeService.date, 'yyyy-MM-dd-HH-mm-ss') + '.json'; const driveFile = await this.driveService.addFile({ user, path, name: fileName, force: true, ext: 'json' }); this.logger.debug(`Exported to: ${driveFile.id}`); diff --git a/packages/backend/src/queue/processors/ExportCustomEmojisProcessorService.ts b/packages/backend/src/queue/processors/ExportCustomEmojisProcessorService.ts index b8f208bbfc..2e53afaca9 100644 --- a/packages/backend/src/queue/processors/ExportCustomEmojisProcessorService.ts +++ b/packages/backend/src/queue/processors/ExportCustomEmojisProcessorService.ts @@ -17,6 +17,7 @@ import { DriveService } from '@/core/DriveService.js'; import { createTemp, createTempDir } from '@/misc/create-temp.js'; import { DownloadService } from '@/core/DownloadService.js'; import { NotificationService } from '@/core/NotificationService.js'; +import { TimeService } from '@/core/TimeService.js'; import { bindThis } from '@/decorators.js'; import { QueueLoggerService } from '../QueueLoggerService.js'; import type * as Bull from 'bullmq'; @@ -39,6 +40,7 @@ export class ExportCustomEmojisProcessorService { private downloadService: DownloadService, private queueLoggerService: QueueLoggerService, private notificationService: NotificationService, + private readonly timeService: TimeService, ) { this.logger = this.queueLoggerService.logger.createSubLogger('export-custom-emojis'); } @@ -133,7 +135,7 @@ export class ExportCustomEmojisProcessorService { archiveStream.on('close', async () => { this.logger.debug(`Exported to: ${archivePath}`); - const fileName = 'custom-emojis-' + dateFormat(new Date(), 'yyyy-MM-dd-HH-mm-ss') + '.zip'; + const fileName = 'custom-emojis-' + dateFormat(this.timeService.date, 'yyyy-MM-dd-HH-mm-ss') + '.zip'; const driveFile = await this.driveService.addFile({ user, path: archivePath, name: fileName, force: true }); this.logger.debug(`Exported to: ${driveFile.id}`); diff --git a/packages/backend/src/queue/processors/ExportFavoritesProcessorService.ts b/packages/backend/src/queue/processors/ExportFavoritesProcessorService.ts index b5716f2d49..bf1f960b79 100644 --- a/packages/backend/src/queue/processors/ExportFavoritesProcessorService.ts +++ b/packages/backend/src/queue/processors/ExportFavoritesProcessorService.ts @@ -17,6 +17,7 @@ import type { MiNote } from '@/models/Note.js'; import { bindThis } from '@/decorators.js'; import { IdService } from '@/core/IdService.js'; import { NotificationService } from '@/core/NotificationService.js'; +import { TimeService } from '@/core/TimeService.js'; import { QueueLoggerService } from '../QueueLoggerService.js'; import type * as Bull from 'bullmq'; import type { DbJobDataWithUser } from '../types.js'; @@ -39,6 +40,7 @@ export class ExportFavoritesProcessorService { private queueLoggerService: QueueLoggerService, private idService: IdService, private notificationService: NotificationService, + private readonly timeService: TimeService, ) { this.logger = this.queueLoggerService.logger.createSubLogger('export-favorites'); } @@ -122,7 +124,7 @@ export class ExportFavoritesProcessorService { stream.end(); this.logger.debug(`Exported to: ${path}`); - const fileName = 'favorites-' + dateFormat(new Date(), 'yyyy-MM-dd-HH-mm-ss') + '.json'; + const fileName = 'favorites-' + dateFormat(this.timeService.date, 'yyyy-MM-dd-HH-mm-ss') + '.json'; const driveFile = await this.driveService.addFile({ user, path, name: fileName, force: true, ext: 'json' }); this.logger.debug(`Exported to: ${driveFile.id}`); diff --git a/packages/backend/src/queue/processors/ExportFollowingProcessorService.ts b/packages/backend/src/queue/processors/ExportFollowingProcessorService.ts index 883f35e366..1e85f27b2d 100644 --- a/packages/backend/src/queue/processors/ExportFollowingProcessorService.ts +++ b/packages/backend/src/queue/processors/ExportFollowingProcessorService.ts @@ -15,6 +15,7 @@ import { createTemp } from '@/misc/create-temp.js'; import type { MiFollowing } from '@/models/Following.js'; import { UtilityService } from '@/core/UtilityService.js'; import { NotificationService } from '@/core/NotificationService.js'; +import { TimeService } from '@/core/TimeService.js'; import { bindThis } from '@/decorators.js'; import { QueueLoggerService } from '../QueueLoggerService.js'; import type * as Bull from 'bullmq'; @@ -38,6 +39,7 @@ export class ExportFollowingProcessorService { private driveService: DriveService, private queueLoggerService: QueueLoggerService, private notificationService: NotificationService, + private readonly timeService: TimeService, ) { this.logger = this.queueLoggerService.logger.createSubLogger('export-following'); } @@ -91,7 +93,7 @@ export class ExportFollowingProcessorService { continue; } - if (job.data.excludeInactive && u.updatedAt && (Date.now() - u.updatedAt.getTime() > 1000 * 60 * 60 * 24 * 90)) { + if (job.data.excludeInactive && u.updatedAt && (this.timeService.now - u.updatedAt.getTime() > 1000 * 60 * 60 * 24 * 90)) { continue; } @@ -112,7 +114,7 @@ export class ExportFollowingProcessorService { stream.end(); this.logger.debug(`Exported to: ${path}`); - const fileName = 'following-' + dateFormat(new Date(), 'yyyy-MM-dd-HH-mm-ss') + '.csv'; + const fileName = 'following-' + dateFormat(this.timeService.date, 'yyyy-MM-dd-HH-mm-ss') + '.csv'; const driveFile = await this.driveService.addFile({ user, path, name: fileName, force: true, ext: 'csv' }); this.logger.debug(`Exported to: ${driveFile.id}`); diff --git a/packages/backend/src/queue/processors/ExportMutingProcessorService.ts b/packages/backend/src/queue/processors/ExportMutingProcessorService.ts index 9cdb94beaf..56d4e12f71 100644 --- a/packages/backend/src/queue/processors/ExportMutingProcessorService.ts +++ b/packages/backend/src/queue/processors/ExportMutingProcessorService.ts @@ -15,6 +15,7 @@ import { createTemp } from '@/misc/create-temp.js'; import { UtilityService } from '@/core/UtilityService.js'; import { NotificationService } from '@/core/NotificationService.js'; import { bindThis } from '@/decorators.js'; +import { TimeService } from '@/core/TimeService.js'; import { QueueLoggerService } from '../QueueLoggerService.js'; import type * as Bull from 'bullmq'; import type { DbJobDataWithUser } from '../types.js'; @@ -34,6 +35,7 @@ export class ExportMutingProcessorService { private driveService: DriveService, private queueLoggerService: QueueLoggerService, private notificationService: NotificationService, + private readonly timeService: TimeService, ) { this.logger = this.queueLoggerService.logger.createSubLogger('export-muting'); } @@ -109,7 +111,7 @@ export class ExportMutingProcessorService { stream.end(); this.logger.debug(`Exported to: ${path}`); - const fileName = 'mute-' + dateFormat(new Date(), 'yyyy-MM-dd-HH-mm-ss') + '.csv'; + const fileName = 'mute-' + dateFormat(this.timeService.date, 'yyyy-MM-dd-HH-mm-ss') + '.csv'; const driveFile = await this.driveService.addFile({ user, path, name: fileName, force: true, ext: 'csv' }); this.logger.debug(`Exported to: ${driveFile.id}`); diff --git a/packages/backend/src/queue/processors/ExportNotesProcessorService.ts b/packages/backend/src/queue/processors/ExportNotesProcessorService.ts index 7d49a8dab2..27b2abb17e 100644 --- a/packages/backend/src/queue/processors/ExportNotesProcessorService.ts +++ b/packages/backend/src/queue/processors/ExportNotesProcessorService.ts @@ -21,6 +21,7 @@ import { IdService } from '@/core/IdService.js'; import { NotificationService } from '@/core/NotificationService.js'; import { JsonArrayStream } from '@/misc/JsonArrayStream.js'; import { FileWriterStream } from '@/misc/FileWriterStream.js'; +import { TimeService } from '@/core/TimeService.js'; import { QueueLoggerService } from '../QueueLoggerService.js'; import type * as Bull from 'bullmq'; import type { DbJobDataWithUser } from '../types.js'; @@ -114,6 +115,7 @@ export class ExportNotesProcessorService { private driveFileEntityService: DriveFileEntityService, private idService: IdService, private notificationService: NotificationService, + private readonly timeService: TimeService, ) { this.logger = this.queueLoggerService.logger.createSubLogger('export-notes'); } @@ -149,7 +151,7 @@ export class ExportNotesProcessorService { this.logger.debug(`Exported to: ${path}`); - const fileName = 'notes-' + dateFormat(new Date(), 'yyyy-MM-dd-HH-mm-ss') + '.json'; + const fileName = 'notes-' + dateFormat(this.timeService.date, 'yyyy-MM-dd-HH-mm-ss') + '.json'; const driveFile = await this.driveService.addFile({ user, path, name: fileName, force: true, ext: 'json' }); this.logger.debug(`Exported to: ${driveFile.id}`); diff --git a/packages/backend/src/queue/processors/ExportUserListsProcessorService.ts b/packages/backend/src/queue/processors/ExportUserListsProcessorService.ts index 43043e3a26..5e3bc8902b 100644 --- a/packages/backend/src/queue/processors/ExportUserListsProcessorService.ts +++ b/packages/backend/src/queue/processors/ExportUserListsProcessorService.ts @@ -14,6 +14,7 @@ import { DriveService } from '@/core/DriveService.js'; import { createTemp } from '@/misc/create-temp.js'; import { UtilityService } from '@/core/UtilityService.js'; import { NotificationService } from '@/core/NotificationService.js'; +import { TimeService } from '@/core/TimeService.js'; import { bindThis } from '@/decorators.js'; import { QueueLoggerService } from '../QueueLoggerService.js'; import type * as Bull from 'bullmq'; @@ -37,6 +38,7 @@ export class ExportUserListsProcessorService { private driveService: DriveService, private queueLoggerService: QueueLoggerService, private notificationService: NotificationService, + private readonly timeService: TimeService, ) { this.logger = this.queueLoggerService.logger.createSubLogger('export-user-lists'); } @@ -88,7 +90,7 @@ export class ExportUserListsProcessorService { stream.end(); this.logger.debug(`Exported to: ${path}`); - const fileName = 'user-lists-' + dateFormat(new Date(), 'yyyy-MM-dd-HH-mm-ss') + '.csv'; + const fileName = 'user-lists-' + dateFormat(this.timeService.date, 'yyyy-MM-dd-HH-mm-ss') + '.csv'; const driveFile = await this.driveService.addFile({ user, path, name: fileName, force: true, ext: 'csv' }); this.logger.debug(`Exported to: ${driveFile.id}`); diff --git a/packages/backend/src/queue/processors/ImportAntennasProcessorService.ts b/packages/backend/src/queue/processors/ImportAntennasProcessorService.ts index 0fca613f29..3060377b18 100644 --- a/packages/backend/src/queue/processors/ImportAntennasProcessorService.ts +++ b/packages/backend/src/queue/processors/ImportAntennasProcessorService.ts @@ -12,6 +12,7 @@ import type { AntennasRepository, UsersRepository } from '@/models/_.js'; import { DI } from '@/di-symbols.js'; import { bindThis } from '@/decorators.js'; import { NotificationService } from '@/core/NotificationService.js'; +import { TimeService } from '@/core/TimeService.js'; import { QueueLoggerService } from '../QueueLoggerService.js'; import { DBAntennaImportJobData } from '../types.js'; import type * as Bull from 'bullmq'; @@ -67,6 +68,7 @@ export class ImportAntennasProcessorService { private idService: IdService, private globalEventService: GlobalEventService, private notificationService: NotificationService, + private readonly timeService: TimeService, ) { this.logger = this.queueLoggerService.logger.createSubLogger('import-antennas'); } @@ -81,7 +83,7 @@ export class ImportAntennasProcessorService { this.logger.debug(`Importing antennas of ${job.data.user.id} ...`); - const now = new Date(); + const now = this.timeService.date; try { for (const antenna of job.data.antenna) { if (antenna.keywords.length === 0 || antenna.keywords[0].every(x => x === '')) continue; diff --git a/packages/backend/src/queue/processors/InboxProcessorService.ts b/packages/backend/src/queue/processors/InboxProcessorService.ts index 5f82d558b3..366d759649 100644 --- a/packages/backend/src/queue/processors/InboxProcessorService.ts +++ b/packages/backend/src/queue/processors/InboxProcessorService.ts @@ -31,6 +31,7 @@ import { SkApInboxLog } from '@/models/_.js'; import type { Config } from '@/config.js'; import { ApLogService, calculateDurationSince } from '@/core/ApLogService.js'; import { UpdateInstanceQueue } from '@/core/UpdateInstanceQueue.js'; +import { TimeService } from '@/core/TimeService.js'; import { isRetryableError } from '@/misc/is-retryable-error.js'; import { renderInlineError } from '@/misc/render-inline-error.js'; import { QueueLoggerService } from '../QueueLoggerService.js'; @@ -66,6 +67,7 @@ export class InboxProcessorService implements OnApplicationShutdown { private queueLoggerService: QueueLoggerService, private readonly apLogService: ApLogService, private readonly updateInstanceQueue: UpdateInstanceQueue, + private readonly timeService: TimeService, ) { this.logger = this.queueLoggerService.logger.createSubLogger('inbox'); } @@ -263,7 +265,7 @@ export class InboxProcessorService implements OnApplicationShutdown { if (i == null) return; this.updateInstanceQueue.enqueue(i.id, { - latestRequestReceivedAt: new Date(), + latestRequestReceivedAt: this.timeService.date, shouldUnsuspend: i.suspensionState === 'autoSuspendedForNotResponding', }); @@ -322,7 +324,7 @@ export class InboxProcessorService implements OnApplicationShutdown { @bindThis public async performUpdateInstance(id: string, job: UpdateInstanceJob) { await this.federatedInstanceService.update(id, { - latestRequestReceivedAt: new Date(), + latestRequestReceivedAt: this.timeService.date, isNotResponding: false, // もしサーバーが死んでるために配信が止まっていた場合には自動的に復活させてあげる suspensionState: job.shouldUnsuspend ? 'none' : undefined, diff --git a/packages/backend/src/queue/processors/ScheduleNotePostProcessorService.ts b/packages/backend/src/queue/processors/ScheduleNotePostProcessorService.ts index 73088f3312..bc380d5848 100644 --- a/packages/backend/src/queue/processors/ScheduleNotePostProcessorService.ts +++ b/packages/backend/src/queue/processors/ScheduleNotePostProcessorService.ts @@ -13,6 +13,7 @@ import { NotificationService } from '@/core/NotificationService.js'; import { IdentifiableError } from '@/misc/identifiable-error.js'; import type { MiScheduleNoteType } from '@/models/NoteSchedule.js'; import { renderInlineError } from '@/misc/render-inline-error.js'; +import { TimeService } from '@/core/TimeService.js'; import { QueueLoggerService } from '../QueueLoggerService.js'; import type * as Bull from 'bullmq'; import type { ScheduleNotePostJobData } from '../types.js'; @@ -37,6 +38,7 @@ export class ScheduleNotePostProcessorService { private noteCreateService: NoteCreateService, private queueLoggerService: QueueLoggerService, private notificationService: NotificationService, + private readonly timeService: TimeService, ) { this.logger = this.queueLoggerService.logger.createSubLogger('schedule-note-post'); } @@ -118,7 +120,7 @@ export class ScheduleNotePostProcessorService { const createdNote = await this.noteCreateService.create(me, { ...note, - createdAt: new Date(), + createdAt: this.timeService.date, files, poll: note.poll ? { choices: note.poll.choices, diff --git a/packages/backend/src/queue/processors/SystemWebhookDeliverProcessorService.ts b/packages/backend/src/queue/processors/SystemWebhookDeliverProcessorService.ts index f9fcd1e928..56138e831b 100644 --- a/packages/backend/src/queue/processors/SystemWebhookDeliverProcessorService.ts +++ b/packages/backend/src/queue/processors/SystemWebhookDeliverProcessorService.ts @@ -11,6 +11,7 @@ import type { Config } from '@/config.js'; import type Logger from '@/logger.js'; import { HttpRequestService } from '@/core/HttpRequestService.js'; import { StatusError } from '@/misc/status-error.js'; +import { TimeService } from '@/core/TimeService.js'; import { bindThis } from '@/decorators.js'; import { renderInlineError } from '@/misc/render-inline-error.js'; import { QueueLoggerService } from '../QueueLoggerService.js'; @@ -29,6 +30,7 @@ export class SystemWebhookDeliverProcessorService { private httpRequestService: HttpRequestService, private queueLoggerService: QueueLoggerService, + private readonly timeService: TimeService, ) { this.logger = this.queueLoggerService.logger.createSubLogger('webhook'); } @@ -58,7 +60,7 @@ export class SystemWebhookDeliverProcessorService { }); this.systemWebhooksRepository.update({ id: job.data.webhookId }, { - latestSentAt: new Date(), + latestSentAt: this.timeService.date, latestStatus: res.status, }); @@ -67,7 +69,7 @@ export class SystemWebhookDeliverProcessorService { this.logger.error(`Failed to send webhook: ${renderInlineError(res)}`); this.systemWebhooksRepository.update({ id: job.data.webhookId }, { - latestSentAt: new Date(), + latestSentAt: this.timeService.date, latestStatus: res instanceof StatusError ? res.statusCode : 1, }); diff --git a/packages/backend/src/queue/processors/UserWebhookDeliverProcessorService.ts b/packages/backend/src/queue/processors/UserWebhookDeliverProcessorService.ts index 0208ce6038..e4bba87df4 100644 --- a/packages/backend/src/queue/processors/UserWebhookDeliverProcessorService.ts +++ b/packages/backend/src/queue/processors/UserWebhookDeliverProcessorService.ts @@ -11,6 +11,7 @@ import type { Config } from '@/config.js'; import type Logger from '@/logger.js'; import { HttpRequestService } from '@/core/HttpRequestService.js'; import { StatusError } from '@/misc/status-error.js'; +import { TimeService } from '@/core/TimeService.js'; import { bindThis } from '@/decorators.js'; import { QueueLoggerService } from '../QueueLoggerService.js'; import { UserWebhookDeliverJobData } from '../types.js'; @@ -28,6 +29,7 @@ export class UserWebhookDeliverProcessorService { private httpRequestService: HttpRequestService, private queueLoggerService: QueueLoggerService, + private readonly timeService: TimeService, ) { this.logger = this.queueLoggerService.logger.createSubLogger('webhook'); } @@ -58,14 +60,14 @@ export class UserWebhookDeliverProcessorService { }); this.webhooksRepository.update({ id: job.data.webhookId }, { - latestSentAt: new Date(), + latestSentAt: this.timeService.date, latestStatus: res.status, }); return 'Success'; } catch (res) { this.webhooksRepository.update({ id: job.data.webhookId }, { - latestSentAt: new Date(), + latestSentAt: this.timeService.date, latestStatus: res instanceof StatusError ? res.statusCode : 1, }); diff --git a/packages/backend/src/server/api/ApiCallService.ts b/packages/backend/src/server/api/ApiCallService.ts index ac3f7ae0f3..9c79434461 100644 --- a/packages/backend/src/server/api/ApiCallService.ts +++ b/packages/backend/src/server/api/ApiCallService.ts @@ -20,6 +20,7 @@ import { RoleService } from '@/core/RoleService.js'; import type { Config } from '@/config.js'; import { sendRateLimitHeaders } from '@/misc/rate-limit-utils.js'; import { SkRateLimiterService } from '@/server/SkRateLimiterService.js'; +import { TimeService, type TimerHandle } from '@/core/TimeService.js'; import { renderInlineError } from '@/misc/render-inline-error.js'; import { renderFullError } from '@/misc/render-full-error.js'; import { ApiError } from './error.js'; @@ -39,7 +40,7 @@ const accessDenied = { export class ApiCallService implements OnApplicationShutdown { private logger: Logger; private userIpHistories: Map>; - private userIpHistoriesClearIntervalId: NodeJS.Timeout; + private userIpHistoriesClearIntervalId: TimerHandle; constructor( @Inject(DI.meta) @@ -55,13 +56,14 @@ export class ApiCallService implements OnApplicationShutdown { private rateLimiterService: SkRateLimiterService, private roleService: RoleService, private apiLoggerService: ApiLoggerService, + private readonly timeService: TimeService, ) { this.logger = this.apiLoggerService.logger; this.userIpHistories = new Map>(); - this.userIpHistoriesClearIntervalId = setInterval(() => { + this.userIpHistoriesClearIntervalId = this.timeService.startTimer(() => { this.userIpHistories.clear(); - }, 1000 * 60 * 60); + }, 1000 * 60 * 60, { repeated: true }); } #sendApiError(reply: FastifyReply, err: ApiError): void { @@ -284,7 +286,7 @@ export class ApiCallService implements OnApplicationShutdown { try { this.userIpsRepository.createQueryBuilder().insert().values({ - createdAt: new Date(), + createdAt: this.timeService.date, userId: user.id, ip: ip, }).orIgnore(true).execute(); @@ -456,7 +458,7 @@ export class ApiCallService implements OnApplicationShutdown { @bindThis public dispose(): void { - clearInterval(this.userIpHistoriesClearIntervalId); + this.timeService.stopTimer(this.userIpHistoriesClearIntervalId); } @bindThis diff --git a/packages/backend/src/server/api/AuthenticateService.ts b/packages/backend/src/server/api/AuthenticateService.ts index c70c50cee1..124c3649ff 100644 --- a/packages/backend/src/server/api/AuthenticateService.ts +++ b/packages/backend/src/server/api/AuthenticateService.ts @@ -15,6 +15,7 @@ import { isNativeUserToken } from '@/misc/token.js'; import { bindThis } from '@/decorators.js'; import { attachCallerId } from '@/misc/attach-caller-id.js'; import { CacheManagementService, type ManagedMemoryKVCache } from '@/core/CacheManagementService.js'; +import { TimeService } from '@/core/TimeService.js'; export class AuthenticationError extends Error { constructor(message: string) { @@ -38,6 +39,8 @@ export class AuthenticateService { private appsRepository: AppsRepository, private cacheService: CacheService, + private readonly timeService: TimeService, + cacheManagementService: CacheManagementService, ) { this.appCache = cacheManagementService.createMemoryKVCache(1000 * 60 * 60 * 24); // 1d @@ -75,7 +78,7 @@ export class AuthenticateService { } this.accessTokensRepository.update(accessToken.id, { - lastUsedAt: new Date(), + lastUsedAt: this.timeService.date, }); // Loaded by relation above diff --git a/packages/backend/src/server/api/SignupApiService.ts b/packages/backend/src/server/api/SignupApiService.ts index 86896264dd..d3c3740b9f 100644 --- a/packages/backend/src/server/api/SignupApiService.ts +++ b/packages/backend/src/server/api/SignupApiService.ts @@ -21,6 +21,7 @@ import { L_CHARS, secureRndstr } from '@/misc/secure-rndstr.js'; import { RoleService } from '@/core/RoleService.js'; import Logger from '@/logger.js'; import { LoggerService } from '@/core/LoggerService.js'; +import { TimeService } from '@/core/TimeService.js'; import { SigninService } from './SigninService.js'; import type { FastifyRequest, FastifyReply } from 'fastify'; @@ -60,6 +61,7 @@ export class SignupApiService { private emailService: EmailService, private roleService: RoleService, private loggerService: LoggerService, + private readonly timeService: TimeService, ) { this.logger = this.loggerService.getLogger('Signup'); } @@ -170,7 +172,7 @@ export class SignupApiService { return; } - if (ticket.expiresAt && ticket.expiresAt < new Date()) { + if (ticket.expiresAt && ticket.expiresAt < this.timeService.date) { reply.code(400); return; } @@ -184,7 +186,7 @@ export class SignupApiService { } // 認証しておらず、メール送信から30分以内ならエラー - if (ticket.usedAt && ticket.usedAt.getTime() + (1000 * 60 * 30) > Date.now()) { + if (ticket.usedAt && ticket.usedAt.getTime() + (1000 * 60 * 30) > this.timeService.now) { reply.code(400); return; } @@ -232,7 +234,7 @@ export class SignupApiService { if (ticket) { await this.registrationTicketsRepository.update(ticket.id, { - usedAt: new Date(), + usedAt: this.timeService.date, pendingUserId: pendingUser.id, }); } @@ -252,7 +254,7 @@ export class SignupApiService { if (ticket) { await this.registrationTicketsRepository.update(ticket.id, { - usedAt: new Date(), + usedAt: this.timeService.date, usedBy: account, usedById: account.id, }); @@ -289,7 +291,7 @@ export class SignupApiService { if (ticket) { await this.registrationTicketsRepository.update(ticket.id, { - usedAt: new Date(), + usedAt: this.timeService.date, usedBy: account, usedById: account.id, }); @@ -318,7 +320,7 @@ export class SignupApiService { try { const pendingUser = await this.userPendingsRepository.findOneByOrFail({ code }); - if (this.idService.parse(pendingUser.id).date.getTime() + (1000 * 60 * 30) < Date.now()) { + if (this.idService.parse(pendingUser.id).date.getTime() + (1000 * 60 * 30) < this.timeService.now) { throw new FastifyReplyError(400, 'EXPIRED'); } @@ -390,7 +392,7 @@ export class SignupApiService { private logIp(ip: string, ipDate: Date | null, userId: MiLocalUser['id']) { try { this.userIpsRepository.createQueryBuilder().insert().values({ - createdAt: ipDate ?? new Date(), + createdAt: ipDate ?? this.timeService.date, userId, ip, }).orIgnore(true).execute(); diff --git a/packages/backend/src/server/api/StreamingApiServerService.ts b/packages/backend/src/server/api/StreamingApiServerService.ts index f39ef51b7e..f6a8a867ef 100644 --- a/packages/backend/src/server/api/StreamingApiServerService.ts +++ b/packages/backend/src/server/api/StreamingApiServerService.ts @@ -24,6 +24,7 @@ import { LoggerService } from '@/core/LoggerService.js'; import type Logger from '@/logger.js'; import { SkRateLimiterService } from '@/server/SkRateLimiterService.js'; import { QueryService } from '@/core/QueryService.js'; +import { TimeService, type TimerHandle } from '@/core/TimeService.js'; import { AuthenticateService, AuthenticationError } from './AuthenticateService.js'; import MainStreamConnection from './stream/Connection.js'; import { ChannelsService } from './stream/ChannelsService.js'; @@ -38,7 +39,7 @@ export class StreamingApiServerService implements OnApplicationShutdown { #wss?: WebSocket.WebSocketServer; #connections = new Map(); #connectionsByClient = new Map>(); // key: IP / user ID -> value: connection - #cleanConnectionsIntervalId: NodeJS.Timeout | null = null; + #cleanConnectionsIntervalId: TimerHandle | null = null; readonly #globalEv = new EventEmitter(); #logger: Logger; @@ -58,7 +59,9 @@ export class StreamingApiServerService implements OnApplicationShutdown { @Inject(DI.noteFavoritesRepository) private readonly noteFavoritesRepository: NoteFavoritesRepository, - private readonly queryService: QueryService, + @Inject(DI.config) + private config: Config, + private cacheService: CacheService, private authenticateService: AuthenticateService, private channelsService: ChannelsService, @@ -67,9 +70,8 @@ export class StreamingApiServerService implements OnApplicationShutdown { private channelFollowingService: ChannelFollowingService, private rateLimiterService: SkRateLimiterService, private loggerService: LoggerService, - - @Inject(DI.config) - private config: Config, + private readonly queryService: QueryService, + private readonly timeService: TimeService, ) { this.redisForSub.on('message', this.onRedis); this.#logger = loggerService.getLogger('streaming', 'coral'); @@ -205,6 +207,7 @@ export class StreamingApiServerService implements OnApplicationShutdown { this.notificationService, this.cacheService, this.channelFollowingService, + this.timeService, this.loggerService, user, app, requestIp, rateLimiter, @@ -265,17 +268,17 @@ export class StreamingApiServerService implements OnApplicationShutdown { await stream.listen(ev, connection); - this.#connections.set(connection, Date.now()); + this.#connections.set(connection, this.timeService.now); // TODO use collapsed queue - const userUpdateIntervalId = user ? setInterval(() => { + const userUpdateIntervalId = user ? this.timeService.startTimer(() => { this.usersService.updateLastActiveDate(user); - }, 1000 * 60 * 5) : null; + }, 1000 * 60 * 5, { repeated: true }) : null; if (user) { this.usersService.updateLastActiveDate(user); } const pong = () => { - this.#connections.set(connection, Date.now()); + this.#connections.set(connection, this.timeService.now); }; connection.once('close', () => { @@ -285,7 +288,7 @@ export class StreamingApiServerService implements OnApplicationShutdown { stream.dispose(); this.#globalEv.off('message', onRedisMessage); this.#connections.delete(connection); - if (userUpdateIntervalId) clearInterval(userUpdateIntervalId); + if (userUpdateIntervalId) this.timeService.stopTimer(userUpdateIntervalId); }); connection.on('error', this.onWsError); @@ -293,8 +296,8 @@ export class StreamingApiServerService implements OnApplicationShutdown { }); // 一定期間通信が無いコネクションは実際には切断されている可能性があるため定期的にterminateする - this.#cleanConnectionsIntervalId = setInterval(() => { - const now = Date.now(); + this.#cleanConnectionsIntervalId = this.timeService.startTimer(() => { + const now = this.timeService.now; for (const [connection, lastActive] of this.#connections.entries()) { if (now - lastActive > 1000 * 60 * 2) { connection.terminate(); @@ -303,13 +306,13 @@ export class StreamingApiServerService implements OnApplicationShutdown { connection.ping(); } } - }, 1000 * 60); + }, 1000 * 60, { repeated: true }); } @bindThis public async detach(): Promise { if (this.#cleanConnectionsIntervalId) { - clearInterval(this.#cleanConnectionsIntervalId); + this.timeService.stopTimer(this.#cleanConnectionsIntervalId); this.#cleanConnectionsIntervalId = null; } diff --git a/packages/backend/src/server/api/endpoints/admin/ad/list.ts b/packages/backend/src/server/api/endpoints/admin/ad/list.ts index 6406709cda..9009ef1525 100644 --- a/packages/backend/src/server/api/endpoints/admin/ad/list.ts +++ b/packages/backend/src/server/api/endpoints/admin/ad/list.ts @@ -8,6 +8,7 @@ import { Endpoint } from '@/server/api/endpoint-base.js'; import type { AdsRepository } from '@/models/_.js'; import { QueryService } from '@/core/QueryService.js'; import { DI } from '@/di-symbols.js'; +import { TimeService } from '@/core/TimeService.js'; export const meta = { tags: ['admin'], @@ -46,13 +47,14 @@ export default class extends Endpoint { // eslint- private adsRepository: AdsRepository, private queryService: QueryService, + private readonly timeService: TimeService, ) { super(meta, paramDef, async (ps, me) => { const query = this.queryService.makePaginationQuery(this.adsRepository.createQueryBuilder('ad'), ps.sinceId, ps.untilId); if (ps.publishing === true) { - query.andWhere('ad.expiresAt > :now', { now: new Date() }).andWhere('ad.startsAt <= :now', { now: new Date() }); + query.andWhere('ad.expiresAt > :now', { now: this.timeService.date }).andWhere('ad.startsAt <= :now', { now: this.timeService.date }); } else if (ps.publishing === false) { - query.andWhere('ad.expiresAt <= :now', { now: new Date() }).orWhere('ad.startsAt > :now', { now: new Date() }); + query.andWhere('ad.expiresAt <= :now', { now: this.timeService.date }).orWhere('ad.startsAt > :now', { now: this.timeService.date }); } const ads = await query.limit(ps.limit).getMany(); diff --git a/packages/backend/src/server/api/endpoints/admin/announcements/update.ts b/packages/backend/src/server/api/endpoints/admin/announcements/update.ts index 0553ef0426..37351c9b73 100644 --- a/packages/backend/src/server/api/endpoints/admin/announcements/update.ts +++ b/packages/backend/src/server/api/endpoints/admin/announcements/update.ts @@ -9,6 +9,7 @@ import type { AnnouncementsRepository } from '@/models/_.js'; import { DI } from '@/di-symbols.js'; import { AnnouncementService } from '@/core/AnnouncementService.js'; import { IdentifiableError } from '@/misc/identifiable-error.js'; +import { TimeService } from '@/core/TimeService.js'; import { ApiError } from '../../../error.js'; export const meta = { @@ -58,6 +59,7 @@ export default class extends Endpoint { // eslint- private announcementsRepository: AnnouncementsRepository, private announcementService: AnnouncementService, + private readonly timeService: TimeService, ) { super(meta, paramDef, async (ps, me) => { const announcement = await this.announcementsRepository.findOneBy({ id: ps.id }); @@ -66,7 +68,7 @@ export default class extends Endpoint { // eslint- try { await this.announcementService.update(announcement, { - updatedAt: new Date(), + updatedAt: this.timeService.date, title: ps.title, text: ps.text, /* eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing -- 空の文字列の場合、nullを渡すようにするため */ diff --git a/packages/backend/src/server/api/endpoints/admin/invite/list.ts b/packages/backend/src/server/api/endpoints/admin/invite/list.ts index e33a9a1aec..32f4c9de25 100644 --- a/packages/backend/src/server/api/endpoints/admin/invite/list.ts +++ b/packages/backend/src/server/api/endpoints/admin/invite/list.ts @@ -8,6 +8,7 @@ import { Endpoint } from '@/server/api/endpoint-base.js'; import type { RegistrationTicketsRepository } from '@/models/_.js'; import { InviteCodeEntityService } from '@/core/entities/InviteCodeEntityService.js'; import { DI } from '@/di-symbols.js'; +import { TimeService } from '@/core/TimeService.js'; export const meta = { tags: ['admin'], @@ -45,6 +46,7 @@ export default class extends Endpoint { // eslint- private registrationTicketsRepository: RegistrationTicketsRepository, private inviteCodeEntityService: InviteCodeEntityService, + private readonly timeService: TimeService, ) { super(meta, paramDef, async (ps, me) => { const query = this.registrationTicketsRepository.createQueryBuilder('ticket') @@ -54,7 +56,7 @@ export default class extends Endpoint { // eslint- switch (ps.type) { case 'unused': query.andWhere('ticket.usedBy IS NULL'); break; case 'used': query.andWhere('ticket.usedBy IS NOT NULL'); break; - case 'expired': query.andWhere('ticket.expiresAt < :now', { now: new Date() }); break; + case 'expired': query.andWhere('ticket.expiresAt < :now', { now: this.timeService.date }); break; } switch (ps.sort) { diff --git a/packages/backend/src/server/api/endpoints/admin/roles/assign.ts b/packages/backend/src/server/api/endpoints/admin/roles/assign.ts index b6c7953781..7080844d03 100644 --- a/packages/backend/src/server/api/endpoints/admin/roles/assign.ts +++ b/packages/backend/src/server/api/endpoints/admin/roles/assign.ts @@ -9,6 +9,7 @@ import type { RolesRepository, UsersRepository } from '@/models/_.js'; import { DI } from '@/di-symbols.js'; import { ApiError } from '@/server/api/error.js'; import { RoleService } from '@/core/RoleService.js'; +import { TimeService } from '@/core/TimeService.js'; export const meta = { tags: ['admin', 'role'], @@ -64,6 +65,7 @@ export default class extends Endpoint { // eslint- private rolesRepository: RolesRepository, private roleService: RoleService, + private readonly timeService: TimeService, ) { super(meta, paramDef, async (ps, me) => { const role = await this.rolesRepository.findOneBy({ id: ps.roleId }); @@ -80,7 +82,7 @@ export default class extends Endpoint { // eslint- throw new ApiError(meta.errors.noSuchUser); } - if (ps.expiresAt && ps.expiresAt <= Date.now()) { + if (ps.expiresAt && ps.expiresAt <= this.timeService.now) { return; } diff --git a/packages/backend/src/server/api/endpoints/admin/roles/users.ts b/packages/backend/src/server/api/endpoints/admin/roles/users.ts index 0a62845087..bb40097390 100644 --- a/packages/backend/src/server/api/endpoints/admin/roles/users.ts +++ b/packages/backend/src/server/api/endpoints/admin/roles/users.ts @@ -11,6 +11,7 @@ import { QueryService } from '@/core/QueryService.js'; import { DI } from '@/di-symbols.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; import { IdService } from '@/core/IdService.js'; +import { TimeService } from '@/core/TimeService.js'; import { ApiError } from '../../../error.js'; export const meta = { @@ -71,6 +72,7 @@ export default class extends Endpoint { // eslint- private queryService: QueryService, private userEntityService: UserEntityService, private idService: IdService, + private readonly timeService: TimeService, ) { super(meta, paramDef, async (ps, me) => { const role = await this.rolesRepository.findOneBy({ @@ -86,7 +88,7 @@ export default class extends Endpoint { // eslint- .andWhere(new Brackets(qb => { qb .where('assign.expiresAt IS NULL') - .orWhere('assign.expiresAt > :now', { now: new Date() }); + .orWhere('assign.expiresAt > :now', { now: this.timeService.date }); })) .innerJoinAndSelect('assign.user', 'user'); diff --git a/packages/backend/src/server/api/endpoints/admin/show-users.ts b/packages/backend/src/server/api/endpoints/admin/show-users.ts index 02117d4b6b..bca23bcdd6 100644 --- a/packages/backend/src/server/api/endpoints/admin/show-users.ts +++ b/packages/backend/src/server/api/endpoints/admin/show-users.ts @@ -10,6 +10,7 @@ import { DI } from '@/di-symbols.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; import { sqlLikeEscape } from '@/misc/sql-like-escape.js'; import { RoleService } from '@/core/RoleService.js'; +import { TimeService } from '@/core/TimeService.js'; export const meta = { tags: ['admin'], @@ -61,13 +62,14 @@ export default class extends Endpoint { // eslint- private userEntityService: UserEntityService, private roleService: RoleService, + private readonly timeService: TimeService, ) { super(meta, paramDef, async (ps, me) => { const query = this.usersRepository.createQueryBuilder('user'); switch (ps.state) { case 'available': query.where('user.isSuspended = FALSE'); break; - case 'alive': query.where('user.updatedAt > :date', { date: new Date(Date.now() - 1000 * 60 * 60 * 24 * 5) }); break; + case 'alive': query.where('user.updatedAt > :date', { date: new Date(this.timeService.now - 1000 * 60 * 60 * 24 * 5) }); break; case 'suspended': query.where('user.isSuspended = TRUE'); break; case 'approved': query.where('user.approved = FALSE'); break; case 'admin': { diff --git a/packages/backend/src/server/api/endpoints/antennas/create.ts b/packages/backend/src/server/api/endpoints/antennas/create.ts index 55d686e390..803e83ec82 100644 --- a/packages/backend/src/server/api/endpoints/antennas/create.ts +++ b/packages/backend/src/server/api/endpoints/antennas/create.ts @@ -11,6 +11,7 @@ import { GlobalEventService } from '@/core/GlobalEventService.js'; import { AntennaEntityService } from '@/core/entities/AntennaEntityService.js'; import { DI } from '@/di-symbols.js'; import { RoleService } from '@/core/RoleService.js'; +import { TimeService } from '@/core/TimeService.js'; import { ApiError } from '../../error.js'; export const meta = { @@ -97,6 +98,7 @@ export default class extends Endpoint { // eslint- private roleService: RoleService, private idService: IdService, private globalEventService: GlobalEventService, + private readonly timeService: TimeService, ) { super(meta, paramDef, async (ps, me) => { if (ps.keywords.flat().every(x => x === '') && ps.excludeKeywords.flat().every(x => x === '')) { @@ -123,7 +125,7 @@ export default class extends Endpoint { // eslint- } } - const now = new Date(); + const now = this.timeService.date; const antenna = await this.antennasRepository.insertOne({ id: this.idService.gen(now.getTime()), diff --git a/packages/backend/src/server/api/endpoints/antennas/notes.ts b/packages/backend/src/server/api/endpoints/antennas/notes.ts index 0aeceda038..ed20809e47 100644 --- a/packages/backend/src/server/api/endpoints/antennas/notes.ts +++ b/packages/backend/src/server/api/endpoints/antennas/notes.ts @@ -11,6 +11,7 @@ import { QueryService } from '@/core/QueryService.js'; import { DI } from '@/di-symbols.js'; import { NoteEntityService } from '@/core/entities/NoteEntityService.js'; import { IdService } from '@/core/IdService.js'; +import { TimeService } from '@/core/TimeService.js'; import { FanoutTimelineService } from '@/core/FanoutTimelineService.js'; import { GlobalEventService } from '@/core/GlobalEventService.js'; import { trackPromise } from '@/misc/promise-tracker.js'; @@ -77,6 +78,7 @@ export default class extends Endpoint { // eslint- private fanoutTimelineService: FanoutTimelineService, private globalEventService: GlobalEventService, private readonly activeUsersChart: ActiveUsersChart, + private readonly timeService: TimeService, ) { super(meta, paramDef, async (ps, me) => { const untilId = ps.untilId ?? (ps.untilDate ? this.idService.gen(ps.untilDate!) : null); @@ -95,7 +97,7 @@ export default class extends Endpoint { // eslint- const needPublishEvent = !antenna.isActive; antenna.isActive = true; - antenna.lastUsedAt = new Date(); + antenna.lastUsedAt = this.timeService.date; trackPromise(this.antennasRepository.update(antenna.id, antenna)); if (needPublishEvent) { diff --git a/packages/backend/src/server/api/endpoints/antennas/update.ts b/packages/backend/src/server/api/endpoints/antennas/update.ts index 3f2513bf75..44a6ed1286 100644 --- a/packages/backend/src/server/api/endpoints/antennas/update.ts +++ b/packages/backend/src/server/api/endpoints/antennas/update.ts @@ -8,6 +8,7 @@ import { Endpoint } from '@/server/api/endpoint-base.js'; import type { AntennasRepository, UserListsRepository } from '@/models/_.js'; import { GlobalEventService } from '@/core/GlobalEventService.js'; import { AntennaEntityService } from '@/core/entities/AntennaEntityService.js'; +import { TimeService } from '@/core/TimeService.js'; import { DI } from '@/di-symbols.js'; import { ApiError } from '../../error.js'; @@ -94,6 +95,7 @@ export default class extends Endpoint { // eslint- private antennaEntityService: AntennaEntityService, private globalEventService: GlobalEventService, + private readonly timeService: TimeService, ) { super(meta, paramDef, async (ps, me) => { if (ps.keywords && ps.excludeKeywords) { @@ -138,7 +140,7 @@ export default class extends Endpoint { // eslint- withFile: ps.withFile, excludeNotesInSensitiveChannel: ps.excludeNotesInSensitiveChannel, isActive: true, - lastUsedAt: new Date(), + lastUsedAt: this.timeService.date, }); this.globalEventService.publishInternalEvent('antennaUpdated', await this.antennasRepository.findOneByOrFail({ id: antenna.id })); diff --git a/packages/backend/src/server/api/endpoints/auth/accept.ts b/packages/backend/src/server/api/endpoints/auth/accept.ts index 0000ce16ef..3d69cc178c 100644 --- a/packages/backend/src/server/api/endpoints/auth/accept.ts +++ b/packages/backend/src/server/api/endpoints/auth/accept.ts @@ -8,6 +8,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; import type { AuthSessionsRepository, AppsRepository, AccessTokensRepository } from '@/models/_.js'; import { IdService } from '@/core/IdService.js'; +import { TimeService } from '@/core/TimeService.js'; import { secureRndstr } from '@/misc/secure-rndstr.js'; import { DI } from '@/di-symbols.js'; import { ApiError } from '../../error.js'; @@ -55,6 +56,7 @@ export default class extends Endpoint { // eslint- private accessTokensRepository: AccessTokensRepository, private idService: IdService, + private readonly timeService: TimeService, ) { super(meta, paramDef, async (ps, me) => { // Fetch token @@ -83,7 +85,7 @@ export default class extends Endpoint { // eslint- sha256.update(accessToken + app.secret); const hash = sha256.digest('hex'); - const now = new Date(); + const now = this.timeService.date; await this.accessTokensRepository.insert({ id: this.idService.gen(now.getTime()), diff --git a/packages/backend/src/server/api/endpoints/bubble-game/ranking.ts b/packages/backend/src/server/api/endpoints/bubble-game/ranking.ts index 1ac3488288..0afd586de7 100644 --- a/packages/backend/src/server/api/endpoints/bubble-game/ranking.ts +++ b/packages/backend/src/server/api/endpoints/bubble-game/ranking.ts @@ -9,6 +9,7 @@ import { Endpoint } from '@/server/api/endpoint-base.js'; import type { BubbleGameRecordsRepository } from '@/models/_.js'; import { DI } from '@/di-symbols.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; +import { TimeService } from '@/core/TimeService.js'; export const meta = { allowGet: true, @@ -63,12 +64,13 @@ export default class extends Endpoint { // eslint- private bubbleGameRecordsRepository: BubbleGameRecordsRepository, private userEntityService: UserEntityService, + private readonly timeService: TimeService, ) { super(meta, paramDef, async (ps, me) => { const records = await this.bubbleGameRecordsRepository.find({ where: { gameMode: ps.gameMode, - seededAt: MoreThan(new Date(Date.now() - 1000 * 60 * 60 * 24 * 7)), + seededAt: MoreThan(new Date(this.timeService.now - 1000 * 60 * 60 * 24 * 7)), }, order: { score: 'DESC', diff --git a/packages/backend/src/server/api/endpoints/bubble-game/register.ts b/packages/backend/src/server/api/endpoints/bubble-game/register.ts index 0a999e42cd..8d5ad64562 100644 --- a/packages/backend/src/server/api/endpoints/bubble-game/register.ts +++ b/packages/backend/src/server/api/endpoints/bubble-game/register.ts @@ -7,6 +7,7 @@ import { Inject, Injectable } from '@nestjs/common'; import ms from 'ms'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { IdService } from '@/core/IdService.js'; +import { TimeService } from '@/core/TimeService.js'; import type { BubbleGameRecordsRepository } from '@/models/_.js'; import { DI } from '@/di-symbols.js'; import { ApiError } from '../../error.js'; @@ -58,10 +59,11 @@ export default class extends Endpoint { // eslint- private bubbleGameRecordsRepository: BubbleGameRecordsRepository, private idService: IdService, + private readonly timeService: TimeService, ) { super(meta, paramDef, async (ps, me) => { const seedDate = new Date(parseInt(ps.seed, 10)); - const now = new Date(); + const now = this.timeService.date; // シードが未来なのは通常のプレイではありえないので弾く if (seedDate.getTime() > now.getTime()) { diff --git a/packages/backend/src/server/api/endpoints/flash/create.ts b/packages/backend/src/server/api/endpoints/flash/create.ts index 588de70b7b..25dc2a1880 100644 --- a/packages/backend/src/server/api/endpoints/flash/create.ts +++ b/packages/backend/src/server/api/endpoints/flash/create.ts @@ -10,6 +10,7 @@ import { IdService } from '@/core/IdService.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { DI } from '@/di-symbols.js'; import { FlashEntityService } from '@/core/entities/FlashEntityService.js'; +import { TimeService } from '@/core/TimeService.js'; export const meta = { tags: ['flash'], @@ -57,12 +58,13 @@ export default class extends Endpoint { // eslint- private flashEntityService: FlashEntityService, private idService: IdService, + private readonly timeService: TimeService, ) { super(meta, paramDef, async (ps, me) => { const flash = await this.flashsRepository.insertOne({ id: this.idService.gen(), userId: me.id, - updatedAt: new Date(), + updatedAt: this.timeService.date, title: ps.title, summary: ps.summary, script: ps.script, diff --git a/packages/backend/src/server/api/endpoints/flash/update.ts b/packages/backend/src/server/api/endpoints/flash/update.ts index e378669f0a..cbe5baa85f 100644 --- a/packages/backend/src/server/api/endpoints/flash/update.ts +++ b/packages/backend/src/server/api/endpoints/flash/update.ts @@ -7,6 +7,7 @@ import ms from 'ms'; import { Inject, Injectable } from '@nestjs/common'; import type { FlashsRepository } from '@/models/_.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; +import { TimeService } from '@/core/TimeService.js'; import { DI } from '@/di-symbols.js'; import { ApiError } from '../../error.js'; @@ -59,6 +60,8 @@ export default class extends Endpoint { // eslint- constructor( @Inject(DI.flashsRepository) private flashsRepository: FlashsRepository, + + private readonly timeService: TimeService, ) { super(meta, paramDef, async (ps, me) => { const flash = await this.flashsRepository.findOneBy({ id: ps.flashId }); @@ -70,7 +73,7 @@ export default class extends Endpoint { // eslint- } await this.flashsRepository.update(flash.id, { - updatedAt: new Date(), + updatedAt: this.timeService.date, ...Object.fromEntries( Object.entries(ps).filter( ([key, val]) => (key !== 'flashId') && Object.hasOwn(paramDef.properties, key) diff --git a/packages/backend/src/server/api/endpoints/gallery/featured.ts b/packages/backend/src/server/api/endpoints/gallery/featured.ts index abbfb9b83b..b31d7de4bd 100644 --- a/packages/backend/src/server/api/endpoints/gallery/featured.ts +++ b/packages/backend/src/server/api/endpoints/gallery/featured.ts @@ -9,6 +9,7 @@ import type { GalleryPostsRepository } from '@/models/_.js'; import { GalleryPostEntityService } from '@/core/entities/GalleryPostEntityService.js'; import { DI } from '@/di-symbols.js'; import { FeaturedService } from '@/core/FeaturedService.js'; +import { TimeService } from '@/core/TimeService.js'; export const meta = { tags: ['gallery'], @@ -52,15 +53,16 @@ export default class extends Endpoint { // eslint- private galleryPostEntityService: GalleryPostEntityService, private featuredService: FeaturedService, + private readonly timeService: TimeService, ) { super(meta, paramDef, async (ps, me) => { let postIds: string[]; - if (this.galleryPostsRankingCacheLastFetchedAt !== 0 && (Date.now() - this.galleryPostsRankingCacheLastFetchedAt < 1000 * 60 * 30)) { + if (this.galleryPostsRankingCacheLastFetchedAt !== 0 && (this.timeService.now - this.galleryPostsRankingCacheLastFetchedAt < 1000 * 60 * 30)) { postIds = this.galleryPostsRankingCache; } else { postIds = await this.featuredService.getGalleryPostsRanking(100); this.galleryPostsRankingCache = postIds; - this.galleryPostsRankingCacheLastFetchedAt = Date.now(); + this.galleryPostsRankingCacheLastFetchedAt = this.timeService.now; } postIds.sort((a, b) => a > b ? -1 : 1); diff --git a/packages/backend/src/server/api/endpoints/gallery/posts/create.ts b/packages/backend/src/server/api/endpoints/gallery/posts/create.ts index 08abd7fed5..0da88a2fdc 100644 --- a/packages/backend/src/server/api/endpoints/gallery/posts/create.ts +++ b/packages/backend/src/server/api/endpoints/gallery/posts/create.ts @@ -12,6 +12,7 @@ import type { MiDriveFile } from '@/models/DriveFile.js'; import { IdService } from '@/core/IdService.js'; import { GalleryPostEntityService } from '@/core/entities/GalleryPostEntityService.js'; import { DI } from '@/di-symbols.js'; +import { TimeService } from '@/core/TimeService.js'; export const meta = { tags: ['gallery'], @@ -62,6 +63,7 @@ export default class extends Endpoint { // eslint- private galleryPostEntityService: GalleryPostEntityService, private idService: IdService, + private readonly timeService: TimeService, ) { super(meta, paramDef, async (ps, me) => { const files = (await Promise.all(ps.fileIds.map(fileId => @@ -77,7 +79,7 @@ export default class extends Endpoint { // eslint- const post = await this.galleryPostsRepository.insertOne(new MiGalleryPost({ id: this.idService.gen(), - updatedAt: new Date(), + updatedAt: this.timeService.date, title: ps.title, description: ps.description, userId: me.id, diff --git a/packages/backend/src/server/api/endpoints/gallery/posts/like.ts b/packages/backend/src/server/api/endpoints/gallery/posts/like.ts index ae8ad6c044..f72aa73ba7 100644 --- a/packages/backend/src/server/api/endpoints/gallery/posts/like.ts +++ b/packages/backend/src/server/api/endpoints/gallery/posts/like.ts @@ -8,6 +8,7 @@ import { Endpoint } from '@/server/api/endpoint-base.js'; import type { GalleryLikesRepository, GalleryPostsRepository } from '@/models/_.js'; import { FeaturedService, GALLERY_POSTS_RANKING_WINDOW } from '@/core/FeaturedService.js'; import { IdService } from '@/core/IdService.js'; +import { TimeService } from '@/core/TimeService.js'; import { DI } from '@/di-symbols.js'; import { ApiError } from '../../../error.js'; @@ -66,6 +67,7 @@ export default class extends Endpoint { // eslint- private featuredService: FeaturedService, private idService: IdService, + private readonly timeService: TimeService, ) { super(meta, paramDef, async (ps, me) => { const post = await this.galleryPostsRepository.findOneBy({ id: ps.postId }); @@ -97,7 +99,7 @@ export default class extends Endpoint { // eslint- }); // ランキング更新 - if (Date.now() - this.idService.parse(post.id).date.getTime() < GALLERY_POSTS_RANKING_WINDOW) { + if (this.timeService.now - this.idService.parse(post.id).date.getTime() < GALLERY_POSTS_RANKING_WINDOW) { await this.featuredService.updateGalleryPostsRanking(post, 1); } diff --git a/packages/backend/src/server/api/endpoints/gallery/posts/unlike.ts b/packages/backend/src/server/api/endpoints/gallery/posts/unlike.ts index be0a5a5584..25d77228ec 100644 --- a/packages/backend/src/server/api/endpoints/gallery/posts/unlike.ts +++ b/packages/backend/src/server/api/endpoints/gallery/posts/unlike.ts @@ -8,6 +8,7 @@ import { Endpoint } from '@/server/api/endpoint-base.js'; import type { GalleryPostsRepository, GalleryLikesRepository } from '@/models/_.js'; import { FeaturedService, GALLERY_POSTS_RANKING_WINDOW } from '@/core/FeaturedService.js'; import { IdService } from '@/core/IdService.js'; +import { TimeService } from '@/core/TimeService.js'; import { DI } from '@/di-symbols.js'; import { ApiError } from '../../../error.js'; @@ -60,6 +61,7 @@ export default class extends Endpoint { // eslint- private featuredService: FeaturedService, private idService: IdService, + private readonly timeService: TimeService, ) { super(meta, paramDef, async (ps, me) => { const post = await this.galleryPostsRepository.findOneBy({ id: ps.postId }); @@ -80,7 +82,7 @@ export default class extends Endpoint { // eslint- await this.galleryLikesRepository.delete(exist.id); // ランキング更新 - if (Date.now() - this.idService.parse(post.id).date.getTime() < GALLERY_POSTS_RANKING_WINDOW) { + if (this.timeService.now - this.idService.parse(post.id).date.getTime() < GALLERY_POSTS_RANKING_WINDOW) { await this.featuredService.updateGalleryPostsRanking(post, -1); } diff --git a/packages/backend/src/server/api/endpoints/gallery/posts/update.ts b/packages/backend/src/server/api/endpoints/gallery/posts/update.ts index d0f9b56863..6d0264bb67 100644 --- a/packages/backend/src/server/api/endpoints/gallery/posts/update.ts +++ b/packages/backend/src/server/api/endpoints/gallery/posts/update.ts @@ -10,6 +10,7 @@ import type { DriveFilesRepository, GalleryPostsRepository } from '@/models/_.js import type { MiDriveFile } from '@/models/DriveFile.js'; import { GalleryPostEntityService } from '@/core/entities/GalleryPostEntityService.js'; import { DI } from '@/di-symbols.js'; +import { TimeService } from '@/core/TimeService.js'; export const meta = { tags: ['gallery'], @@ -60,6 +61,7 @@ export default class extends Endpoint { // eslint- private driveFilesRepository: DriveFilesRepository, private galleryPostEntityService: GalleryPostEntityService, + private readonly timeService: TimeService, ) { super(meta, paramDef, async (ps, me) => { let files: Array | undefined; @@ -81,7 +83,7 @@ export default class extends Endpoint { // eslint- id: ps.postId, userId: me.id, }, { - updatedAt: new Date(), + updatedAt: this.timeService.date, title: ps.title, description: ps.description, isSensitive: ps.isSensitive, diff --git a/packages/backend/src/server/api/endpoints/get-online-users-count.ts b/packages/backend/src/server/api/endpoints/get-online-users-count.ts index ede777c890..939ec33dfa 100644 --- a/packages/backend/src/server/api/endpoints/get-online-users-count.ts +++ b/packages/backend/src/server/api/endpoints/get-online-users-count.ts @@ -9,6 +9,7 @@ import { USER_ONLINE_THRESHOLD } from '@/const.js'; import type { UsersRepository } from '@/models/_.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { DI } from '@/di-symbols.js'; +import { TimeService } from '@/core/TimeService.js'; export const meta = { tags: ['meta'], @@ -45,10 +46,11 @@ export default class extends Endpoint { // eslint- constructor( @Inject(DI.usersRepository) private usersRepository: UsersRepository, + private readonly timeService: TimeService, ) { super(meta, paramDef, async () => { const count = await this.usersRepository.countBy({ - lastActiveDate: MoreThan(new Date(Date.now() - USER_ONLINE_THRESHOLD)), + lastActiveDate: MoreThan(new Date(this.timeService.now - USER_ONLINE_THRESHOLD)), }); return { diff --git a/packages/backend/src/server/api/endpoints/hashtags/users.ts b/packages/backend/src/server/api/endpoints/hashtags/users.ts index c43e750cf3..7f018132dc 100644 --- a/packages/backend/src/server/api/endpoints/hashtags/users.ts +++ b/packages/backend/src/server/api/endpoints/hashtags/users.ts @@ -11,6 +11,7 @@ import { normalizeForSearch } from '@/misc/normalize-for-search.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; import { DI } from '@/di-symbols.js'; import { RoleService } from '@/core/RoleService.js'; +import { TimeService } from '@/core/TimeService.js'; export const meta = { requireCredential: false, @@ -60,6 +61,7 @@ export default class extends Endpoint { // eslint- private userEntityService: UserEntityService, private readonly roleService: RoleService, + private readonly timeService: TimeService, ) { super(meta, paramDef, async (ps, me) => { if (!safeForSql(normalizeForSearch(ps.tag))) throw new Error('Injection'); @@ -67,7 +69,7 @@ export default class extends Endpoint { // eslint- .where(':tag <@ user.tags', { tag: [normalizeForSearch(ps.tag)] }) .andWhere('user.isSuspended = FALSE'); - const recent = new Date(Date.now() - (1000 * 60 * 60 * 24 * 5)); + const recent = new Date(this.timeService.now - (1000 * 60 * 60 * 24 * 5)); if (ps.state === 'alive') { query.andWhere('user.updatedAt > :date', { date: recent }); diff --git a/packages/backend/src/server/api/endpoints/i.ts b/packages/backend/src/server/api/endpoints/i.ts index 23e90db356..6aaf13d791 100644 --- a/packages/backend/src/server/api/endpoints/i.ts +++ b/packages/backend/src/server/api/endpoints/i.ts @@ -7,6 +7,7 @@ import { Inject, Injectable } from '@nestjs/common'; import type { UserProfilesRepository } from '@/models/_.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; +import { TimeService } from '@/core/TimeService.js'; import { DI } from '@/di-symbols.js'; import { ApiError } from '../error.js'; @@ -54,11 +55,12 @@ export default class extends Endpoint { // eslint- private userProfilesRepository: UserProfilesRepository, private userEntityService: UserEntityService, + private readonly timeService: TimeService, ) { super(meta, paramDef, async (ps, user, token) => { const isSecure = token == null; - const now = new Date(); + const now = this.timeService.date; const today = `${now.getFullYear()}/${now.getMonth() + 1}/${now.getDate()}`; // 渡ってきている user はキャッシュされていて古い可能性があるので改めて取得 diff --git a/packages/backend/src/server/api/endpoints/i/import-blocking.ts b/packages/backend/src/server/api/endpoints/i/import-blocking.ts index 78678f33e5..3d4ab91864 100644 --- a/packages/backend/src/server/api/endpoints/i/import-blocking.ts +++ b/packages/backend/src/server/api/endpoints/i/import-blocking.ts @@ -7,6 +7,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { QueueService } from '@/core/QueueService.js'; import { AccountMoveService } from '@/core/AccountMoveService.js'; +import { TimeService } from '@/core/TimeService.js'; import type { DriveFilesRepository } from '@/models/_.js'; import { DI } from '@/di-symbols.js'; import { ApiError } from '../../error.js'; @@ -67,6 +68,7 @@ export default class extends Endpoint { // eslint- private queueService: QueueService, private accountMoveService: AccountMoveService, + private readonly timeService: TimeService, ) { super(meta, paramDef, async (ps, me) => { const file = await this.driveFilesRepository.findOneBy({ id: ps.fileId }); @@ -77,7 +79,7 @@ export default class extends Endpoint { // eslint- const checkMoving = await this.accountMoveService.validateAlsoKnownAs( me, - (old, src) => !!src.movedAt && src.movedAt.getTime() + 1000 * 60 * 60 * 2 > Date.now(), + (old, src) => !!src.movedAt && src.movedAt.getTime() + 1000 * 60 * 60 * 2 > this.timeService.now, true, ); if (checkMoving ? file.size > 32 * 1024 * 1024 : file.size > 64 * 1024) throw new ApiError(meta.errors.tooBigFile); diff --git a/packages/backend/src/server/api/endpoints/i/import-following.ts b/packages/backend/src/server/api/endpoints/i/import-following.ts index 2d45ff2c8a..cfd5138dbc 100644 --- a/packages/backend/src/server/api/endpoints/i/import-following.ts +++ b/packages/backend/src/server/api/endpoints/i/import-following.ts @@ -8,6 +8,7 @@ import ms from 'ms'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { QueueService } from '@/core/QueueService.js'; import { AccountMoveService } from '@/core/AccountMoveService.js'; +import { TimeService } from '@/core/TimeService.js'; import type { DriveFilesRepository } from '@/models/_.js'; import { DI } from '@/di-symbols.js'; import { ApiError } from '../../error.js'; @@ -69,6 +70,7 @@ export default class extends Endpoint { // eslint- private queueService: QueueService, private accountMoveService: AccountMoveService, + private readonly timeService: TimeService, ) { super(meta, paramDef, async (ps, me) => { const file = await this.driveFilesRepository.findOneBy({ id: ps.fileId }); @@ -79,7 +81,7 @@ export default class extends Endpoint { // eslint- const checkMoving = await this.accountMoveService.validateAlsoKnownAs( me, - (old, src) => !!src.movedAt && src.movedAt.getTime() + 1000 * 60 * 60 * 2 > Date.now(), + (old, src) => !!src.movedAt && src.movedAt.getTime() + 1000 * 60 * 60 * 2 > this.timeService.now, true, ); if (checkMoving ? file.size > 32 * 1024 * 1024 : file.size > 64 * 1024) throw new ApiError(meta.errors.tooBigFile); diff --git a/packages/backend/src/server/api/endpoints/i/import-muting.ts b/packages/backend/src/server/api/endpoints/i/import-muting.ts index 1af9cc6263..f4f95ccfd2 100644 --- a/packages/backend/src/server/api/endpoints/i/import-muting.ts +++ b/packages/backend/src/server/api/endpoints/i/import-muting.ts @@ -7,6 +7,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { QueueService } from '@/core/QueueService.js'; import { AccountMoveService } from '@/core/AccountMoveService.js'; +import { TimeService } from '@/core/TimeService.js'; import type { DriveFilesRepository } from '@/models/_.js'; import { DI } from '@/di-symbols.js'; import { ApiError } from '../../error.js'; @@ -67,6 +68,7 @@ export default class extends Endpoint { // eslint- private queueService: QueueService, private accountMoveService: AccountMoveService, + private readonly timeService: TimeService, ) { super(meta, paramDef, async (ps, me) => { const file = await this.driveFilesRepository.findOneBy({ id: ps.fileId }); @@ -77,7 +79,7 @@ export default class extends Endpoint { // eslint- const checkMoving = await this.accountMoveService.validateAlsoKnownAs( me, - (old, src) => !!src.movedAt && src.movedAt.getTime() + 1000 * 60 * 60 * 2 > Date.now(), + (old, src) => !!src.movedAt && src.movedAt.getTime() + 1000 * 60 * 60 * 2 > this.timeService.now, true, ); if (checkMoving ? file.size > 32 * 1024 * 1024 : file.size > 64 * 1024) throw new ApiError(meta.errors.tooBigFile); diff --git a/packages/backend/src/server/api/endpoints/i/import-user-lists.ts b/packages/backend/src/server/api/endpoints/i/import-user-lists.ts index 509483be3f..53d5f350ac 100644 --- a/packages/backend/src/server/api/endpoints/i/import-user-lists.ts +++ b/packages/backend/src/server/api/endpoints/i/import-user-lists.ts @@ -7,6 +7,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { QueueService } from '@/core/QueueService.js'; import { AccountMoveService } from '@/core/AccountMoveService.js'; +import { TimeService } from '@/core/TimeService.js'; import type { DriveFilesRepository } from '@/models/_.js'; import { DI } from '@/di-symbols.js'; import { ApiError } from '../../error.js'; @@ -67,6 +68,7 @@ export default class extends Endpoint { // eslint- private queueService: QueueService, private accountMoveService: AccountMoveService, + private readonly timeService: TimeService, ) { super(meta, paramDef, async (ps, me) => { const file = await this.driveFilesRepository.findOneBy({ id: ps.fileId }); @@ -77,7 +79,7 @@ export default class extends Endpoint { // eslint- const checkMoving = await this.accountMoveService.validateAlsoKnownAs( me, - (old, src) => !!src.movedAt && src.movedAt.getTime() + 1000 * 60 * 60 * 2 > Date.now(), + (old, src) => !!src.movedAt && src.movedAt.getTime() + 1000 * 60 * 60 * 2 > this.timeService.now, true, ); if (checkMoving ? file.size > 32 * 1024 * 1024 : file.size > 64 * 1024) throw new ApiError(meta.errors.tooBigFile); diff --git a/packages/backend/src/server/api/endpoints/invite/create.ts b/packages/backend/src/server/api/endpoints/invite/create.ts index f607a35515..21bfaf9a5e 100644 --- a/packages/backend/src/server/api/endpoints/invite/create.ts +++ b/packages/backend/src/server/api/endpoints/invite/create.ts @@ -10,6 +10,7 @@ import type { RegistrationTicketsRepository } from '@/models/_.js'; import { InviteCodeEntityService } from '@/core/entities/InviteCodeEntityService.js'; import { IdService } from '@/core/IdService.js'; import { RoleService } from '@/core/RoleService.js'; +import { TimeService } from '@/core/TimeService.js'; import { DI } from '@/di-symbols.js'; import { generateInviteCode } from '@/misc/generate-invite-code.js'; import { ApiError } from '../../error.js'; @@ -57,13 +58,14 @@ export default class extends Endpoint { // eslint- private inviteCodeEntityService: InviteCodeEntityService, private idService: IdService, private roleService: RoleService, + private readonly timeService: TimeService, ) { super(meta, paramDef, async (ps, me) => { const policies = await this.roleService.getUserPolicies(me.id); if (policies.inviteLimit) { const count = await this.registrationTicketsRepository.countBy({ - id: MoreThan(this.idService.gen(Date.now() - (policies.inviteLimitCycle * 1000 * 60))), + id: MoreThan(this.idService.gen(this.timeService.now - (policies.inviteLimitCycle * 1000 * 60))), createdById: me.id, }); @@ -76,7 +78,7 @@ export default class extends Endpoint { // eslint- id: this.idService.gen(), createdBy: me, createdById: me.id, - expiresAt: policies.inviteExpirationTime ? new Date(Date.now() + (policies.inviteExpirationTime * 1000 * 60)) : null, + expiresAt: policies.inviteExpirationTime ? new Date(this.timeService.now + (policies.inviteExpirationTime * 1000 * 60)) : null, code: generateInviteCode(), }); diff --git a/packages/backend/src/server/api/endpoints/invite/limit.ts b/packages/backend/src/server/api/endpoints/invite/limit.ts index 150f4de441..b7bb4f1d22 100644 --- a/packages/backend/src/server/api/endpoints/invite/limit.ts +++ b/packages/backend/src/server/api/endpoints/invite/limit.ts @@ -10,6 +10,7 @@ import type { RegistrationTicketsRepository } from '@/models/_.js'; import { RoleService } from '@/core/RoleService.js'; import { DI } from '@/di-symbols.js'; import { IdService } from '@/core/IdService.js'; +import { TimeService } from '@/core/TimeService.js'; export const meta = { tags: ['meta'], @@ -50,12 +51,13 @@ export default class extends Endpoint { // eslint- private roleService: RoleService, private idService: IdService, + private readonly timeService: TimeService, ) { super(meta, paramDef, async (ps, me) => { const policies = await this.roleService.getUserPolicies(me.id); const count = policies.inviteLimit ? await this.registrationTicketsRepository.countBy({ - id: MoreThan(this.idService.gen(Date.now() - (policies.inviteLimitCycle * 60 * 1000))), + id: MoreThan(this.idService.gen(this.timeService.now - (policies.inviteLimitCycle * 60 * 1000))), createdById: me.id, }) : null; diff --git a/packages/backend/src/server/api/endpoints/miauth/gen-token.ts b/packages/backend/src/server/api/endpoints/miauth/gen-token.ts index 8644d40538..088197f487 100644 --- a/packages/backend/src/server/api/endpoints/miauth/gen-token.ts +++ b/packages/backend/src/server/api/endpoints/miauth/gen-token.ts @@ -11,6 +11,7 @@ import { IdService } from '@/core/IdService.js'; import { NotificationService } from '@/core/NotificationService.js'; import { secureRndstr } from '@/misc/secure-rndstr.js'; import { DI } from '@/di-symbols.js'; +import { TimeService } from '@/core/TimeService.js'; import { CacheService } from '@/core/CacheService.js'; export const meta = { @@ -77,6 +78,7 @@ export default class extends Endpoint { // eslint- private idService: IdService, private notificationService: NotificationService, + private readonly timeService: TimeService, private readonly cacheService: CacheService, ) { super(meta, paramDef, async (ps, me) => { @@ -98,7 +100,7 @@ export default class extends Endpoint { // eslint- // Generate access token const accessToken = secureRndstr(32); - const now = new Date(); + const now = this.timeService.date; // Insert access token doc await this.accessTokensRepository.insert({ diff --git a/packages/backend/src/server/api/endpoints/mute/create.ts b/packages/backend/src/server/api/endpoints/mute/create.ts index e39c133b43..00decb90ea 100644 --- a/packages/backend/src/server/api/endpoints/mute/create.ts +++ b/packages/backend/src/server/api/endpoints/mute/create.ts @@ -10,6 +10,7 @@ import type { MutingsRepository } from '@/models/_.js'; import { DI } from '@/di-symbols.js'; import { GetterService } from '@/server/api/GetterService.js'; import { UserMutingService } from '@/core/UserMutingService.js'; +import { TimeService } from '@/core/TimeService.js'; import { ApiError } from '../../error.js'; export const meta = { @@ -67,6 +68,7 @@ export default class extends Endpoint { // eslint- private getterService: GetterService, private userMutingService: UserMutingService, + private readonly timeService: TimeService, ) { super(meta, paramDef, async (ps, me) => { const muter = me; @@ -94,7 +96,7 @@ export default class extends Endpoint { // eslint- throw new ApiError(meta.errors.alreadyMuting); } - if (ps.expiresAt && ps.expiresAt <= Date.now()) { + if (ps.expiresAt && ps.expiresAt <= this.timeService.now) { return; } diff --git a/packages/backend/src/server/api/endpoints/notes/create.ts b/packages/backend/src/server/api/endpoints/notes/create.ts index 4aece5353e..7077832305 100644 --- a/packages/backend/src/server/api/endpoints/notes/create.ts +++ b/packages/backend/src/server/api/endpoints/notes/create.ts @@ -18,6 +18,7 @@ import { NoteCreateService } from '@/core/NoteCreateService.js'; import { DI } from '@/di-symbols.js'; import { isQuote, isRenote } from '@/misc/is-renote.js'; import { IdentifiableError } from '@/misc/identifiable-error.js'; +import { TimeService } from '@/core/TimeService.js'; import { ApiError } from '../../error.js'; export const meta = { @@ -261,6 +262,7 @@ export default class extends Endpoint { // eslint- private noteEntityService: NoteEntityService, private noteCreateService: NoteCreateService, + private readonly timeService: TimeService, ) { super(meta, paramDef, async (ps, me) => { if (ps.text && ps.text.length > this.config.maxNoteLength) { @@ -331,11 +333,11 @@ export default class extends Endpoint { // eslint- if (ps.poll) { if (typeof ps.poll.expiresAt === 'number') { - if (ps.poll.expiresAt < Date.now()) { + if (ps.poll.expiresAt < this.timeService.now) { throw new ApiError(meta.errors.cannotCreateAlreadyExpiredPoll); } } else if (typeof ps.poll.expiredAfter === 'number') { - ps.poll.expiresAt = Date.now() + ps.poll.expiredAfter; + ps.poll.expiresAt = this.timeService.now + ps.poll.expiredAfter; } } @@ -351,7 +353,7 @@ export default class extends Endpoint { // eslint- // 投稿を作成 try { const note = await this.noteCreateService.create(me, { - createdAt: new Date(), + createdAt: this.timeService.date, files: files, poll: ps.poll ? { choices: ps.poll.choices, diff --git a/packages/backend/src/server/api/endpoints/notes/edit.ts b/packages/backend/src/server/api/endpoints/notes/edit.ts index 276f2672d0..17bea6df3d 100644 --- a/packages/backend/src/server/api/endpoints/notes/edit.ts +++ b/packages/backend/src/server/api/endpoints/notes/edit.ts @@ -17,6 +17,7 @@ import { NoteEditService } from '@/core/NoteEditService.js'; import { DI } from '@/di-symbols.js'; import { isQuote, isRenote } from '@/misc/is-renote.js'; import { IdentifiableError } from '@/misc/identifiable-error.js'; +import { TimeService } from '@/core/TimeService.js'; import { ApiError } from '../../error.js'; export const meta = { @@ -311,6 +312,7 @@ export default class extends Endpoint { // eslint- private noteEntityService: NoteEntityService, private noteEditService: NoteEditService, + private readonly timeService: TimeService, ) { super(meta, paramDef, async (ps, me) => { if (ps.text && ps.text.length > this.config.maxNoteLength) { @@ -381,11 +383,11 @@ export default class extends Endpoint { // eslint- if (ps.poll) { if (typeof ps.poll.expiresAt === 'number') { - if (ps.poll.expiresAt < Date.now()) { + if (ps.poll.expiresAt < this.timeService.now) { throw new ApiError(meta.errors.cannotCreateAlreadyExpiredPoll); } } else if (typeof ps.poll.expiredAfter === 'number') { - ps.poll.expiresAt = Date.now() + ps.poll.expiredAfter; + ps.poll.expiresAt = this.timeService.now + ps.poll.expiredAfter; } } diff --git a/packages/backend/src/server/api/endpoints/notes/featured.ts b/packages/backend/src/server/api/endpoints/notes/featured.ts index c9a4f15fcc..7029db567d 100644 --- a/packages/backend/src/server/api/endpoints/notes/featured.ts +++ b/packages/backend/src/server/api/endpoints/notes/featured.ts @@ -13,6 +13,7 @@ import { isUserRelated } from '@/misc/is-user-related.js'; import { QueryService } from '@/core/QueryService.js'; import { ApiError } from '@/server/api/error.js'; import { RoleService } from '@/core/RoleService.js'; +import { TimeService } from '@/core/TimeService.js'; export const meta = { tags: ['notes'], @@ -70,6 +71,7 @@ export default class extends Endpoint { // eslint- private featuredService: FeaturedService, private queryService: QueryService, private readonly roleService: RoleService, + private readonly timeService: TimeService, ) { super(meta, paramDef, async (ps, me) => { const policies = await this.roleService.getUserPolicies(me ? me.id : null); @@ -81,12 +83,12 @@ export default class extends Endpoint { // eslint- if (ps.channelId) { noteIds = await this.featuredService.getInChannelNotesRanking(ps.channelId, 50); } else { - if (this.globalNotesRankingCacheLastFetchedAt !== 0 && (Date.now() - this.globalNotesRankingCacheLastFetchedAt < 1000 * 60 * 30)) { + if (this.globalNotesRankingCacheLastFetchedAt !== 0 && (this.timeService.now - this.globalNotesRankingCacheLastFetchedAt < 1000 * 60 * 30)) { noteIds = this.globalNotesRankingCache; } else { noteIds = await this.featuredService.getGlobalNotesRanking(100); this.globalNotesRankingCache = noteIds; - this.globalNotesRankingCacheLastFetchedAt = Date.now(); + this.globalNotesRankingCacheLastFetchedAt = this.timeService.now; } } diff --git a/packages/backend/src/server/api/endpoints/notes/polls/recommendation.ts b/packages/backend/src/server/api/endpoints/notes/polls/recommendation.ts index 6e5fdaa281..5a309d1e29 100644 --- a/packages/backend/src/server/api/endpoints/notes/polls/recommendation.ts +++ b/packages/backend/src/server/api/endpoints/notes/polls/recommendation.ts @@ -12,6 +12,7 @@ import { DI } from '@/di-symbols.js'; import { QueryService } from '@/core/QueryService.js'; import { RoleService } from '@/core/RoleService.js'; import { ApiError } from '@/server/api/error.js'; +import { TimeService } from '@/core/TimeService.js'; export const meta = { tags: ['notes'], @@ -77,6 +78,7 @@ export default class extends Endpoint { // eslint- private noteEntityService: NoteEntityService, private readonly queryService: QueryService, private readonly roleService: RoleService, + private readonly timeService: TimeService, ) { super(meta, paramDef, async (ps, me) => { const query = this.pollsRepository.createQueryBuilder('poll') @@ -96,16 +98,16 @@ export default class extends Endpoint { // eslint- if (ps.expired) { query.andWhere('poll.expiresAt IS NOT NULL'); query.andWhere('poll.expiresAt <= :expiresMax', { - expiresMax: new Date(), + expiresMax: this.timeService.date, }); query.andWhere('poll.expiresAt >= :expiresMin', { - expiresMin: new Date(Date.now() - (1000 * 60 * 60 * 24 * 7)), + expiresMin: new Date(this.timeService.now - (1000 * 60 * 60 * 24 * 7)), }); } else { query.andWhere(new Brackets(qb => { qb .where('poll.expiresAt IS NULL') - .orWhere('poll.expiresAt > :now', { now: new Date() }); + .orWhere('poll.expiresAt > :now', { now: this.timeService.date }); })); } diff --git a/packages/backend/src/server/api/endpoints/notes/polls/vote.ts b/packages/backend/src/server/api/endpoints/notes/polls/vote.ts index 0b318304f3..afa37e3204 100644 --- a/packages/backend/src/server/api/endpoints/notes/polls/vote.ts +++ b/packages/backend/src/server/api/endpoints/notes/polls/vote.ts @@ -15,6 +15,7 @@ import { ApRendererService } from '@/core/activitypub/ApRendererService.js'; import { GlobalEventService } from '@/core/GlobalEventService.js'; import { DI } from '@/di-symbols.js'; import { UserBlockingService } from '@/core/UserBlockingService.js'; +import { TimeService } from '@/core/TimeService.js'; import { ApiError } from '../../../error.js'; export const meta = { @@ -101,9 +102,10 @@ export default class extends Endpoint { // eslint- private apRendererService: ApRendererService, private globalEventService: GlobalEventService, private userBlockingService: UserBlockingService, + private readonly timeService: TimeService, ) { super(meta, paramDef, async (ps, me) => { - const createdAt = new Date(); + const createdAt = this.timeService.date; // Get votee const note = await this.getterService.getNote(ps.noteId).catch(err => { diff --git a/packages/backend/src/server/api/endpoints/notes/schedule/create.ts b/packages/backend/src/server/api/endpoints/notes/schedule/create.ts index 0c2e00ee8d..9dc1158822 100644 --- a/packages/backend/src/server/api/endpoints/notes/schedule/create.ts +++ b/packages/backend/src/server/api/endpoints/notes/schedule/create.ts @@ -24,6 +24,7 @@ import { QueueService } from '@/core/QueueService.js'; import { IdService } from '@/core/IdService.js'; import { MiScheduleNoteType } from '@/models/NoteSchedule.js'; import { RoleService } from '@/core/RoleService.js'; +import { TimeService } from '@/core/TimeService.js'; import { ApiError } from '../../../error.js'; export const meta = { @@ -211,6 +212,7 @@ export default class extends Endpoint { // eslint- private queueService: QueueService, private roleService: RoleService, private idService: IdService, + private readonly timeService: TimeService, ) { super(meta, paramDef, async (ps, me) => { const scheduleNoteCount = await this.noteScheduleRepository.countBy({ userId: me.id }); @@ -301,7 +303,7 @@ export default class extends Endpoint { // eslint- } if (ps.poll) { - let scheduleNote_scheduledAt = Date.now(); + let scheduleNote_scheduledAt = this.timeService.now; if (typeof ps.scheduleNote.scheduledAt === 'number') { scheduleNote_scheduledAt = moment.utc(ps.scheduleNote.scheduledAt).local().valueOf(); } @@ -314,7 +316,7 @@ export default class extends Endpoint { // eslint- } } if (typeof ps.scheduleNote.scheduledAt === 'number') { - if (moment.utc(ps.scheduleNote.scheduledAt).local().valueOf() < Date.now()) { + if (moment.utc(ps.scheduleNote.scheduledAt).local().valueOf() < this.timeService.now) { throw new ApiError(meta.errors.cannotCreateAlreadyExpiredSchedule); } } else { @@ -342,7 +344,7 @@ export default class extends Endpoint { // eslint- if (ps.scheduleNote.scheduledAt) { me.token = null; - const noteId = this.idService.gen(new Date().getTime()); + const noteId = this.idService.gen(this.timeService.now); const schedNoteLocalTime = moment.utc(ps.scheduleNote.scheduledAt).local().valueOf(); await this.noteScheduleRepository.insert({ id: noteId, @@ -351,7 +353,7 @@ export default class extends Endpoint { // eslint- scheduledAt: new Date(schedNoteLocalTime), }); - const delay = new Date(schedNoteLocalTime).getTime() - Date.now(); + const delay = new Date(schedNoteLocalTime).getTime() - this.timeService.now; await this.queueService.ScheduleNotePostQueue.add(String(delay), { scheduleNoteId: noteId, }, { diff --git a/packages/backend/src/server/api/endpoints/pages/create.ts b/packages/backend/src/server/api/endpoints/pages/create.ts index 1f3ad9281e..2be2a22cfe 100644 --- a/packages/backend/src/server/api/endpoints/pages/create.ts +++ b/packages/backend/src/server/api/endpoints/pages/create.ts @@ -10,6 +10,7 @@ import { IdService } from '@/core/IdService.js'; import { MiPage, pageNameSchema } from '@/models/Page.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { PageEntityService } from '@/core/entities/PageEntityService.js'; +import { TimeService } from '@/core/TimeService.js'; import { DI } from '@/di-symbols.js'; import { ApiError } from '../../error.js'; @@ -79,6 +80,7 @@ export default class extends Endpoint { // eslint- private pageEntityService: PageEntityService, private idService: IdService, + private readonly timeService: TimeService, ) { super(meta, paramDef, async (ps, me) => { let eyeCatchingImage = null; @@ -104,7 +106,7 @@ export default class extends Endpoint { // eslint- const page = await this.pagesRepository.insertOne(new MiPage({ id: this.idService.gen(), - updatedAt: new Date(), + updatedAt: this.timeService.date, title: ps.title, name: ps.name, summary: ps.summary, diff --git a/packages/backend/src/server/api/endpoints/pages/update.ts b/packages/backend/src/server/api/endpoints/pages/update.ts index a6aeb6002e..0ca86867b6 100644 --- a/packages/backend/src/server/api/endpoints/pages/update.ts +++ b/packages/backend/src/server/api/endpoints/pages/update.ts @@ -8,6 +8,7 @@ import { Not } from 'typeorm'; import { Inject, Injectable } from '@nestjs/common'; import type { PagesRepository, DriveFilesRepository } from '@/models/_.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; +import { TimeService } from '@/core/TimeService.js'; import { DI } from '@/di-symbols.js'; import { ApiError } from '../../error.js'; import { pageNameSchema } from '@/models/Page.js'; @@ -80,6 +81,8 @@ export default class extends Endpoint { // eslint- @Inject(DI.driveFilesRepository) private driveFilesRepository: DriveFilesRepository, + + private readonly timeService: TimeService, ) { super(meta, paramDef, async (ps, me) => { const page = await this.pagesRepository.findOneBy({ id: ps.pageId }); @@ -114,7 +117,7 @@ export default class extends Endpoint { // eslint- } await this.pagesRepository.update(page.id, { - updatedAt: new Date(), + updatedAt: this.timeService.date, title: ps.title, name: ps.name, summary: ps.summary === undefined ? page.summary : ps.summary, diff --git a/packages/backend/src/server/api/endpoints/ping.ts b/packages/backend/src/server/api/endpoints/ping.ts index ed6f5207a0..f6d2177962 100644 --- a/packages/backend/src/server/api/endpoints/ping.ts +++ b/packages/backend/src/server/api/endpoints/ping.ts @@ -5,6 +5,7 @@ import { Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; +import { TimeService } from '@/core/TimeService.js'; export const meta = { requireCredential: false, @@ -38,10 +39,11 @@ export const paramDef = { @Injectable() export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( + private readonly timeService: TimeService, ) { super(meta, paramDef, async () => { return { - pong: Date.now(), + pong: this.timeService.now, }; }); } diff --git a/packages/backend/src/server/api/endpoints/reset-password.ts b/packages/backend/src/server/api/endpoints/reset-password.ts index ba0c60f4ec..7a7e1fef86 100644 --- a/packages/backend/src/server/api/endpoints/reset-password.ts +++ b/packages/backend/src/server/api/endpoints/reset-password.ts @@ -9,6 +9,7 @@ import type { UserProfilesRepository, PasswordResetRequestsRepository } from '@/ import { Endpoint } from '@/server/api/endpoint-base.js'; import { DI } from '@/di-symbols.js'; import { IdService } from '@/core/IdService.js'; +import { TimeService } from '@/core/TimeService.js'; export const meta = { tags: ['reset password'], @@ -47,6 +48,7 @@ export default class extends Endpoint { // eslint- private userProfilesRepository: UserProfilesRepository, private idService: IdService, + private readonly timeService: TimeService, ) { super(meta, paramDef, async (ps, me) => { const req = await this.passwordResetRequestsRepository.findOneByOrFail({ @@ -54,7 +56,7 @@ export default class extends Endpoint { // eslint- }); // 発行してから30分以上経過していたら無効 - if (Date.now() - this.idService.parse(req.id).date.getTime() > 1000 * 60 * 30) { + if (this.timeService.now - this.idService.parse(req.id).date.getTime() > 1000 * 60 * 30) { throw new Error(); // TODO } diff --git a/packages/backend/src/server/api/endpoints/roles/users.ts b/packages/backend/src/server/api/endpoints/roles/users.ts index a9adac8b1c..8d19ea08d2 100644 --- a/packages/backend/src/server/api/endpoints/roles/users.ts +++ b/packages/backend/src/server/api/endpoints/roles/users.ts @@ -8,6 +8,7 @@ import { Brackets } from 'typeorm'; import type { RoleAssignmentsRepository, RolesRepository } from '@/models/_.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { QueryService } from '@/core/QueryService.js'; +import { TimeService } from '@/core/TimeService.js'; import { DI } from '@/di-symbols.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; import { ApiError } from '../../error.js'; @@ -78,6 +79,7 @@ export default class extends Endpoint { // eslint- private queryService: QueryService, private userEntityService: UserEntityService, + private readonly timeService: TimeService, ) { super(meta, paramDef, async (ps, me) => { const role = await this.rolesRepository.findOneBy({ @@ -95,7 +97,7 @@ export default class extends Endpoint { // eslint- .andWhere(new Brackets(qb => { qb .where('assign.expiresAt IS NULL') - .orWhere('assign.expiresAt > :now', { now: new Date() }); + .orWhere('assign.expiresAt > :now', { now: this.timeService.date }); })) .innerJoinAndSelect('assign.user', 'user'); diff --git a/packages/backend/src/server/api/endpoints/users.ts b/packages/backend/src/server/api/endpoints/users.ts index 48942db002..f62bb275cd 100644 --- a/packages/backend/src/server/api/endpoints/users.ts +++ b/packages/backend/src/server/api/endpoints/users.ts @@ -11,6 +11,7 @@ import { QueryService } from '@/core/QueryService.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; import { DI } from '@/di-symbols.js'; import { RoleService } from '@/core/RoleService.js'; +import { TimeService } from '@/core/TimeService.js'; import type { SelectQueryBuilder } from 'typeorm'; export const meta = { @@ -68,6 +69,7 @@ export default class extends Endpoint { // eslint- private userEntityService: UserEntityService, private queryService: QueryService, private readonly roleService: RoleService, + private readonly timeService: TimeService, ) { super(meta, paramDef, async (ps, me) => { const query = this.usersRepository.createQueryBuilder('user') @@ -75,7 +77,7 @@ export default class extends Endpoint { // eslint- .andWhere('user.isSuspended = FALSE'); switch (ps.state) { - case 'alive': query.andWhere('user.updatedAt > :date', { date: new Date(Date.now() - 1000 * 60 * 60 * 24 * 5) }); break; + case 'alive': query.andWhere('user.updatedAt > :date', { date: new Date(this.timeService.now - 1000 * 60 * 60 * 24 * 5) }); break; } switch (ps.origin) { diff --git a/packages/backend/src/server/api/endpoints/users/recommendation.ts b/packages/backend/src/server/api/endpoints/users/recommendation.ts index 2585efdc11..bbf9279b96 100644 --- a/packages/backend/src/server/api/endpoints/users/recommendation.ts +++ b/packages/backend/src/server/api/endpoints/users/recommendation.ts @@ -10,6 +10,7 @@ import { Endpoint } from '@/server/api/endpoint-base.js'; import { QueryService } from '@/core/QueryService.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; import { DI } from '@/di-symbols.js'; +import { TimeService } from '@/core/TimeService.js'; export const meta = { tags: ['users'], @@ -62,13 +63,14 @@ export default class extends Endpoint { // eslint- private userEntityService: UserEntityService, private queryService: QueryService, + private readonly timeService: TimeService, ) { super(meta, paramDef, async (ps, me) => { const query = this.usersRepository.createQueryBuilder('user') .where('user.isLocked = FALSE') .andWhere('user.isExplorable = TRUE') .andWhere('user.host IS NULL') - .andWhere('user.updatedAt >= :date', { date: new Date(Date.now() - ms('7days')) }) + .andWhere('user.updatedAt >= :date', { date: new Date(this.timeService.now - ms('7days')) }) .andWhere('user.id != :meId', { meId: me.id }) .orderBy('user.followersCount', 'DESC'); diff --git a/packages/backend/src/server/api/stream/Connection.ts b/packages/backend/src/server/api/stream/Connection.ts index 90657236d6..76fb44997f 100644 --- a/packages/backend/src/server/api/stream/Connection.ts +++ b/packages/backend/src/server/api/stream/Connection.ts @@ -16,6 +16,7 @@ import { ChannelFollowingService } from '@/core/ChannelFollowingService.js'; import { isJsonObject } from '@/misc/json-value.js'; import type { JsonObject, JsonValue } from '@/misc/json-value.js'; import { LoggerService } from '@/core/LoggerService.js'; +import { TimeService, type TimerHandle } from '@/core/TimeService.js'; import type Logger from '@/logger.js'; import { QueryService } from '@/core/QueryService.js'; import type { ChannelsService } from './ChannelsService.js'; @@ -48,7 +49,7 @@ export default class Connection { public myRecentReactions: Map = new Map(); public myRecentRenotes: Set = new Set(); public myRecentFavorites: Set = new Set(); - private fetchIntervalId: NodeJS.Timeout | null = null; + private fetchIntervalId: TimerHandle | null = null; private closingConnection = false; private logger: Logger; @@ -61,6 +62,8 @@ export default class Connection { private notificationService: NotificationService, public readonly cacheService: CacheService, private channelFollowingService: ChannelFollowingService, + private readonly timeService: TimeService, + loggerService: LoggerService, user: MiUser | null | undefined, @@ -126,7 +129,7 @@ export default class Connection { await this.fetch(); if (!this.fetchIntervalId) { - this.fetchIntervalId = setInterval(this.fetch, 1000 * 10); + this.fetchIntervalId = this.timeService.startTimer(this.fetch, 1000 * 10, { repeated: true }); } } } @@ -376,7 +379,7 @@ export default class Connection { */ @bindThis public dispose() { - if (this.fetchIntervalId) clearInterval(this.fetchIntervalId); + if (this.fetchIntervalId) this.timeService.stopTimer(this.fetchIntervalId); for (const c of this.channels.values()) { if (c.dispose) c.dispose(); } diff --git a/packages/backend/src/server/oauth/OAuth2ProviderService.ts b/packages/backend/src/server/oauth/OAuth2ProviderService.ts index 01ee451297..5e8f7c3cb4 100644 --- a/packages/backend/src/server/oauth/OAuth2ProviderService.ts +++ b/packages/backend/src/server/oauth/OAuth2ProviderService.ts @@ -11,6 +11,7 @@ import { DI } from '@/di-symbols.js'; import { MastodonClientService } from '@/server/api/mastodon/MastodonClientService.js'; import { getErrorData } from '@/server/api/mastodon/MastodonLogger.js'; import { ServerUtilityService } from '@/server/ServerUtilityService.js'; +import { TimeService } from '@/core/TimeService.js'; import type { FastifyInstance } from 'fastify'; const kinds = [ @@ -56,6 +57,7 @@ export class OAuth2ProviderService { private readonly mastodonClientService: MastodonClientService, private readonly serverUtilityService: ServerUtilityService, + private readonly timeService: TimeService, ) { } // https://datatracker.ietf.org/doc/html/rfc8414.html @@ -118,7 +120,7 @@ export class OAuth2ProviderService { access_token: uuid(), token_type: 'Bearer', scope: 'read', - created_at: Math.floor(new Date().getTime() / 1000), + created_at: Math.floor(this.timeService.now / 1000), }; return reply.send(ret); } @@ -138,7 +140,7 @@ export class OAuth2ProviderService { access_token: atData.accessToken, token_type: 'Bearer', scope: atData.scope || body.scope || 'read write follow push', - created_at: atData.createdAt || Math.floor(new Date().getTime() / 1000), + created_at: atData.createdAt || Math.floor(this.timeService.now / 1000), }; return reply.send(ret); } catch (e: unknown) { diff --git a/packages/backend/src/server/web/ClientServerService.ts b/packages/backend/src/server/web/ClientServerService.ts index 654d477628..d9b6158e9a 100644 --- a/packages/backend/src/server/web/ClientServerService.ts +++ b/packages/backend/src/server/web/ClientServerService.ts @@ -57,6 +57,7 @@ import { handleRequestRedirectToOmitSearch } from '@/misc/fastify-hook-handlers. import { bindThis } from '@/decorators.js'; import { FlashEntityService } from '@/core/entities/FlashEntityService.js'; import { RoleService } from '@/core/RoleService.js'; +import { TimeService } from '@/core/TimeService.js'; import { ReversiGameEntityService } from '@/core/entities/ReversiGameEntityService.js'; import { AnnouncementEntityService } from '@/core/entities/AnnouncementEntityService.js'; import { FeedService } from './FeedService.js'; @@ -130,6 +131,7 @@ export class ClientServerService { private feedService: FeedService, private roleService: RoleService, private clientLoggerService: ClientLoggerService, + private readonly timeService: TimeService, @Inject('queue:system') public systemQueue: SystemQueue, @Inject('queue:endedPollNotification') public endedPollNotificationQueue: EndedPollNotificationQueue, @@ -216,7 +218,7 @@ export class ClientServerService { instanceUrl: this.config.url, randomMOTD: this.config.customMOTD ? this.config.customMOTD[Math.floor(Math.random() * this.config.customMOTD.length)] : undefined, metaJson: htmlSafeJsonStringify(await this.metaEntityService.packDetailed(meta)), - now: Date.now(), + now: this.timeService.now, }; } diff --git a/packages/backend/src/server/web/FeedService.ts b/packages/backend/src/server/web/FeedService.ts index a622ae7e34..5de05057bc 100644 --- a/packages/backend/src/server/web/FeedService.ts +++ b/packages/backend/src/server/web/FeedService.ts @@ -6,6 +6,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { In, IsNull } from 'typeorm'; import { Feed } from 'feed'; +import { parse as mfmParse } from 'mfm-js'; import { DI } from '@/di-symbols.js'; import type { DriveFilesRepository, NotesRepository, UserProfilesRepository } from '@/models/_.js'; import type { Config } from '@/config.js'; @@ -15,7 +16,7 @@ import { DriveFileEntityService } from '@/core/entities/DriveFileEntityService.j import { bindThis } from '@/decorators.js'; import { IdService } from '@/core/IdService.js'; import { MfmService } from "@/core/MfmService.js"; -import { parse as mfmParse } from 'mfm-js'; +import { TimeService } from '@/core/TimeService.js'; @Injectable() export class FeedService { @@ -36,6 +37,7 @@ export class FeedService { private driveFileEntityService: DriveFileEntityService, private idService: IdService, private mfmService: MfmService, + private readonly timeService: TimeService, ) { } @@ -107,7 +109,7 @@ export class FeedService { private shouldHideNote(reference: number | null, createdAt: Date): boolean { if ((reference !== null) && ( - (reference <= 0 && (Date.now() - createdAt.getTime() > 0 - (reference * 1000))) + (reference <= 0 && (this.timeService.now - createdAt.getTime() > 0 - (reference * 1000))) || (reference > 0 && (createdAt.getTime() < reference * 1000)) ) ) { diff --git a/packages/backend/test/misc/MockLoggerService.ts b/packages/backend/test/misc/MockLoggerService.ts index fe4af8d659..403e4a8097 100644 --- a/packages/backend/test/misc/MockLoggerService.ts +++ b/packages/backend/test/misc/MockLoggerService.ts @@ -10,6 +10,7 @@ import type { Config } from '@/config.js'; import Logger, { type Console } from '@/logger.js'; import { LoggerService } from '@/core/LoggerService.js'; import { bindThis } from '@/decorators.js'; +import { NativeTimeService, TimeService } from '@/core/TimeService.js'; /** * Mocked implementation of LoggerService. @@ -35,9 +36,10 @@ export class MockLoggerService extends LoggerService { */ public verbose: boolean; - constructor(config?: Config) { + constructor(config?: Config, timeService?: TimeService) { config ??= { logging: { verbose: false } } as Config; - super(config); + timeService ??= new NativeTimeService(); + super(config, timeService); } /**