mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-02-04 08:38:34 +00:00
Compare commits
1 Commits
darksky/na
...
61/use-dur
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ba78a6bd45 |
@@ -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'),
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -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]),
|
||||
});
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 };
|
||||
})
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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');
|
||||
|
||||
@@ -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);
|
||||
},
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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',
|
||||
},
|
||||
});
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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',
|
||||
},
|
||||
});
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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`;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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, {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
},
|
||||
|
||||
@@ -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`);
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -78,7 +78,7 @@ export class CaptchaService {
|
||||
const challenge = await this.models.verificationToken.create(
|
||||
TokenType.Challenge,
|
||||
resource,
|
||||
5 * 60
|
||||
'5m'
|
||||
);
|
||||
|
||||
return {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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');
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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'),
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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({
|
||||
|
||||
Reference in New Issue
Block a user