convert all remaining backend code to TimeService

This commit is contained in:
Hazelnoot 2025-10-01 20:26:18 -04:00
parent 9c96dca5a6
commit 3a92471b68
11 changed files with 74 additions and 46 deletions

View file

@ -145,6 +145,8 @@ export interface PromiseTimerHandle<T = void> extends PromiseLike<T> {
@Injectable()
export class NativeTimeService extends TimeService<NativeTimer> implements OnApplicationShutdown {
public get now(): number {
// This is the one place that actually *should* have it
// eslint-disable-next-line no-restricted-properties
return Date.now();
}

View file

@ -14,16 +14,17 @@ import { CustomEmojiService } from '@/core/CustomEmojiService.js';
import { type UserWebhookPayload, UserWebhookService } from '@/core/UserWebhookService.js';
import { QueueService } from '@/core/QueueService.js';
import { IdService } from '@/core/IdService.js';
import { TimeService } from '@/core/TimeService.js';
import { ModeratorInactivityRemainingTime } from '@/queue/processors/CheckModeratorsActivityProcessorService.js';
const oneDayMillis = 24 * 60 * 60 * 1000;
function generateDummyUser(override?: Partial<MiUser>): MiUser {
function generateDummyUser(now: number, override?: Partial<MiUser>): MiUser {
return {
id: 'dummy-user-1',
updatedAt: new Date(Date.now() - oneDayMillis * 7),
lastFetchedAt: new Date(Date.now() - oneDayMillis * 5),
lastActiveDate: new Date(Date.now() - oneDayMillis * 3),
updatedAt: new Date(now - oneDayMillis * 7),
lastFetchedAt: new Date(now - oneDayMillis * 5),
lastActiveDate: new Date(now - oneDayMillis * 3),
hideOnlineStatus: false,
username: 'dummy1',
usernameLower: 'dummy1',
@ -132,31 +133,34 @@ function generateDummyNote(override?: Partial<MiNote>): MiNote {
};
}
const dummyUser1 = generateDummyUser();
const dummyUser2 = generateDummyUser({
id: 'dummy-user-2',
updatedAt: new Date(Date.now() - oneDayMillis * 30),
lastFetchedAt: new Date(Date.now() - oneDayMillis),
lastActiveDate: new Date(Date.now() - oneDayMillis),
username: 'dummy2',
usernameLower: 'dummy2',
name: 'DummyUser2',
followersCount: 40,
followingCount: 50,
notesCount: 900,
});
const dummyUser3 = generateDummyUser({
id: 'dummy-user-3',
updatedAt: new Date(Date.now() - oneDayMillis * 15),
lastFetchedAt: new Date(Date.now() - oneDayMillis * 2),
lastActiveDate: new Date(Date.now() - oneDayMillis * 2),
username: 'dummy3',
usernameLower: 'dummy3',
name: 'DummyUser3',
followersCount: 60,
followingCount: 70,
notesCount: 15900,
});
function makeDummyUsers(now: number) {
const dummyUser1 = generateDummyUser(now);
const dummyUser2 = generateDummyUser(now, {
id: 'dummy-user-2',
updatedAt: new Date(now - oneDayMillis * 30),
lastFetchedAt: new Date(now - oneDayMillis),
lastActiveDate: new Date(now - oneDayMillis),
username: 'dummy2',
usernameLower: 'dummy2',
name: 'DummyUser2',
followersCount: 40,
followingCount: 50,
notesCount: 900,
});
const dummyUser3 = generateDummyUser(now, {
id: 'dummy-user-3',
updatedAt: new Date(now - oneDayMillis * 15),
lastFetchedAt: new Date(now - oneDayMillis * 2),
lastActiveDate: new Date(now - oneDayMillis * 2),
username: 'dummy3',
usernameLower: 'dummy3',
name: 'DummyUser3',
followersCount: 60,
followingCount: 70,
notesCount: 15900,
});
return { dummyUser1, dummyUser2, dummyUser3 };
}
@Injectable()
export class WebhookTestService {
@ -169,6 +173,7 @@ export class WebhookTestService {
private systemWebhookService: SystemWebhookService,
private queueService: QueueService,
private readonly idService: IdService,
private readonly timeService: TimeService,
) {
}
@ -207,6 +212,8 @@ export class WebhookTestService {
this.queueService.userWebhookDeliver(merged, type, contents, { attempts: 1 });
};
const { dummyUser1, dummyUser2, dummyUser3 } = makeDummyUsers(this.timeService.now);
const dummyNote1 = generateDummyNote({
userId: dummyUser1.id,
user: dummyUser1,
@ -311,6 +318,8 @@ export class WebhookTestService {
this.queueService.systemWebhookDeliver(merged, type, contents, { attempts: 1 });
};
const { dummyUser1, dummyUser2, dummyUser3 } = makeDummyUsers(this.timeService.now);
switch (params.type) {
case 'abuseReport': {
send('abuseReport', await this.generateAbuseReport({
@ -396,13 +405,13 @@ export class WebhookTestService {
return {
id: note.id,
threadId: note.threadId ?? note.id,
createdAt: new Date().toISOString(),
createdAt: this.timeService.date.toISOString(),
deletedAt: null,
text: note.text,
cw: note.cw,
userId: note.userId,
userHost: note.userHost ?? null,
user: await this.toPackedUserLite(note.user ?? generateDummyUser()),
user: await this.toPackedUserLite(note.user ?? generateDummyUser(this.timeService.now)),
replyId: note.replyId,
renoteId: note.renoteId,
isHidden: false,
@ -486,7 +495,7 @@ export class WebhookTestService {
uri: null,
movedTo: null,
alsoKnownAs: [],
createdAt: new Date().toISOString(),
createdAt: this.timeService.date.toISOString(),
updatedAt: user.updatedAt?.toISOString() ?? null,
lastFetchedAt: user.lastFetchedAt?.toISOString() ?? null,
bannerUrl: user.bannerId == null ? null : user.bannerUrl,

View file

@ -14,6 +14,7 @@ import { UserKeypairService } from '@/core/UserKeypairService.js';
import { ApUtilityService } from '@/core/activitypub/ApUtilityService.js';
import { HttpRequestService } from '@/core/HttpRequestService.js';
import { LoggerService } from '@/core/LoggerService.js';
import { TimeService } from '@/core/TimeService.js';
import { bindThis } from '@/decorators.js';
import type Logger from '@/logger.js';
import { validateContentTypeSetAsActivityPub } from '@/core/activitypub/misc/validator.js';
@ -40,7 +41,7 @@ type PrivateKey = {
};
export class ApRequestCreator {
static createSignedPost(args: { key: PrivateKey, url: string, body: string, digest?: string, additionalHeaders: Record<string, string> }): Signed {
static createSignedPost(args: { key: PrivateKey, url: string, body: string, digest?: string, additionalHeaders: Record<string, string>, now: Date | string | number }): Signed {
const u = new URL(args.url);
const digestHeader = args.digest ?? this.createDigest(args.body);
@ -48,7 +49,7 @@ export class ApRequestCreator {
url: u.href,
method: 'POST',
headers: this.#objectAssignWithLcKey({
'Date': new Date().toUTCString(),
'Date': new Date(args.now).toUTCString(),
'Host': u.host,
'Content-Type': 'application/activity+json',
'Digest': digestHeader,
@ -69,7 +70,7 @@ export class ApRequestCreator {
return `SHA-256=${crypto.createHash('sha256').update(body).digest('base64')}`;
}
static createSignedGet(args: { key: PrivateKey, url: string, additionalHeaders: Record<string, string> }): Signed {
static createSignedGet(args: { key: PrivateKey, url: string, additionalHeaders: Record<string, string>, now: Date | string | number }): Signed {
const u = new URL(args.url);
const request: Request = {
@ -77,7 +78,7 @@ export class ApRequestCreator {
method: 'GET',
headers: this.#objectAssignWithLcKey({
'Accept': 'application/activity+json, application/ld+json; profile="https://www.w3.org/ns/activitystreams"',
'Date': new Date().toUTCString(),
'Date': new Date(args.now).toUTCString(),
'Host': new URL(args.url).host,
}, args.additionalHeaders),
};
@ -150,6 +151,7 @@ export class ApRequestService {
private httpRequestService: HttpRequestService,
private loggerService: LoggerService,
private readonly apUtilityService: ApUtilityService,
private readonly timeService: TimeService,
) {
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
this.logger = this.loggerService?.getLogger('ap-request'); // なぜか TypeError: Cannot read properties of undefined (reading 'getLogger') と言われる
@ -171,6 +173,7 @@ export class ApRequestService {
digest,
additionalHeaders: {
},
now: this.timeService.now,
});
await this.httpRequestService.send(url, {
@ -200,6 +203,7 @@ export class ApRequestService {
url,
additionalHeaders: {
},
now: this.timeService.now,
});
const res = await this.httpRequestService.send(url, {

View file

@ -7,13 +7,13 @@ import { secureRndstr } from './secure-rndstr.js';
const CHARS = '23456789ABCDEFGHJKLMNPQRSTUVWXYZ'; // [0-9A-Z] w/o [01IO] (32 patterns)
export function generateInviteCode(): string {
export function generateInviteCode(now: number): string {
const code = secureRndstr(8, {
chars: CHARS,
});
const uniqueId = [];
let n = Math.floor(Date.now() / 1000 / 60);
let n = Math.floor(now / 1000 / 60);
while (true) {
uniqueId.push(CHARS[n % CHARS.length]);
const t = Math.floor(n / CHARS.length);

View file

@ -24,6 +24,8 @@ export async function resetDb(db: DataSource) {
if (i === 3) {
throw e;
} else {
// Ignore rule - this is just testing code.
// eslint-disable-next-line no-restricted-globals
await new Promise(resolve => setTimeout(resolve, 1000));
continue;
}

View file

@ -11,6 +11,7 @@ import { DI } from '@/di-symbols.js';
import type Logger from '@/logger.js';
import { bindThis } from '@/decorators.js';
import { CheckModeratorsActivityProcessorService } from '@/queue/processors/CheckModeratorsActivityProcessorService.js';
import { TimeService } from '@/core/TimeService.js';
import { renderFullError } from '@/misc/render-full-error.js';
import { renderInlineError } from '@/misc/render-inline-error.js';
import { UserWebhookDeliverProcessorService } from './processors/UserWebhookDeliverProcessorService.js';
@ -61,10 +62,10 @@ function httpRelatedBackoff(attemptsMade: number) {
return backoff;
}
function getJobInfo(job: Bull.Job | undefined, increment = false): string {
function _getJobInfo(now: number, job: Bull.Job | undefined, increment = false): string {
if (job == null) return '-';
const age = Date.now() - job.timestamp;
const age = now - job.timestamp;
const formated = age > 60000 ? `${Math.floor(age / 1000 / 60)}m`
: age > 10000 ? `${Math.floor(age / 1000)}s`
@ -134,9 +135,15 @@ export class QueueProcessorService implements OnApplicationShutdown {
private checkModeratorsActivityProcessorService: CheckModeratorsActivityProcessorService,
private cleanProcessorService: CleanProcessorService,
private scheduleNotePostProcessorService: ScheduleNotePostProcessorService,
private readonly timeService: TimeService,
) {
this.logger = this.queueLoggerService.logger;
// This is just to avoid modifying all the existing code.
const getJobInfo = (job: Bull.Job | undefined, increment = false) => {
return _getJobInfo(this.timeService.now, job, increment);
};
//#region system
{
const processer = (job: Bull.Job) => {
@ -559,7 +566,7 @@ export class QueueProcessorService implements OnApplicationShutdown {
// Render job
if (job) {
parts.push('job [');
parts.push(getJobInfo(job));
parts.push(_getJobInfo(this.timeService.now, job));
parts.push('] failed: ');
} else {
parts.push('job failed: ');

View file

@ -78,7 +78,7 @@ export class ExportCustomEmojisProcessorService {
});
};
await writeMeta(`{"metaVersion":2,"host":"${this.config.host}","exportedAt":"${new Date().toString()}","emojis":[`);
await writeMeta(`{"metaVersion":2,"host":"${this.config.host}","exportedAt":"${this.timeService.date.toString()}","emojis":[`);
const customEmojis = await this.emojisRepository.find({
where: {

View file

@ -11,6 +11,7 @@ import { IdService } from '@/core/IdService.js';
import { DI } from '@/di-symbols.js';
import { generateInviteCode } from '@/misc/generate-invite-code.js';
import { ModerationLogService } from '@/core/ModerationLogService.js';
import type { TimeService } from '@/core/TimeService.js';
import { ApiError } from '../../../error.js';
export const meta = {
@ -57,6 +58,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
private inviteCodeEntityService: InviteCodeEntityService,
private idService: IdService,
private moderationLogService: ModerationLogService,
private readonly timeService: TimeService,
) {
super(meta, paramDef, async (ps, me) => {
if (ps.expiresAt && isNaN(Date.parse(ps.expiresAt))) {
@ -71,7 +73,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
createdBy: me,
createdById: me.id,
expiresAt: ps.expiresAt ? new Date(ps.expiresAt) : null,
code: generateInviteCode(),
code: generateInviteCode(this.timeService.now),
}));
}

View file

@ -79,7 +79,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
createdBy: me,
createdById: me.id,
expiresAt: policies.inviteExpirationTime ? new Date(this.timeService.now + (policies.inviteExpirationTime * 1000 * 60)) : null,
code: generateInviteCode(),
code: generateInviteCode(this.timeService.now),
});
return await this.inviteCodeEntityService.pack(ticket, me);

View file

@ -66,6 +66,8 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
logger.info('---- Database reset complete.');
// Ignore rule - this is just testing code.
// eslint-disable-next-line no-restricted-globals
await new Promise(resolve => setTimeout(resolve, 1000));
});
}

View file

@ -40,7 +40,7 @@ describe('ap-request', () => {
'User-Agent': 'UA',
};
const req = ApRequestCreator.createSignedPost({ key, url, body, additionalHeaders: headers });
const req = ApRequestCreator.createSignedPost({ key, url, body, additionalHeaders: headers, now: Date.now() });
const parsed = buildParsedSignature(req.signingString, req.signature, 'rsa-sha256');
@ -56,7 +56,7 @@ describe('ap-request', () => {
'User-Agent': 'UA',
};
const req = ApRequestCreator.createSignedGet({ key, url, additionalHeaders: headers });
const req = ApRequestCreator.createSignedGet({ key, url, additionalHeaders: headers, now: Date.now() });
const parsed = buildParsedSignature(req.signingString, req.signature, 'rsa-sha256');