use TimeService everywhere in the backend

This commit is contained in:
Hazelnoot 2025-10-01 19:11:33 -04:00
parent ed750fd990
commit 6cceca90f9
123 changed files with 550 additions and 285 deletions

View file

@ -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<MiUser['id'], Set<string>>;
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<MiUser['id'], Set<string>>();
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

View file

@ -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<MiApp>(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

View file

@ -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();

View file

@ -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<WebSocket.WebSocket, number>();
#connectionsByClient = new Map<string, Set<WebSocket.WebSocket>>(); // 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<void> {
if (this.#cleanConnectionsIntervalId) {
clearInterval(this.#cleanConnectionsIntervalId);
this.timeService.stopTimer(this.#cleanConnectionsIntervalId);
this.#cleanConnectionsIntervalId = null;
}

View file

@ -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<typeof meta, typeof paramDef> { // 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();

View file

@ -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<typeof meta, typeof paramDef> { // 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<typeof meta, typeof paramDef> { // 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を渡すようにするため */

View file

@ -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<typeof meta, typeof paramDef> { // 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<typeof meta, typeof paramDef> { // 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) {

View file

@ -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<typeof meta, typeof paramDef> { // 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<typeof meta, typeof paramDef> { // eslint-
throw new ApiError(meta.errors.noSuchUser);
}
if (ps.expiresAt && ps.expiresAt <= Date.now()) {
if (ps.expiresAt && ps.expiresAt <= this.timeService.now) {
return;
}

View file

@ -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<typeof meta, typeof paramDef> { // 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<typeof meta, typeof paramDef> { // 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');

View file

@ -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<typeof meta, typeof paramDef> { // 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': {

View file

@ -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<typeof meta, typeof paramDef> { // 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<typeof meta, typeof paramDef> { // eslint-
}
}
const now = new Date();
const now = this.timeService.date;
const antenna = await this.antennasRepository.insertOne({
id: this.idService.gen(now.getTime()),

View file

@ -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<typeof meta, typeof paramDef> { // 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<typeof meta, typeof paramDef> { // 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) {

View file

@ -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<typeof meta, typeof paramDef> { // 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<typeof meta, typeof paramDef> { // 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 }));

View file

@ -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<typeof meta, typeof paramDef> { // 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<typeof meta, typeof paramDef> { // 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()),

View file

@ -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<typeof meta, typeof paramDef> { // 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',

View file

@ -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<typeof meta, typeof paramDef> { // 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()) {

View file

@ -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<typeof meta, typeof paramDef> { // 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,

View file

@ -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<typeof meta, typeof paramDef> { // 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<typeof meta, typeof paramDef> { // 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)

View file

@ -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<typeof meta, typeof paramDef> { // 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);

View file

@ -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<typeof meta, typeof paramDef> { // 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<typeof meta, typeof paramDef> { // 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,

View file

@ -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<typeof meta, typeof paramDef> { // 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<typeof meta, typeof paramDef> { // 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);
}

View file

@ -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<typeof meta, typeof paramDef> { // 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<typeof meta, typeof paramDef> { // 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);
}

View file

@ -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<typeof meta, typeof paramDef> { // eslint-
private driveFilesRepository: DriveFilesRepository,
private galleryPostEntityService: GalleryPostEntityService,
private readonly timeService: TimeService,
) {
super(meta, paramDef, async (ps, me) => {
let files: Array<MiDriveFile> | undefined;
@ -81,7 +83,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
id: ps.postId,
userId: me.id,
}, {
updatedAt: new Date(),
updatedAt: this.timeService.date,
title: ps.title,
description: ps.description,
isSensitive: ps.isSensitive,

View file

@ -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<typeof meta, typeof paramDef> { // 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 {

View file

@ -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<typeof meta, typeof paramDef> { // 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<typeof meta, typeof paramDef> { // 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 });

View file

@ -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<typeof meta, typeof paramDef> { // 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 はキャッシュされていて古い可能性があるので改めて取得

View file

@ -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<typeof meta, typeof paramDef> { // 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<typeof meta, typeof paramDef> { // 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);

View file

@ -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<typeof meta, typeof paramDef> { // 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<typeof meta, typeof paramDef> { // 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);

View file

@ -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<typeof meta, typeof paramDef> { // 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<typeof meta, typeof paramDef> { // 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);

View file

@ -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<typeof meta, typeof paramDef> { // 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<typeof meta, typeof paramDef> { // 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);

View file

@ -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<typeof meta, typeof paramDef> { // 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<typeof meta, typeof paramDef> { // 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(),
});

View file

@ -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<typeof meta, typeof paramDef> { // 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;

View file

@ -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<typeof meta, typeof paramDef> { // 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<typeof meta, typeof paramDef> { // 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({

View file

@ -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<typeof meta, typeof paramDef> { // 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<typeof meta, typeof paramDef> { // eslint-
throw new ApiError(meta.errors.alreadyMuting);
}
if (ps.expiresAt && ps.expiresAt <= Date.now()) {
if (ps.expiresAt && ps.expiresAt <= this.timeService.now) {
return;
}

View file

@ -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<typeof meta, typeof paramDef> { // 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<typeof meta, typeof paramDef> { // 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<typeof meta, typeof paramDef> { // 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,

View file

@ -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<typeof meta, typeof paramDef> { // 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<typeof meta, typeof paramDef> { // 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;
}
}

View file

@ -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<typeof meta, typeof paramDef> { // 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<typeof meta, typeof paramDef> { // 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;
}
}

View file

@ -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<typeof meta, typeof paramDef> { // 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<typeof meta, typeof paramDef> { // 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 });
}));
}

View file

@ -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<typeof meta, typeof paramDef> { // 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 => {

View file

@ -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<typeof meta, typeof paramDef> { // 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<typeof meta, typeof paramDef> { // 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<typeof meta, typeof paramDef> { // 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<typeof meta, typeof paramDef> { // 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<typeof meta, typeof paramDef> { // 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,
}, {

View file

@ -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<typeof meta, typeof paramDef> { // 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<typeof meta, typeof paramDef> { // 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,

View file

@ -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<typeof meta, typeof paramDef> { // 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<typeof meta, typeof paramDef> { // 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,

View file

@ -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<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
constructor(
private readonly timeService: TimeService,
) {
super(meta, paramDef, async () => {
return {
pong: Date.now(),
pong: this.timeService.now,
};
});
}

View file

@ -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<typeof meta, typeof paramDef> { // 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<typeof meta, typeof paramDef> { // 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
}

View file

@ -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<typeof meta, typeof paramDef> { // 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<typeof meta, typeof paramDef> { // 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');

View file

@ -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<typeof meta, typeof paramDef> { // 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<typeof meta, typeof paramDef> { // 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) {

View file

@ -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<typeof meta, typeof paramDef> { // 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');

View file

@ -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<string, string> = new Map();
public myRecentRenotes: Set<string> = new Set();
public myRecentFavorites: Set<string> = 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();
}

View file

@ -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) {

View file

@ -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,
};
}

View file

@ -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))
)
) {