Compare commits

...

1 Commits

Author SHA1 Message Date
liuyi
ba78a6bd45 refactor(server): use duration helper 2025-06-27 11:12:15 +08:00
33 changed files with 99 additions and 119 deletions

View File

@@ -4,6 +4,7 @@ import ava, { TestFn } from 'ava';
import Sinon from 'sinon';
import request from 'supertest';
import { Due } from '../../base';
import { AuthModule, CurrentUser, Public, Session } from '../../core/auth';
import { AuthService } from '../../core/auth/service';
import { Models } from '../../models';
@@ -125,7 +126,7 @@ test('should be able to refresh session if needed', async t => {
sessionId,
},
data: {
expiresAt: new Date(Date.now() + 1000 * 60 * 60 /* expires in 1 hour */),
expiresAt: Due.after('1h'),
},
});

View File

@@ -3,6 +3,7 @@ import { PrismaClient } from '@prisma/client';
import test from 'ava';
import * as Sinon from 'sinon';
import { Due } from '../../base';
import { DocStorageModule, PgWorkspaceDocStorageAdapter } from '../../core/doc';
import { DocStorageOptions } from '../../core/doc/options';
import { DocRecord } from '../../core/doc/storage';
@@ -122,7 +123,7 @@ test('should create history if time diff is larger than interval config and stat
// @ts-expect-error private method
Sinon.stub(adapter, 'lastDocHistory').resolves({
timestamp: new Date(timestamp.getTime() - 1000 * 60 * 20),
timestamp: Due.before('20m', timestamp),
state: Buffer.from([0, 1]),
});

View File

@@ -5,6 +5,7 @@ import { Config } from '../../base/config';
import { SessionModel } from '../../models/session';
import { UserModel } from '../../models/user';
import { createTestingModule, type TestingModule } from '../utils';
import { Due } from '../../base/utils';
interface Context {
config: Config;
@@ -137,9 +138,7 @@ test('should not refresh userSession when expires time not hit ttr', async t =>
let newExpiresAt =
await t.context.session.refreshUserSessionIfNeeded(userSession);
t.is(newExpiresAt, undefined);
userSession.expiresAt = new Date(
userSession.expiresAt!.getTime() - t.context.config.auth.session.ttr * 1000
);
userSession.expiresAt = Due.before(t.context.config.auth.session.ttr);
newExpiresAt =
await t.context.session.refreshUserSessionIfNeeded(userSession);
t.is(newExpiresAt, undefined);
@@ -154,9 +153,9 @@ test('should not refresh userSession when expires time hit ttr', async t => {
user.id,
session.id
);
const ttr = t.context.config.auth.session.ttr * 2;
userSession.expiresAt = new Date(
userSession.expiresAt!.getTime() - ttr * 1000
userSession.expiresAt!.getTime() -
Due.ms(t.context.config.auth.session.ttr) * 2
);
const newExpiresAt =
await t.context.session.refreshUserSessionIfNeeded(userSession);

View File

@@ -6,7 +6,7 @@ import Sinon from 'sinon';
import Stripe from 'stripe';
import { AppModule } from '../../app.module';
import { EventBus } from '../../base';
import { Due, EventBus } from '../../base';
import { ConfigFactory, ConfigModule } from '../../base/config';
import { CurrentUser } from '../../core/auth';
import { AuthService } from '../../core/auth/service';
@@ -129,8 +129,8 @@ const sub: Stripe.Subscription = {
object: 'subscription',
cancel_at_period_end: false,
canceled_at: null,
current_period_end: unixNow() + 60 * 60 * 24 * 30,
current_period_start: unixNow() - 60 * 60 * 24 * 1,
current_period_end: Due.after('30d').getTime() / 1000,
current_period_start: Due.before('1d').getTime() / 1000,
// @ts-expect-error stub
customer: {
id: 'cus_1',
@@ -914,7 +914,7 @@ const subscriptionSchedule: Stripe.SubscriptionSchedule = {
},
],
start_date: unixNow(),
end_date: unixNow() + 30 * 24 * 60 * 60,
end_date: Due.after('30d').getTime() / 1000,
},
{
items: [
@@ -924,7 +924,7 @@ const subscriptionSchedule: Stripe.SubscriptionSchedule = {
quantity: 1,
},
],
start_date: unixNow() + 30 * 24 * 60 * 60,
start_date: Due.after('30d').getTime() / 1000,
},
],
};
@@ -1550,10 +1550,7 @@ test('should be able to subscribe onetime payment subscription', async t => {
t.is(subInDB?.recurring, SubscriptionRecurring.Monthly);
t.is(subInDB?.status, SubscriptionStatus.Active);
t.is(subInDB?.stripeSubscriptionId, null);
t.is(
subInDB?.end?.toDateString(),
new Date(Date.now() + 30 * 24 * 60 * 60 * 1000).toDateString()
);
t.is(subInDB?.end?.toDateString(), Due.after('30d').toDateString());
});
test('should be able to accumulate onetime payment subscription period', async t => {
@@ -1574,7 +1571,7 @@ test('should be able to accumulate onetime payment subscription period', async t
});
// add 365 days
t.is(subInDB!.end!.getTime(), end.getTime() + 365 * 24 * 60 * 60 * 1000);
t.is(subInDB!.end!.getTime(), Due.after('1y', end).getTime());
});
test('should be able to recalculate onetime payment subscription period after expiration', async t => {
@@ -1599,10 +1596,7 @@ test('should be able to recalculate onetime payment subscription period after ex
});
// add 365 days from now
t.is(
subInDB?.end?.toDateString(),
new Date(Date.now() + 365 * 24 * 60 * 60 * 1000).toDateString()
);
t.is(subInDB?.end?.toDateString(), Due.after('1y').toDateString());
});
test('should not accumulate onetime payment subscription period for redeemed invoices', async t => {
@@ -1617,10 +1611,7 @@ test('should not accumulate onetime payment subscription period for redeemed inv
where: { targetId: u1.id },
});
t.is(
subInDB?.end?.toDateString(),
new Date(Date.now() + 365 * 24 * 60 * 60 * 1000).toDateString()
);
t.is(subInDB?.end?.toDateString(), Due.after('1y').toDateString());
});
// TEAM

