mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-02-18 06:47:02 +08:00
Compare commits
1 Commits
v0.26.3-be
...
61/use-dur
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ba78a6bd45 |
@@ -4,6 +4,7 @@ import ava, { TestFn } from 'ava';
|
|||||||
import Sinon from 'sinon';
|
import Sinon from 'sinon';
|
||||||
import request from 'supertest';
|
import request from 'supertest';
|
||||||
|
|
||||||
|
import { Due } from '../../base';
|
||||||
import { AuthModule, CurrentUser, Public, Session } from '../../core/auth';
|
import { AuthModule, CurrentUser, Public, Session } from '../../core/auth';
|
||||||
import { AuthService } from '../../core/auth/service';
|
import { AuthService } from '../../core/auth/service';
|
||||||
import { Models } from '../../models';
|
import { Models } from '../../models';
|
||||||
@@ -125,7 +126,7 @@ test('should be able to refresh session if needed', async t => {
|
|||||||
sessionId,
|
sessionId,
|
||||||
},
|
},
|
||||||
data: {
|
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 test from 'ava';
|
||||||
import * as Sinon from 'sinon';
|
import * as Sinon from 'sinon';
|
||||||
|
|
||||||
|
import { Due } from '../../base';
|
||||||
import { DocStorageModule, PgWorkspaceDocStorageAdapter } from '../../core/doc';
|
import { DocStorageModule, PgWorkspaceDocStorageAdapter } from '../../core/doc';
|
||||||
import { DocStorageOptions } from '../../core/doc/options';
|
import { DocStorageOptions } from '../../core/doc/options';
|
||||||
import { DocRecord } from '../../core/doc/storage';
|
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
|
// @ts-expect-error private method
|
||||||
Sinon.stub(adapter, 'lastDocHistory').resolves({
|
Sinon.stub(adapter, 'lastDocHistory').resolves({
|
||||||
timestamp: new Date(timestamp.getTime() - 1000 * 60 * 20),
|
timestamp: Due.before('20m', timestamp),
|
||||||
state: Buffer.from([0, 1]),
|
state: Buffer.from([0, 1]),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import { Config } from '../../base/config';
|
|||||||
import { SessionModel } from '../../models/session';
|
import { SessionModel } from '../../models/session';
|
||||||
import { UserModel } from '../../models/user';
|
import { UserModel } from '../../models/user';
|
||||||
import { createTestingModule, type TestingModule } from '../utils';
|
import { createTestingModule, type TestingModule } from '../utils';
|
||||||
|
import { Due } from '../../base/utils';
|
||||||
|
|
||||||
interface Context {
|
interface Context {
|
||||||
config: Config;
|
config: Config;
|
||||||
@@ -137,9 +138,7 @@ test('should not refresh userSession when expires time not hit ttr', async t =>
|
|||||||
let newExpiresAt =
|
let newExpiresAt =
|
||||||
await t.context.session.refreshUserSessionIfNeeded(userSession);
|
await t.context.session.refreshUserSessionIfNeeded(userSession);
|
||||||
t.is(newExpiresAt, undefined);
|
t.is(newExpiresAt, undefined);
|
||||||
userSession.expiresAt = new Date(
|
userSession.expiresAt = Due.before(t.context.config.auth.session.ttr);
|
||||||
userSession.expiresAt!.getTime() - t.context.config.auth.session.ttr * 1000
|
|
||||||
);
|
|
||||||
newExpiresAt =
|
newExpiresAt =
|
||||||
await t.context.session.refreshUserSessionIfNeeded(userSession);
|
await t.context.session.refreshUserSessionIfNeeded(userSession);
|
||||||
t.is(newExpiresAt, undefined);
|
t.is(newExpiresAt, undefined);
|
||||||
@@ -154,9 +153,9 @@ test('should not refresh userSession when expires time hit ttr', async t => {
|
|||||||
user.id,
|
user.id,
|
||||||
session.id
|
session.id
|
||||||
);
|
);
|
||||||
const ttr = t.context.config.auth.session.ttr * 2;
|
|
||||||
userSession.expiresAt = new Date(
|
userSession.expiresAt = new Date(
|
||||||
userSession.expiresAt!.getTime() - ttr * 1000
|
userSession.expiresAt!.getTime() -
|
||||||
|
Due.ms(t.context.config.auth.session.ttr) * 2
|
||||||
);
|
);
|
||||||
const newExpiresAt =
|
const newExpiresAt =
|
||||||
await t.context.session.refreshUserSessionIfNeeded(userSession);
|
await t.context.session.refreshUserSessionIfNeeded(userSession);
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import Sinon from 'sinon';
|
|||||||
import Stripe from 'stripe';
|
import Stripe from 'stripe';
|
||||||
|
|
||||||
import { AppModule } from '../../app.module';
|
import { AppModule } from '../../app.module';
|
||||||
import { EventBus } from '../../base';
|
import { Due, EventBus } from '../../base';
|
||||||
import { ConfigFactory, ConfigModule } from '../../base/config';
|
import { ConfigFactory, ConfigModule } from '../../base/config';
|
||||||
import { CurrentUser } from '../../core/auth';
|
import { CurrentUser } from '../../core/auth';
|
||||||
import { AuthService } from '../../core/auth/service';
|
import { AuthService } from '../../core/auth/service';
|
||||||
@@ -129,8 +129,8 @@ const sub: Stripe.Subscription = {
|
|||||||
object: 'subscription',
|
object: 'subscription',
|
||||||
cancel_at_period_end: false,
|
cancel_at_period_end: false,
|
||||||
canceled_at: null,
|
canceled_at: null,
|
||||||
current_period_end: unixNow() + 60 * 60 * 24 * 30,
|
current_period_end: Due.after('30d').getTime() / 1000,
|
||||||
current_period_start: unixNow() - 60 * 60 * 24 * 1,
|
current_period_start: Due.before('1d').getTime() / 1000,
|
||||||
// @ts-expect-error stub
|
// @ts-expect-error stub
|
||||||
customer: {
|
customer: {
|
||||||
id: 'cus_1',
|
id: 'cus_1',
|
||||||
@@ -914,7 +914,7 @@ const subscriptionSchedule: Stripe.SubscriptionSchedule = {
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
start_date: unixNow(),
|
start_date: unixNow(),
|
||||||
end_date: unixNow() + 30 * 24 * 60 * 60,
|
end_date: Due.after('30d').getTime() / 1000,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
items: [
|
items: [
|
||||||
@@ -924,7 +924,7 @@ const subscriptionSchedule: Stripe.SubscriptionSchedule = {
|
|||||||
quantity: 1,
|
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?.recurring, SubscriptionRecurring.Monthly);
|
||||||
t.is(subInDB?.status, SubscriptionStatus.Active);
|
t.is(subInDB?.status, SubscriptionStatus.Active);
|
||||||
t.is(subInDB?.stripeSubscriptionId, null);
|
t.is(subInDB?.stripeSubscriptionId, null);
|
||||||
t.is(
|
t.is(subInDB?.end?.toDateString(), Due.after('30d').toDateString());
|
||||||
subInDB?.end?.toDateString(),
|
|
||||||
new Date(Date.now() + 30 * 24 * 60 * 60 * 1000).toDateString()
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should be able to accumulate onetime payment subscription period', async t => {
|
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
|
// 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 => {
|
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
|
// add 365 days from now
|
||||||
t.is(
|
t.is(subInDB?.end?.toDateString(), Due.after('1y').toDateString());
|
||||||
subInDB?.end?.toDateString(),
|
|
||||||
new Date(Date.now() + 365 * 24 * 60 * 60 * 1000).toDateString()
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should not accumulate onetime payment subscription period for redeemed invoices', async t => {
|
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 },
|
where: { targetId: u1.id },
|
||||||
});
|
});
|
||||||
|
|
||||||
t.is(
|
t.is(subInDB?.end?.toDateString(), Due.after('1y').toDateString());
|
||||||
subInDB?.end?.toDateString(),
|
|
||||||
new Date(Date.now() + 365 * 24 * 60 * 60 * 1000).toDateString()
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// TEAM
|
// TEAM
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
import Redis from 'ioredis';
|
import Redis from 'ioredis';
|
||||||
|
import { type Duration, Due } from '../utils';
|
||||||
|
|
||||||
export interface CacheSetOptions {
|
export interface CacheSetOptions {
|
||||||
/**
|
/**
|
||||||
* in milliseconds
|
* in milliseconds
|
||||||
*/
|
*/
|
||||||
ttl?: number;
|
ttl?: Duration;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class CacheProvider {
|
export class CacheProvider {
|
||||||
@@ -30,7 +31,7 @@ export class CacheProvider {
|
|||||||
): Promise<boolean> {
|
): Promise<boolean> {
|
||||||
if (opts.ttl) {
|
if (opts.ttl) {
|
||||||
return this.redis
|
return this.redis
|
||||||
.set(key, JSON.stringify(value), 'PX', opts.ttl)
|
.set(key, JSON.stringify(value), 'PX', Due.ms(opts.ttl))
|
||||||
.then(() => true)
|
.then(() => true)
|
||||||
.catch(() => false);
|
.catch(() => false);
|
||||||
}
|
}
|
||||||
@@ -56,7 +57,7 @@ export class CacheProvider {
|
|||||||
): Promise<boolean> {
|
): Promise<boolean> {
|
||||||
if (opts.ttl) {
|
if (opts.ttl) {
|
||||||
return this.redis
|
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)
|
.then(v => !!v)
|
||||||
.catch(() => false);
|
.catch(() => false);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -103,8 +103,8 @@ test('should override correctly', t => {
|
|||||||
// keyed config
|
// keyed config
|
||||||
// 'session.ttl', 'session.ttr'
|
// 'session.ttl', 'session.ttr'
|
||||||
session: {
|
session: {
|
||||||
ttl: 2000,
|
ttl: '1M',
|
||||||
ttr: 1000,
|
ttr: '1d',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
storages: {
|
storages: {
|
||||||
@@ -131,7 +131,7 @@ test('should override correctly', t => {
|
|||||||
},
|
},
|
||||||
allowSignup: true,
|
allowSignup: true,
|
||||||
session: {
|
session: {
|
||||||
ttl: 3000,
|
ttl: '2M',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
storages: {
|
storages: {
|
||||||
@@ -159,8 +159,8 @@ test('should override correctly', t => {
|
|||||||
|
|
||||||
// right merged to left
|
// right merged to left
|
||||||
t.deepEqual(config.auth.session, {
|
t.deepEqual(config.auth.session, {
|
||||||
ttl: 3000,
|
ttl: '2M',
|
||||||
ttr: 1000,
|
ttr: '1d',
|
||||||
});
|
});
|
||||||
|
|
||||||
// right covered left
|
// right covered left
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import { type QueueOptions } from 'bullmq';
|
|||||||
|
|
||||||
import { Config } from '../../config';
|
import { Config } from '../../config';
|
||||||
import { QueueRedis } from '../../redis';
|
import { QueueRedis } from '../../redis';
|
||||||
|
import { Due } from '../../utils';
|
||||||
import { Queue, QUEUES } from './def';
|
import { Queue, QUEUES } from './def';
|
||||||
import { JobExecutor } from './executor';
|
import { JobExecutor } from './executor';
|
||||||
import { JobQueue } from './queue';
|
import { JobQueue } from './queue';
|
||||||
@@ -40,7 +41,7 @@ export class JobModule {
|
|||||||
...QUEUES.map(name => {
|
...QUEUES.map(name => {
|
||||||
if (name === Queue.NIGHTLY_JOB) {
|
if (name === Queue.NIGHTLY_JOB) {
|
||||||
// avoid nightly jobs been run multiple times
|
// avoid nightly jobs been run multiple times
|
||||||
return { name, removeOnComplete: { age: 1000 * 60 * 60 } };
|
return { name, removeOnComplete: { age: Due.ms('1m') } };
|
||||||
}
|
}
|
||||||
return { name };
|
return { name };
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ import {
|
|||||||
PutObjectMetadata,
|
PutObjectMetadata,
|
||||||
StorageProvider,
|
StorageProvider,
|
||||||
} from './provider';
|
} from './provider';
|
||||||
import { autoMetadata, SIGNED_URL_EXPIRED, toBuffer } from './utils';
|
import { autoMetadata, SIGNED_URL_EXPIRED_SEC, toBuffer } from './utils';
|
||||||
|
|
||||||
export interface S3StorageConfig extends S3ClientConfig {
|
export interface S3StorageConfig extends S3ClientConfig {
|
||||||
usePresignedURL?: {
|
usePresignedURL?: {
|
||||||
@@ -138,7 +138,7 @@ export class S3StorageProvider implements StorageProvider {
|
|||||||
Bucket: this.bucket,
|
Bucket: this.bucket,
|
||||||
Key: key,
|
Key: key,
|
||||||
}),
|
}),
|
||||||
{ expiresIn: SIGNED_URL_EXPIRED }
|
{ expiresIn: SIGNED_URL_EXPIRED_SEC }
|
||||||
);
|
);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { crc32 } from '@node-rs/crc32';
|
|||||||
import { getStreamAsBuffer } from 'get-stream';
|
import { getStreamAsBuffer } from 'get-stream';
|
||||||
|
|
||||||
import { getMime } from '../../../native';
|
import { getMime } from '../../../native';
|
||||||
|
import { Due } from '../../utils';
|
||||||
import { BlobInputType, PutObjectMetadata } from './provider';
|
import { BlobInputType, PutObjectMetadata } from './provider';
|
||||||
|
|
||||||
export async function toBuffer(input: BlobInputType): Promise<Buffer> {
|
export async function toBuffer(input: BlobInputType): Promise<Buffer> {
|
||||||
@@ -43,4 +44,4 @@ export function autoMetadata(
|
|||||||
return metadata;
|
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;
|
return input;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type Duration = string | DurationInput;
|
||||||
|
|
||||||
export const Due = {
|
export const Due = {
|
||||||
ms: (dueStr: string | DurationInput) => {
|
ms: (duration: Duration) => {
|
||||||
const input = typeof dueStr === 'string' ? parse(dueStr) : dueStr;
|
const input = typeof duration === 'string' ? parse(duration) : duration;
|
||||||
return Object.entries(input).reduce((duration, [unit, val]) => {
|
return Object.entries(input).reduce((duration, [unit, val]) => {
|
||||||
return duration + UnitToSecMap[unit as DurationUnit] * (val || 0) * 1000;
|
return duration + UnitToSecMap[unit as DurationUnit] * (val || 0) * 1000;
|
||||||
}, 0);
|
}, 0);
|
||||||
},
|
},
|
||||||
s: (dueStr: string | DurationInput) => {
|
s: (duration: Duration) => {
|
||||||
const input = typeof dueStr === 'string' ? parse(dueStr) : dueStr;
|
const input = typeof duration === 'string' ? parse(duration) : duration;
|
||||||
return Object.entries(input).reduce((duration, [unit, val]) => {
|
return Object.entries(input).reduce((duration, [unit, val]) => {
|
||||||
return duration + UnitToSecMap[unit as DurationUnit] * (val || 0);
|
return duration + UnitToSecMap[unit as DurationUnit] * (val || 0);
|
||||||
}, 0);
|
}, 0);
|
||||||
},
|
},
|
||||||
parse,
|
parse,
|
||||||
after: (dueStr: string | number | DurationInput, date?: Date) => {
|
after: (duration: Duration, date?: Date) => {
|
||||||
const timestamp = typeof dueStr === 'number' ? dueStr : Due.ms(dueStr);
|
const timestamp = Due.ms(duration);
|
||||||
return new Date((date?.getTime() ?? Date.now()) + timestamp);
|
return new Date((date?.getTime() ?? Date.now()) + timestamp);
|
||||||
},
|
},
|
||||||
before: (dueStr: string | number | DurationInput, date?: Date) => {
|
before: (duration: Duration, date?: Date) => {
|
||||||
const timestamp = typeof dueStr === 'number' ? dueStr : Due.ms(dueStr);
|
const timestamp = Due.ms(duration);
|
||||||
return new Date((date?.getTime() ?? Date.now()) - timestamp);
|
return new Date((date?.getTime() ?? Date.now()) - timestamp);
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
export const OneKB = 1024;
|
export const OneKB = 1024;
|
||||||
export const OneMB = OneKB * OneKB;
|
export const OneMB = OneKB * OneKB;
|
||||||
export const OneGB = OneKB * OneMB;
|
export const OneGB = OneKB * OneMB;
|
||||||
export const OneDay = 1000 * 60 * 60 * 24;
|
|
||||||
|
|||||||
@@ -4,8 +4,8 @@ import { defineModuleConfig } from '../../base';
|
|||||||
|
|
||||||
export interface AuthConfig {
|
export interface AuthConfig {
|
||||||
session: {
|
session: {
|
||||||
ttl: number;
|
ttl: string;
|
||||||
ttr: number;
|
ttr: string;
|
||||||
};
|
};
|
||||||
allowSignup: boolean;
|
allowSignup: boolean;
|
||||||
requireEmailDomainVerification: boolean;
|
requireEmailDomainVerification: boolean;
|
||||||
@@ -60,10 +60,10 @@ defineModuleConfig('auth', {
|
|||||||
},
|
},
|
||||||
'session.ttl': {
|
'session.ttl': {
|
||||||
desc: 'Application auth expiration time in seconds.',
|
desc: 'Application auth expiration time in seconds.',
|
||||||
default: 60 * 60 * 24 * 15, // 15 days
|
default: '15d',
|
||||||
},
|
},
|
||||||
'session.ttr': {
|
'session.ttr': {
|
||||||
desc: 'Application auth time to refresh in seconds.',
|
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 });
|
throw new WrongSignInCredentials({ email });
|
||||||
}
|
}
|
||||||
|
|
||||||
const ttlInSec = 30 * 60;
|
const ttl = '30m';
|
||||||
const token = await this.models.verificationToken.create(
|
const token = await this.models.verificationToken.create(
|
||||||
TokenType.SignIn,
|
TokenType.SignIn,
|
||||||
email,
|
email,
|
||||||
ttlInSec
|
ttl
|
||||||
);
|
);
|
||||||
|
|
||||||
const otp = this.crypto.otp();
|
const otp = this.crypto.otp();
|
||||||
// TODO(@forehalo): this is a temporary solution, we should not rely on cache to store the otp
|
// TODO(@forehalo): this is a temporary solution, we should not rely on cache to store the otp
|
||||||
const cacheKey = OTP_CACHE_KEY(otp);
|
const cacheKey = OTP_CACHE_KEY(otp);
|
||||||
await this.cache.set(
|
await this.cache.set(cacheKey, { token, clientNonce }, { ttl });
|
||||||
cacheKey,
|
|
||||||
{ token, clientNonce },
|
|
||||||
{ ttl: ttlInSec * 1000 }
|
|
||||||
);
|
|
||||||
|
|
||||||
const magicLink = this.url.link(callbackUrl, {
|
const magicLink = this.url.link(callbackUrl, {
|
||||||
token: otp,
|
token: otp,
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { Injectable, OnApplicationBootstrap } from '@nestjs/common';
|
|||||||
import type { CookieOptions, Request, Response } from 'express';
|
import type { CookieOptions, Request, Response } from 'express';
|
||||||
import { assign, pick } from 'lodash-es';
|
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 { Models, type User, type UserSession } from '../../models';
|
||||||
import { FeatureService } from '../features';
|
import { FeatureService } from '../features';
|
||||||
import { Mailer } from '../mail/mailer';
|
import { Mailer } from '../mail/mailer';
|
||||||
@@ -128,7 +128,7 @@ export class AuthService implements OnApplicationBootstrap {
|
|||||||
return await this.models.session.findUserSessionsBySessionId(sessionId);
|
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(
|
return await this.models.session.createOrRefreshUserSession(
|
||||||
userId,
|
userId,
|
||||||
sessionId,
|
sessionId,
|
||||||
@@ -157,7 +157,7 @@ export class AuthService implements OnApplicationBootstrap {
|
|||||||
async refreshUserSessionIfNeeded(
|
async refreshUserSessionIfNeeded(
|
||||||
res: Response,
|
res: Response,
|
||||||
userSession: UserSession,
|
userSession: UserSession,
|
||||||
ttr?: number
|
ttr?: Duration
|
||||||
): Promise<boolean> {
|
): Promise<boolean> {
|
||||||
const newExpiresAt = await this.models.session.refreshUserSessionIfNeeded(
|
const newExpiresAt = await this.models.session.refreshUserSessionIfNeeded(
|
||||||
userSession,
|
userSession,
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { chunk } from 'lodash-es';
|
|||||||
import {
|
import {
|
||||||
DocHistoryNotFound,
|
DocHistoryNotFound,
|
||||||
DocNotFound,
|
DocNotFound,
|
||||||
|
Due,
|
||||||
EventBus,
|
EventBus,
|
||||||
FailedToSaveUpdates,
|
FailedToSaveUpdates,
|
||||||
FailedToUpsertSnapshot,
|
FailedToUpsertSnapshot,
|
||||||
@@ -251,7 +252,8 @@ export class PgWorkspaceDocStorageAdapter extends DocStorageAdapter {
|
|||||||
force ||
|
force ||
|
||||||
// last history created before interval in configs
|
// last history created before interval in configs
|
||||||
lastHistoryTimestamp <
|
lastHistoryTimestamp <
|
||||||
snapshot.timestamp - this.options.historyMinInterval(snapshot.spaceId)
|
snapshot.timestamp -
|
||||||
|
Due.ms(this.options.historyMinInterval(snapshot.spaceId))
|
||||||
) {
|
) {
|
||||||
shouldCreateHistory = true;
|
shouldCreateHistory = true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ declare global {
|
|||||||
interface AppConfigSchema {
|
interface AppConfigSchema {
|
||||||
doc: {
|
doc: {
|
||||||
history: {
|
history: {
|
||||||
interval: number;
|
interval: string;
|
||||||
};
|
};
|
||||||
experimental: {
|
experimental: {
|
||||||
yocto: boolean;
|
yocto: boolean;
|
||||||
@@ -20,6 +20,6 @@ defineModuleConfig('doc', {
|
|||||||
},
|
},
|
||||||
'history.interval': {
|
'history.interval': {
|
||||||
desc: 'The minimum time interval in milliseconds of creating a new history snapshot when doc get updated.',
|
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 { PgWorkspaceDocStorageAdapter } from './adapters/workspace';
|
||||||
import { type DocDiff, type DocRecord } from './storage';
|
import { type DocDiff, type DocRecord } from './storage';
|
||||||
|
|
||||||
const DOC_CONTENT_CACHE_7_DAYS = 7 * 24 * 60 * 60 * 1000;
|
|
||||||
|
|
||||||
export interface WorkspaceDocInfo {
|
export interface WorkspaceDocInfo {
|
||||||
id: string;
|
id: string;
|
||||||
name: string;
|
name: string;
|
||||||
@@ -90,7 +88,7 @@ export abstract class DocReader {
|
|||||||
const content = await this.getDocContentWithoutCache(workspaceId, docId);
|
const content = await this.getDocContentWithoutCache(workspaceId, docId);
|
||||||
if (content) {
|
if (content) {
|
||||||
await this.cache.set(cacheKey, content, {
|
await this.cache.set(cacheKey, content, {
|
||||||
ttl: DOC_CONTENT_CACHE_7_DAYS,
|
ttl: '7d',
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
return content;
|
return content;
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import {
|
|||||||
createTestingModule,
|
createTestingModule,
|
||||||
type TestingModule,
|
type TestingModule,
|
||||||
} from '../../../__tests__/utils';
|
} from '../../../__tests__/utils';
|
||||||
import { NotificationNotFound } from '../../../base';
|
import { Due, NotificationNotFound } from '../../../base';
|
||||||
import {
|
import {
|
||||||
DocMode,
|
DocMode,
|
||||||
MentionNotificationBody,
|
MentionNotificationBody,
|
||||||
@@ -381,7 +381,7 @@ test('should clean expired notifications', async t => {
|
|||||||
// wait for 100 days
|
// wait for 100 days
|
||||||
mock.timers.enable({
|
mock.timers.enable({
|
||||||
apis: ['Date'],
|
apis: ['Date'],
|
||||||
now: Date.now() + 1000 * 60 * 60 * 24 * 100,
|
now: Due.after('100d'),
|
||||||
});
|
});
|
||||||
await t.context.models.notification.cleanExpiredNotifications();
|
await t.context.models.notification.cleanExpiredNotifications();
|
||||||
count = await notificationService.countByUserId(member.id);
|
count = await notificationService.countByUserId(member.id);
|
||||||
@@ -390,7 +390,7 @@ test('should clean expired notifications', async t => {
|
|||||||
// wait for 1 year
|
// wait for 1 year
|
||||||
mock.timers.enable({
|
mock.timers.enable({
|
||||||
apis: ['Date'],
|
apis: ['Date'],
|
||||||
now: Date.now() + 1000 * 60 * 60 * 24 * 365,
|
now: Due.after('1y'),
|
||||||
});
|
});
|
||||||
await t.context.models.notification.cleanExpiredNotifications();
|
await t.context.models.notification.cleanExpiredNotifications();
|
||||||
count = await notificationService.countByUserId(member.id);
|
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'];
|
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 {
|
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(
|
const allowed = await this.cache.setnx(
|
||||||
`fixingOwner:${workspaceId}:${docId}`,
|
`fixingOwner:${workspaceId}:${docId}`,
|
||||||
1,
|
1,
|
||||||
// TODO(@forehalo): we definitely need a timer helper
|
{ ttl: '1d' }
|
||||||
{ ttl: 1000 * 60 * 60 * 24 }
|
|
||||||
);
|
);
|
||||||
|
|
||||||
// fixed by other instance
|
// fixed by other instance
|
||||||
|
|||||||
@@ -174,13 +174,11 @@ export class InviteResult {
|
|||||||
error?: object;
|
error?: object;
|
||||||
}
|
}
|
||||||
|
|
||||||
const Day = 24 * 60 * 60 * 1000;
|
|
||||||
|
|
||||||
export enum WorkspaceInviteLinkExpireTime {
|
export enum WorkspaceInviteLinkExpireTime {
|
||||||
OneDay = Day,
|
OneDay = '1d',
|
||||||
ThreeDays = 3 * Day,
|
ThreeDays = '3d',
|
||||||
OneWeek = 7 * Day,
|
OneWeek = '1w',
|
||||||
OneMonth = 30 * Day,
|
OneMonth = '1M',
|
||||||
}
|
}
|
||||||
|
|
||||||
registerEnumType(WorkspaceInviteLinkExpireTime, {
|
registerEnumType(WorkspaceInviteLinkExpireTime, {
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { mock } from 'node:test';
|
|||||||
import ava, { TestFn } from 'ava';
|
import ava, { TestFn } from 'ava';
|
||||||
|
|
||||||
import { createTestingModule, type TestingModule } from '../../__tests__/utils';
|
import { createTestingModule, type TestingModule } from '../../__tests__/utils';
|
||||||
|
import { Due } from '../../base';
|
||||||
import { Config } from '../../base/config';
|
import { Config } from '../../base/config';
|
||||||
import {
|
import {
|
||||||
DocMode,
|
DocMode,
|
||||||
@@ -259,7 +260,7 @@ test('should clean expired notifications', async t => {
|
|||||||
// wait for 1 year
|
// wait for 1 year
|
||||||
mock.timers.enable({
|
mock.timers.enable({
|
||||||
apis: ['Date'],
|
apis: ['Date'],
|
||||||
now: Date.now() + 1000 * 60 * 60 * 24 * 365,
|
now: Due.after('1y'),
|
||||||
});
|
});
|
||||||
count = await t.context.models.notification.cleanExpiredNotifications();
|
count = await t.context.models.notification.cleanExpiredNotifications();
|
||||||
t.is(count, 1);
|
t.is(count, 1);
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
|
|
||||||
import { OneDay, OneGB, OneMB } from '../../base';
|
import { Due, OneGB, OneMB } from '../../base';
|
||||||
|
|
||||||
const UserPlanQuotaConfig = z.object({
|
const UserPlanQuotaConfig = z.object({
|
||||||
// quota name
|
// quota name
|
||||||
@@ -104,7 +104,7 @@ export const FeatureConfigs: {
|
|||||||
blobLimit: 10 * OneMB,
|
blobLimit: 10 * OneMB,
|
||||||
businessBlobLimit: 100 * OneMB,
|
businessBlobLimit: 100 * OneMB,
|
||||||
storageQuota: 10 * OneGB,
|
storageQuota: 10 * OneGB,
|
||||||
historyPeriod: 7 * OneDay,
|
historyPeriod: Due.ms('7d'),
|
||||||
memberLimit: 3,
|
memberLimit: 3,
|
||||||
copilotActionLimit: 10,
|
copilotActionLimit: 10,
|
||||||
},
|
},
|
||||||
@@ -116,7 +116,7 @@ export const FeatureConfigs: {
|
|||||||
name: 'Pro',
|
name: 'Pro',
|
||||||
blobLimit: 100 * OneMB,
|
blobLimit: 100 * OneMB,
|
||||||
storageQuota: 100 * OneGB,
|
storageQuota: 100 * OneGB,
|
||||||
historyPeriod: 30 * OneDay,
|
historyPeriod: Due.ms('30d'),
|
||||||
memberLimit: 10,
|
memberLimit: 10,
|
||||||
copilotActionLimit: 10,
|
copilotActionLimit: 10,
|
||||||
},
|
},
|
||||||
@@ -128,7 +128,7 @@ export const FeatureConfigs: {
|
|||||||
name: 'Lifetime Pro',
|
name: 'Lifetime Pro',
|
||||||
blobLimit: 100 * OneMB,
|
blobLimit: 100 * OneMB,
|
||||||
storageQuota: 1024 * OneGB,
|
storageQuota: 1024 * OneGB,
|
||||||
historyPeriod: 30 * OneDay,
|
historyPeriod: Due.ms('30d'),
|
||||||
memberLimit: 10,
|
memberLimit: 10,
|
||||||
copilotActionLimit: 10,
|
copilotActionLimit: 10,
|
||||||
},
|
},
|
||||||
@@ -141,7 +141,7 @@ export const FeatureConfigs: {
|
|||||||
blobLimit: 500 * OneMB,
|
blobLimit: 500 * OneMB,
|
||||||
storageQuota: 100 * OneGB,
|
storageQuota: 100 * OneGB,
|
||||||
seatQuota: 20 * OneGB,
|
seatQuota: 20 * OneGB,
|
||||||
historyPeriod: 30 * OneDay,
|
historyPeriod: Due.ms('30d'),
|
||||||
memberLimit: 1,
|
memberLimit: 1,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import {
|
|||||||
} from '@prisma/client';
|
} from '@prisma/client';
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
|
|
||||||
import { PaginationInput } from '../base';
|
import { Due, PaginationInput } from '../base';
|
||||||
import { BaseModel } from './base';
|
import { BaseModel } from './base';
|
||||||
import { DocMode } from './common';
|
import { DocMode } from './common';
|
||||||
|
|
||||||
@@ -15,8 +15,6 @@ export { NotificationLevel, NotificationType };
|
|||||||
export type { Notification };
|
export type { Notification };
|
||||||
|
|
||||||
// #region input
|
// #region input
|
||||||
|
|
||||||
export const ONE_YEAR = 1000 * 60 * 60 * 24 * 365;
|
|
||||||
const IdSchema = z.string().trim().min(1).max(100);
|
const IdSchema = z.string().trim().min(1).max(100);
|
||||||
|
|
||||||
export const BaseNotificationCreateSchema = z.object({
|
export const BaseNotificationCreateSchema = z.object({
|
||||||
@@ -237,7 +235,7 @@ export class NotificationModel extends BaseModel {
|
|||||||
async cleanExpiredNotifications() {
|
async cleanExpiredNotifications() {
|
||||||
const { count } = await this.db.notification.deleteMany({
|
const { count } = await this.db.notification.deleteMany({
|
||||||
// delete notifications that are older than one year
|
// 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) {
|
if (count > 0) {
|
||||||
this.logger.log(`Deleted ${count} expired notifications`);
|
this.logger.log(`Deleted ${count} expired notifications`);
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import {
|
|||||||
type UserSession,
|
type UserSession,
|
||||||
} from '@prisma/client';
|
} from '@prisma/client';
|
||||||
|
|
||||||
import { Config } from '../base';
|
import { Config, Due, Duration } from '../base';
|
||||||
import { BaseModel } from './base';
|
import { BaseModel } from './base';
|
||||||
|
|
||||||
export type { Session, UserSession };
|
export type { Session, UserSession };
|
||||||
@@ -46,7 +46,7 @@ export class SessionModel extends BaseModel {
|
|||||||
async createOrRefreshUserSession(
|
async createOrRefreshUserSession(
|
||||||
userId: string,
|
userId: string,
|
||||||
sessionId?: string,
|
sessionId?: string,
|
||||||
ttl = this.config.auth.session.ttl
|
ttl: Duration = this.config.auth.session.ttl
|
||||||
) {
|
) {
|
||||||
// check whether given session is valid
|
// check whether given session is valid
|
||||||
if (sessionId) {
|
if (sessionId) {
|
||||||
@@ -66,7 +66,7 @@ export class SessionModel extends BaseModel {
|
|||||||
sessionId = session.id;
|
sessionId = session.id;
|
||||||
}
|
}
|
||||||
|
|
||||||
const expiresAt = new Date(Date.now() + ttl * 1000);
|
const expiresAt = Due.after(ttl);
|
||||||
return await this.db.userSession.upsert({
|
return await this.db.userSession.upsert({
|
||||||
where: {
|
where: {
|
||||||
sessionId_userId: {
|
sessionId_userId: {
|
||||||
@@ -87,19 +87,17 @@ export class SessionModel extends BaseModel {
|
|||||||
|
|
||||||
async refreshUserSessionIfNeeded(
|
async refreshUserSessionIfNeeded(
|
||||||
userSession: UserSession,
|
userSession: UserSession,
|
||||||
ttr = this.config.auth.session.ttr
|
ttr: Duration = this.config.auth.session.ttr
|
||||||
): Promise<Date | undefined> {
|
): Promise<Date | undefined> {
|
||||||
if (
|
if (
|
||||||
userSession.expiresAt &&
|
userSession.expiresAt &&
|
||||||
userSession.expiresAt.getTime() - Date.now() > ttr * 1000
|
Due.before(ttr, userSession.expiresAt) > new Date()
|
||||||
) {
|
) {
|
||||||
// no need to refresh
|
// no need to refresh
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const newExpiresAt = new Date(
|
const newExpiresAt = Due.after(this.config.auth.session.ttl);
|
||||||
Date.now() + this.config.auth.session.ttl * 1000
|
|
||||||
);
|
|
||||||
await this.db.userSession.update({
|
await this.db.userSession.update({
|
||||||
where: {
|
where: {
|
||||||
id: userSession.id,
|
id: userSession.id,
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { randomUUID } from 'node:crypto';
|
|||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable } from '@nestjs/common';
|
||||||
import { type VerificationToken } from '@prisma/client';
|
import { type VerificationToken } from '@prisma/client';
|
||||||
|
|
||||||
|
import { Due, Duration } from '../base';
|
||||||
import { CryptoHelper } from '../base/helpers';
|
import { CryptoHelper } from '../base/helpers';
|
||||||
import { BaseModel } from './base';
|
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)
|
* create token by type and credential (optional) with ttl in seconds (default 30 minutes)
|
||||||
*/
|
*/
|
||||||
async create(
|
async create(type: TokenType, credential?: string, ttl: Duration = '30m') {
|
||||||
type: TokenType,
|
|
||||||
credential?: string,
|
|
||||||
ttlInSec: number = 30 * 60
|
|
||||||
) {
|
|
||||||
const plaintextToken = randomUUID();
|
const plaintextToken = randomUUID();
|
||||||
const { token } = await this.db.verificationToken.create({
|
const { token } = await this.db.verificationToken.create({
|
||||||
data: {
|
data: {
|
||||||
type,
|
type,
|
||||||
token: plaintextToken,
|
token: plaintextToken,
|
||||||
credential,
|
credential,
|
||||||
expiresAt: new Date(Date.now() + ttlInSec * 1000),
|
expiresAt: Due.after(ttl),
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
return this.crypto.encrypt(token);
|
return this.crypto.encrypt(token);
|
||||||
|
|||||||
@@ -78,7 +78,7 @@ export class CaptchaService {
|
|||||||
const challenge = await this.models.verificationToken.create(
|
const challenge = await this.models.verificationToken.create(
|
||||||
TokenType.Challenge,
|
TokenType.Challenge,
|
||||||
resource,
|
resource,
|
||||||
5 * 60
|
'5m'
|
||||||
);
|
);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import { SessionCache } from '../../base';
|
|||||||
import { SubmittedMessage, SubmittedMessageSchema } from './types';
|
import { SubmittedMessage, SubmittedMessageSchema } from './types';
|
||||||
|
|
||||||
const CHAT_MESSAGE_KEY = 'chat-message';
|
const CHAT_MESSAGE_KEY = 'chat-message';
|
||||||
const CHAT_MESSAGE_TTL = 3600 * 1 * 1000; // 1 hours
|
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class ChatMessageCache {
|
export class ChatMessageCache {
|
||||||
@@ -20,7 +19,7 @@ export class ChatMessageCache {
|
|||||||
const parsedMessage = SubmittedMessageSchema.parse(message);
|
const parsedMessage = SubmittedMessageSchema.parse(message);
|
||||||
const id = randomUUID();
|
const id = randomUUID();
|
||||||
await this.cache.set(`${CHAT_MESSAGE_KEY}:${id}`, parsedMessage, {
|
await this.cache.set(`${CHAT_MESSAGE_KEY}:${id}`, parsedMessage, {
|
||||||
ttl: CHAT_MESSAGE_TTL,
|
ttl: '1h',
|
||||||
});
|
});
|
||||||
return id;
|
return id;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import Sinon from 'sinon';
|
|||||||
|
|
||||||
import { createModule } from '../../../__tests__/create-module';
|
import { createModule } from '../../../__tests__/create-module';
|
||||||
import { Mockers } from '../../../__tests__/mocks';
|
import { Mockers } from '../../../__tests__/mocks';
|
||||||
import { JOB_SIGNAL } from '../../../base';
|
import { Due, JOB_SIGNAL } from '../../../base';
|
||||||
import { ConfigModule } from '../../../base/config';
|
import { ConfigModule } from '../../../base/config';
|
||||||
import { ServerConfigModule } from '../../../core/config';
|
import { ServerConfigModule } from '../../../core/config';
|
||||||
import { Models } from '../../../models';
|
import { Models } from '../../../models';
|
||||||
@@ -160,7 +160,7 @@ test('should not index workspace if it is not updated in 180 days', async t => {
|
|||||||
user,
|
user,
|
||||||
workspaceId: workspace.id,
|
workspaceId: workspace.id,
|
||||||
docId: 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');
|
const count = module.queue.count('indexer.indexWorkspace');
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { Injectable, Logger } from '@nestjs/common';
|
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 { readAllDocIdsFromWorkspaceSnapshot } from '../../core/utils/blocksuite';
|
||||||
import { Models } from '../../models';
|
import { Models } from '../../models';
|
||||||
import { IndexerService } from './service';
|
import { IndexerService } from './service';
|
||||||
@@ -182,8 +182,7 @@ export class IndexerJob {
|
|||||||
// ignore 180 days not updated workspaces
|
// ignore 180 days not updated workspaces
|
||||||
if (
|
if (
|
||||||
!snapshotMeta?.updatedAt ||
|
!snapshotMeta?.updatedAt ||
|
||||||
Date.now() - snapshotMeta.updatedAt.getTime() >
|
snapshotMeta.updatedAt < Due.before('180d')
|
||||||
180 * 24 * 60 * 60 * 1000
|
|
||||||
) {
|
) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import { z } from 'zod';
|
|||||||
|
|
||||||
import {
|
import {
|
||||||
CryptoHelper,
|
CryptoHelper,
|
||||||
|
Due,
|
||||||
EventBus,
|
EventBus,
|
||||||
InternalServerError,
|
InternalServerError,
|
||||||
InvalidLicenseToActivate,
|
InvalidLicenseToActivate,
|
||||||
@@ -337,7 +338,7 @@ export class LicenseService {
|
|||||||
const licenses = await this.db.installedLicense.findMany({
|
const licenses = await this.db.installedLicense.findMany({
|
||||||
where: {
|
where: {
|
||||||
validatedAt: {
|
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) {
|
async saveOAuthState(state: OAuthState) {
|
||||||
const token = randomUUID();
|
const token = randomUUID();
|
||||||
await this.cache.set(`${OAUTH_STATE_KEY}:${token}`, state, {
|
await this.cache.set(`${OAUTH_STATE_KEY}:${token}`, state, {
|
||||||
ttl: 3600 * 3 * 1000 /* 3 hours */,
|
ttl: '3h',
|
||||||
});
|
});
|
||||||
|
|
||||||
return token;
|
return token;
|
||||||
|
|||||||
@@ -26,9 +26,6 @@ import {
|
|||||||
} from './utils';
|
} from './utils';
|
||||||
import { decodeWithCharset } from './utils/encoding';
|
import { decodeWithCharset } from './utils/encoding';
|
||||||
|
|
||||||
// cache for 30 minutes
|
|
||||||
const CACHE_TTL = 1000 * 60 * 30;
|
|
||||||
|
|
||||||
@Public()
|
@Public()
|
||||||
@UseNamedGuard('selfhost')
|
@UseNamedGuard('selfhost')
|
||||||
@Controller('/api/worker')
|
@Controller('/api/worker')
|
||||||
@@ -98,7 +95,7 @@ export class WorkerController {
|
|||||||
if (contentType?.startsWith('image/')) {
|
if (contentType?.startsWith('image/')) {
|
||||||
const buffer = Buffer.from(await response.arrayBuffer());
|
const buffer = Buffer.from(await response.arrayBuffer());
|
||||||
await this.cache.set(cachedUrl, buffer.toString('base64'), {
|
await this.cache.set(cachedUrl, buffer.toString('base64'), {
|
||||||
ttl: CACHE_TTL,
|
ttl: '30m',
|
||||||
});
|
});
|
||||||
const contentDisposition = response.headers.get('Content-Disposition');
|
const contentDisposition = response.headers.get('Content-Disposition');
|
||||||
return resp
|
return resp
|
||||||
@@ -118,7 +115,7 @@ export class WorkerController {
|
|||||||
if (response.status >= 400 && response.status < 500) {
|
if (response.status >= 400 && response.status < 500) {
|
||||||
// rejected by server, cache a empty response
|
// rejected by server, cache a empty response
|
||||||
await this.cache.set(cachedUrl, Buffer.from([]).toString('base64'), {
|
await this.cache.set(cachedUrl, Buffer.from([]).toString('base64'), {
|
||||||
ttl: CACHE_TTL,
|
ttl: '30m',
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
this.logger.error('Failed to fetch image', {
|
this.logger.error('Failed to fetch image', {
|
||||||
@@ -302,7 +299,7 @@ export class WorkerController {
|
|||||||
responseSize: json.length,
|
responseSize: json.length,
|
||||||
});
|
});
|
||||||
|
|
||||||
await this.cache.set(cachedUrl, res, { ttl: CACHE_TTL });
|
await this.cache.set(cachedUrl, res, { ttl: '30m' });
|
||||||
return resp
|
return resp
|
||||||
.status(200)
|
.status(200)
|
||||||
.header({
|
.header({
|
||||||
|
|||||||
Reference in New Issue
Block a user