View File

@@ -1,10 +1,11 @@
import Redis from 'ioredis';
import { type Duration, Due } from '../utils';
export interface CacheSetOptions {
/**
* in milliseconds
*/
ttl?: number;
ttl?: Duration;
}
export class CacheProvider {
@@ -30,7 +31,7 @@ export class CacheProvider {
): Promise<boolean> {
if (opts.ttl) {
return this.redis
.set(key, JSON.stringify(value), 'PX', opts.ttl)
.set(key, JSON.stringify(value), 'PX', Due.ms(opts.ttl))
.then(() => true)
.catch(() => false);
}
@@ -56,7 +57,7 @@ export class CacheProvider {
): Promise<boolean> {
if (opts.ttl) {
return this.redis
.set(key, JSON.stringify(value), 'PX', opts.ttl, 'NX')
.set(key, JSON.stringify(value), 'PX', Due.ms(opts.ttl), 'NX')
.then(v => !!v)
.catch(() => false);
}

View File

@@ -103,8 +103,8 @@ test('should override correctly', t => {
// keyed config
// 'session.ttl', 'session.ttr'
session: {
ttl: 2000,
ttr: 1000,
ttl: '1M',
ttr: '1d',
},
},
storages: {
@@ -131,7 +131,7 @@ test('should override correctly', t => {
},
allowSignup: true,
session: {
ttl: 3000,
ttl: '2M',
},
},
storages: {
@@ -159,8 +159,8 @@ test('should override correctly', t => {
// right merged to left
t.deepEqual(config.auth.session, {
ttl: 3000,
ttr: 1000,
ttl: '2M',
ttr: '1d',
});
// right covered left

View File

@@ -6,6 +6,7 @@ import { type QueueOptions } from 'bullmq';
import { Config } from '../../config';
import { QueueRedis } from '../../redis';
import { Due } from '../../utils';
import { Queue, QUEUES } from './def';
import { JobExecutor } from './executor';
import { JobQueue } from './queue';
@@ -40,7 +41,7 @@ export class JobModule {
...QUEUES.map(name => {
if (name === Queue.NIGHTLY_JOB) {
// avoid nightly jobs been run multiple times
return { name, removeOnComplete: { age: 1000 * 60 * 60 } };
return { name, removeOnComplete: { age: Due.ms('1m') } };
}
return { name };
})

View File

@@ -22,7 +22,7 @@ import {
PutObjectMetadata,
StorageProvider,
} from './provider';
import { autoMetadata, SIGNED_URL_EXPIRED, toBuffer } from './utils';
import { autoMetadata, SIGNED_URL_EXPIRED_SEC, toBuffer } from './utils';
export interface S3StorageConfig extends S3ClientConfig {
usePresignedURL?: {
@@ -138,7 +138,7 @@ export class S3StorageProvider implements StorageProvider {
Bucket: this.bucket,
Key: key,
}),
{ expiresIn: SIGNED_URL_EXPIRED }
{ expiresIn: SIGNED_URL_EXPIRED_SEC }
);
return {

View File

@@ -4,6 +4,7 @@ import { crc32 } from '@node-rs/crc32';
import { getStreamAsBuffer } from 'get-stream';
import { getMime } from '../../../native';
import { Due } from '../../utils';
import { BlobInputType, PutObjectMetadata } from './provider';
export async function toBuffer(input: BlobInputType): Promise<Buffer> {
@@ -43,4 +44,4 @@ export function autoMetadata(
return metadata;
}
export const SIGNED_URL_EXPIRED = 60 * 60; // 1 hour
export const SIGNED_URL_EXPIRED_SEC = Due.s('1h');

View File

@@ -53,26 +53,28 @@ function parse(str: string): DurationInput {
return input;
}
export type Duration = string | DurationInput;
export const Due = {
ms: (dueStr: string | DurationInput) => {
const input = typeof dueStr === 'string' ? parse(dueStr) : dueStr;
ms: (duration: Duration) => {
const input = typeof duration === 'string' ? parse(duration) : duration;
return Object.entries(input).reduce((duration, [unit, val]) => {
return duration + UnitToSecMap[unit as DurationUnit] * (val || 0) * 1000;
}, 0);
},
s: (dueStr: string | DurationInput) => {
const input = typeof dueStr === 'string' ? parse(dueStr) : dueStr;
s: (duration: Duration) => {
const input = typeof duration === 'string' ? parse(duration) : duration;
return Object.entries(input).reduce((duration, [unit, val]) => {
return duration + UnitToSecMap[unit as DurationUnit] * (val || 0);
}, 0);
},
parse,
after: (dueStr: string | number | DurationInput, date?: Date) => {
const timestamp = typeof dueStr === 'number' ? dueStr : Due.ms(dueStr);
after: (duration: Duration, date?: Date) => {
const timestamp = Due.ms(duration);
return new Date((date?.getTime() ?? Date.now()) + timestamp);
},
before: (dueStr: string | number | DurationInput, date?: Date) => {
const timestamp = typeof dueStr === 'number' ? dueStr : Due.ms(dueStr);
before: (duration: Duration, date?: Date) => {
const timestamp = Due.ms(duration);
return new Date((date?.getTime() ?? Date.now()) - timestamp);
},
};

View File

@@ -1,4 +1,3 @@
export const OneKB = 1024;
export const OneMB = OneKB * OneKB;
export const OneGB = OneKB * OneMB;
export const OneDay = 1000 * 60 * 60 * 24;

View File

@@ -4,8 +4,8 @@ import { defineModuleConfig } from '../../base';
export interface AuthConfig {
session: {
ttl: number;
ttr: number;
ttl: string;
ttr: string;
};
allowSignup: boolean;
requireEmailDomainVerification: boolean;
@@ -60,10 +60,10 @@ defineModuleConfig('auth', {
},
'session.ttl': {
desc: 'Application auth expiration time in seconds.',
default: 60 * 60 * 24 * 15, // 15 days
default: '15d',
},
'session.ttr': {
desc: 'Application auth time to refresh in seconds.',
default: 60 * 60 * 24 * 7, // 7 days
default: '7d',
},
});

View File

@@ -199,21 +199,17 @@ export class AuthController {
throw new WrongSignInCredentials({ email });
}
const ttlInSec = 30 * 60;
const ttl = '30m';
const token = await this.models.verificationToken.create(
TokenType.SignIn,
email,
ttlInSec
ttl
);
const otp = this.crypto.otp();
// TODO(@forehalo): this is a temporary solution, we should not rely on cache to store the otp
const cacheKey = OTP_CACHE_KEY(otp);
await this.cache.set(
cacheKey,
{ token, clientNonce },
{ ttl: ttlInSec * 1000 }
);
await this.cache.set(cacheKey, { token, clientNonce }, { ttl });
const magicLink = this.url.link(callbackUrl, {
token: otp,

View File

@@ -2,7 +2,7 @@ import { Injectable, OnApplicationBootstrap } from '@nestjs/common';
import type { CookieOptions, Request, Response } from 'express';
import { assign, pick } from 'lodash-es';
import { Config, SignUpForbidden } from '../../base';
import { Config, type Duration, SignUpForbidden } from '../../base';
import { Models, type User, type UserSession } from '../../models';
import { FeatureService } from '../features';
import { Mailer } from '../mail/mailer';
@@ -128,7 +128,7 @@ export class AuthService implements OnApplicationBootstrap {
return await this.models.session.findUserSessionsBySessionId(sessionId);
}
async createUserSession(userId: string, sessionId?: string, ttl?: number) {
async createUserSession(userId: string, sessionId?: string, ttl?: Duration) {
return await this.models.session.createOrRefreshUserSession(
userId,
sessionId,
@@ -157,7 +157,7 @@ export class AuthService implements OnApplicationBootstrap {
async refreshUserSessionIfNeeded(
res: Response,
userSession: UserSession,
ttr?: number
ttr?: Duration
): Promise<boolean> {
const newExpiresAt = await this.models.session.refreshUserSessionIfNeeded(
userSession,

View File

@@ -4,6 +4,7 @@ import { chunk } from 'lodash-es';
import {
DocHistoryNotFound,
DocNotFound,
Due,
EventBus,
FailedToSaveUpdates,
FailedToUpsertSnapshot,
@@ -251,7 +252,8 @@ export class PgWorkspaceDocStorageAdapter extends DocStorageAdapter {
force ||
// last history created before interval in configs
lastHistoryTimestamp <
snapshot.timestamp - this.options.historyMinInterval(snapshot.spaceId)
snapshot.timestamp -
Due.ms(this.options.historyMinInterval(snapshot.spaceId))
) {
shouldCreateHistory = true;
}

View File

@@ -4,7 +4,7 @@ declare global {
interface AppConfigSchema {
doc: {
history: {
interval: number;
interval: string;
};
experimental: {
yocto: boolean;
@@ -20,6 +20,6 @@ defineModuleConfig('doc', {
},
'history.interval': {
desc: 'The minimum time interval in milliseconds of creating a new history snapshot when doc get updated.',
default: 1000 * 60 * 10 /* 10 mins */,
default: '10m',
},
});

View File

@@ -25,8 +25,6 @@ import {
import { PgWorkspaceDocStorageAdapter } from './adapters/workspace';
import { type DocDiff, type DocRecord } from './storage';
const DOC_CONTENT_CACHE_7_DAYS = 7 * 24 * 60 * 60 * 1000;
export interface WorkspaceDocInfo {
id: string;
name: string;
@@ -90,7 +88,7 @@ export abstract class DocReader {
const content = await this.getDocContentWithoutCache(workspaceId, docId);
if (content) {
await this.cache.set(cacheKey, content, {
ttl: DOC_CONTENT_CACHE_7_DAYS,
ttl: '7d',
});
}
return content;

View File

@@ -8,7 +8,7 @@ import {
createTestingModule,
type TestingModule,
} from '../../../__tests__/utils';
import { NotificationNotFound } from '../../../base';
import { Due, NotificationNotFound } from '../../../base';
import {
DocMode,
MentionNotificationBody,
@@ -381,7 +381,7 @@ test('should clean expired notifications', async t => {
// wait for 100 days
mock.timers.enable({
apis: ['Date'],
now: Date.now() + 1000 * 60 * 60 * 24 * 100,
now: Due.after('100d'),
});
await t.context.models.notification.cleanExpiredNotifications();
count = await notificationService.countByUserId(member.id);
@@ -390,7 +390,7 @@ test('should clean expired notifications', async t => {
// wait for 1 year
mock.timers.enable({
apis: ['Date'],
now: Date.now() + 1000 * 60 * 60 * 24 * 365,
now: Due.after('1y'),
});
await t.context.models.notification.cleanExpiredNotifications();
count = await notificationService.countByUserId(member.id);

View File

@@ -1,4 +1,4 @@
import { OneDay, OneKB } from '../../base';
import { Due, OneKB } from '../../base';
export const ByteUnit = ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
@@ -14,6 +14,7 @@ export function formatSize(bytes: number, decimals: number = 2): string {
);
}
const ONE_DAY_IN_MS = Due.ms('1d');
export function formatDate(ms: number): string {
return `${(ms / OneDay).toFixed(0)} days`;
return `${(ms / ONE_DAY_IN_MS).toFixed(0)} days`;
}

View File

@@ -403,8 +403,7 @@ export class WorkspaceDocResolver {
const allowed = await this.cache.setnx(
`fixingOwner:${workspaceId}:${docId}`,
1,
// TODO(@forehalo): we definitely need a timer helper
{ ttl: 1000 * 60 * 60 * 24 }
{ ttl: '1d' }
);
// fixed by other instance

View File

@@ -174,13 +174,11 @@ export class InviteResult {
error?: object;
}
const Day = 24 * 60 * 60 * 1000;
export enum WorkspaceInviteLinkExpireTime {
OneDay = Day,
ThreeDays = 3 * Day,
OneWeek = 7 * Day,
OneMonth = 30 * Day,
OneDay = '1d',
ThreeDays = '3d',
OneWeek = '1w',
OneMonth = '1M',
}
registerEnumType(WorkspaceInviteLinkExpireTime, {

View File

@@ -4,6 +4,7 @@ import { mock } from 'node:test';
import ava, { TestFn } from 'ava';
import { createTestingModule, type TestingModule } from '../../__tests__/utils';
import { Due } from '../../base';
import { Config } from '../../base/config';
import {
DocMode,
@@ -259,7 +260,7 @@ test('should clean expired notifications', async t => {
// wait for 1 year
mock.timers.enable({
apis: ['Date'],
now: Date.now() + 1000 * 60 * 60 * 24 * 365,
now: Due.after('1y'),
});
count = await t.context.models.notification.cleanExpiredNotifications();
t.is(count, 1);

View File

@@ -1,6 +1,6 @@
import { z } from 'zod';
import { OneDay, OneGB, OneMB } from '../../base';
import { Due, OneGB, OneMB } from '../../base';
const UserPlanQuotaConfig = z.object({
// quota name
@@ -104,7 +104,7 @@ export const FeatureConfigs: {
blobLimit: 10 * OneMB,
businessBlobLimit: 100 * OneMB,
storageQuota: 10 * OneGB,
historyPeriod: 7 * OneDay,
historyPeriod: Due.ms('7d'),
memberLimit: 3,
copilotActionLimit: 10,
},
@@ -116,7 +116,7 @@ export const FeatureConfigs: {
name: 'Pro',
blobLimit: 100 * OneMB,
storageQuota: 100 * OneGB,
historyPeriod: 30 * OneDay,
historyPeriod: Due.ms('30d'),
memberLimit: 10,
copilotActionLimit: 10,
},
@@ -128,7 +128,7 @@ export const FeatureConfigs: {
name: 'Lifetime Pro',
blobLimit: 100 * OneMB,
storageQuota: 1024 * OneGB,
historyPeriod: 30 * OneDay,
historyPeriod: Due.ms('30d'),
memberLimit: 10,
copilotActionLimit: 10,
},
@@ -141,7 +141,7 @@ export const FeatureConfigs: {
blobLimit: 500 * OneMB,
storageQuota: 100 * OneGB,
seatQuota: 20 * OneGB,
historyPeriod: 30 * OneDay,
historyPeriod: Due.ms('30d'),
memberLimit: 1,
},
},

View File

@@ -7,7 +7,7 @@ import {
} from '@prisma/client';
import { z } from 'zod';
import { PaginationInput } from '../base';
import { Due, PaginationInput } from '../base';
import { BaseModel } from './base';
import { DocMode } from './common';
@@ -15,8 +15,6 @@ export { NotificationLevel, NotificationType };
export type { Notification };
// #region input
export const ONE_YEAR = 1000 * 60 * 60 * 24 * 365;
const IdSchema = z.string().trim().min(1).max(100);
export const BaseNotificationCreateSchema = z.object({
@@ -237,7 +235,7 @@ export class NotificationModel extends BaseModel {
async cleanExpiredNotifications() {
const { count } = await this.db.notification.deleteMany({
// delete notifications that are older than one year
where: { createdAt: { lte: new Date(Date.now() - ONE_YEAR) } },
where: { createdAt: { lte: Due.before('1y') } },
});
if (count > 0) {
this.logger.log(`Deleted ${count} expired notifications`);

View File

@@ -6,7 +6,7 @@ import {
type UserSession,
} from '@prisma/client';
import { Config } from '../base';
import { Config, Due, Duration } from '../base';
import { BaseModel } from './base';
export type { Session, UserSession };
@@ -46,7 +46,7 @@ export class SessionModel extends BaseModel {
async createOrRefreshUserSession(
userId: string,
sessionId?: string,
ttl = this.config.auth.session.ttl
ttl: Duration = this.config.auth.session.ttl
) {
// check whether given session is valid
if (sessionId) {
@@ -66,7 +66,7 @@ export class SessionModel extends BaseModel {
sessionId = session.id;
}
const expiresAt = new Date(Date.now() + ttl * 1000);
const expiresAt = Due.after(ttl);
return await this.db.userSession.upsert({
where: {
sessionId_userId: {
@@ -87,19 +87,17 @@ export class SessionModel extends BaseModel {
async refreshUserSessionIfNeeded(
userSession: UserSession,
ttr = this.config.auth.session.ttr
ttr: Duration = this.config.auth.session.ttr
): Promise<Date | undefined> {
if (
userSession.expiresAt &&
userSession.expiresAt.getTime() - Date.now() > ttr * 1000
Due.before(ttr, userSession.expiresAt) > new Date()
) {
// no need to refresh
return;
}
const newExpiresAt = new Date(
Date.now() + this.config.auth.session.ttl * 1000
);
const newExpiresAt = Due.after(this.config.auth.session.ttl);
await this.db.userSession.update({
where: {
id: userSession.id,

View File

@@ -3,6 +3,7 @@ import { randomUUID } from 'node:crypto';
import { Injectable } from '@nestjs/common';
import { type VerificationToken } from '@prisma/client';
import { Due, Duration } from '../base';
import { CryptoHelper } from '../base/helpers';
import { BaseModel } from './base';
@@ -25,18 +26,14 @@ export class VerificationTokenModel extends BaseModel {
/**
* create token by type and credential (optional) with ttl in seconds (default 30 minutes)
*/
async create(
type: TokenType,
credential?: string,
ttlInSec: number = 30 * 60
) {
async create(type: TokenType, credential?: string, ttl: Duration = '30m') {
const plaintextToken = randomUUID();
const { token } = await this.db.verificationToken.create({
data: {
type,
token: plaintextToken,
credential,
expiresAt: new Date(Date.now() + ttlInSec * 1000),
expiresAt: Due.after(ttl),
},
});
return this.crypto.encrypt(token);

View File

@@ -78,7 +78,7 @@ export class CaptchaService {
const challenge = await this.models.verificationToken.create(
TokenType.Challenge,
resource,
5 * 60
'5m'
);
return {

View File

@@ -6,7 +6,6 @@ import { SessionCache } from '../../base';
import { SubmittedMessage, SubmittedMessageSchema } from './types';
const CHAT_MESSAGE_KEY = 'chat-message';
const CHAT_MESSAGE_TTL = 3600 * 1 * 1000; // 1 hours
@Injectable()
export class ChatMessageCache {
@@ -20,7 +19,7 @@ export class ChatMessageCache {
const parsedMessage = SubmittedMessageSchema.parse(message);
const id = randomUUID();
await this.cache.set(`${CHAT_MESSAGE_KEY}:${id}`, parsedMessage, {
ttl: CHAT_MESSAGE_TTL,
ttl: '1h',
});
return id;
}

View File

@@ -6,7 +6,7 @@ import Sinon from 'sinon';
import { createModule } from '../../../__tests__/create-module';
import { Mockers } from '../../../__tests__/mocks';
import { JOB_SIGNAL } from '../../../base';
import { Due, JOB_SIGNAL } from '../../../base';
import { ConfigModule } from '../../../base/config';
import { ServerConfigModule } from '../../../core/config';
import { Models } from '../../../models';
@@ -160,7 +160,7 @@ test('should not index workspace if it is not updated in 180 days', async t => {
user,
workspaceId: workspace.id,
docId: workspace.id,
updatedAt: new Date(Date.now() - 180 * 24 * 60 * 60 * 1000 - 1),
updatedAt: Due.before('181d'),
});
const count = module.queue.count('indexer.indexWorkspace');

View File

@@ -1,6 +1,6 @@
import { Injectable, Logger } from '@nestjs/common';
import { Config, JOB_SIGNAL, JobQueue, OnJob } from '../../base';
import { Config, Due, JOB_SIGNAL, JobQueue, OnJob } from '../../base';
import { readAllDocIdsFromWorkspaceSnapshot } from '../../core/utils/blocksuite';
import { Models } from '../../models';
import { IndexerService } from './service';
@@ -182,8 +182,7 @@ export class IndexerJob {
// ignore 180 days not updated workspaces
if (
!snapshotMeta?.updatedAt ||
Date.now() - snapshotMeta.updatedAt.getTime() >
180 * 24 * 60 * 60 * 1000
snapshotMeta.updatedAt < Due.before('180d')
) {
continue;
}

View File

@@ -7,6 +7,7 @@ import { z } from 'zod';
import {
CryptoHelper,
Due,
EventBus,
InternalServerError,
InvalidLicenseToActivate,
@@ -337,7 +338,7 @@ export class LicenseService {
const licenses = await this.db.installedLicense.findMany({
where: {
validatedAt: {
lte: new Date(Date.now() - 1000 * 60 * 60 /* 1h */),
lte: Due.before('1h'),
},
},
});

View File

@@ -29,7 +29,7 @@ export class OAuthService {
async saveOAuthState(state: OAuthState) {
const token = randomUUID();
await this.cache.set(`${OAUTH_STATE_KEY}:${token}`, state, {
ttl: 3600 * 3 * 1000 /* 3 hours */,
ttl: '3h',
});
return token;

View File

@@ -26,9 +26,6 @@ import {
} from './utils';
import { decodeWithCharset } from './utils/encoding';
// cache for 30 minutes
const CACHE_TTL = 1000 * 60 * 30;
@Public()
@UseNamedGuard('selfhost')
@Controller('/api/worker')
@@ -98,7 +95,7 @@ export class WorkerController {
if (contentType?.startsWith('image/')) {
const buffer = Buffer.from(await response.arrayBuffer());
await this.cache.set(cachedUrl, buffer.toString('base64'), {
ttl: CACHE_TTL,
ttl: '30m',
});
const contentDisposition = response.headers.get('Content-Disposition');
return resp
@@ -118,7 +115,7 @@ export class WorkerController {
if (response.status >= 400 && response.status < 500) {
// rejected by server, cache a empty response
await this.cache.set(cachedUrl, Buffer.from([]).toString('base64'), {
ttl: CACHE_TTL,
ttl: '30m',
});
}
this.logger.error('Failed to fetch image', {
@@ -302,7 +299,7 @@ export class WorkerController {
responseSize: json.length,
});
await this.cache.set(cachedUrl, res, { ttl: CACHE_TTL });
await this.cache.set(cachedUrl, res, { ttl: '30m' });
return resp
.status(200)
.header({