mirror of
https://github.com/docmost/docmost.git
synced 2026-05-07 22:53:08 +08:00
cleanup
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { Injectable, Logger } from '@nestjs/common';
|
||||
import { Interval } from '@nestjs/schedule';
|
||||
import { TokenService } from '../auth/services/token.service';
|
||||
import { UserSessionRepo } from '@docmost/db/repos/session/user-session.repo';
|
||||
import { EnvironmentService } from '../../integrations/environment/environment.service';
|
||||
@@ -10,8 +11,13 @@ import {
|
||||
} from '../../common/middlewares/audit-context.middleware';
|
||||
import * as Bowser from 'bowser';
|
||||
|
||||
const MAX_SESSIONS_PER_USER = 25;
|
||||
const RETENTION_DAYS = 7;
|
||||
|
||||
@Injectable()
|
||||
export class SessionService {
|
||||
private readonly logger = new Logger(SessionService.name);
|
||||
|
||||
constructor(
|
||||
private readonly tokenService: TokenService,
|
||||
private readonly userSessionRepo: UserSessionRepo,
|
||||
@@ -19,6 +25,17 @@ export class SessionService {
|
||||
private readonly cls: ClsService,
|
||||
) {}
|
||||
|
||||
@Interval('session-cleanup', 24 * 60 * 60 * 1000)
|
||||
async cleanupSessions() {
|
||||
try {
|
||||
await this.userSessionRepo.deleteStale(RETENTION_DAYS);
|
||||
await this.userSessionRepo.trimExcessSessions(MAX_SESSIONS_PER_USER);
|
||||
this.logger.debug('Session cleanup completed');
|
||||
} catch (err) {
|
||||
this.logger.error('Session cleanup failed', err);
|
||||
}
|
||||
}
|
||||
|
||||
async createSessionAndToken(user: User): Promise<string> {
|
||||
const auditContext = this.cls.get<AuditContext>(AUDIT_CONTEXT_KEY);
|
||||
const ipAddress = auditContext?.ipAddress ?? null;
|
||||
|
||||
@@ -27,17 +27,19 @@ export async function up(db: Kysely<any>): Promise<void> {
|
||||
)
|
||||
.execute();
|
||||
|
||||
await db.schema
|
||||
.createIndex('idx_user_sessions_user_workspace')
|
||||
.on('user_sessions')
|
||||
.columns(['user_id', 'workspace_id'])
|
||||
.execute();
|
||||
// Partial index for active session queries (list, validate)
|
||||
await sql`
|
||||
CREATE INDEX idx_user_sessions_active
|
||||
ON user_sessions (user_id, workspace_id, last_active_at DESC)
|
||||
WHERE revoked_at IS NULL
|
||||
`.execute(db);
|
||||
|
||||
await db.schema
|
||||
.createIndex('idx_user_sessions_expires_at')
|
||||
.on('user_sessions')
|
||||
.column('expires_at')
|
||||
.execute();
|
||||
// For session cleanup
|
||||
await sql`
|
||||
CREATE INDEX idx_user_sessions_cleanup
|
||||
ON user_sessions (revoked_at, expires_at)
|
||||
WHERE revoked_at IS NOT NULL OR expires_at < now()
|
||||
`.execute(db);
|
||||
}
|
||||
|
||||
export async function down(db: Kysely<any>): Promise<void> {
|
||||
|
||||
@@ -6,6 +6,7 @@ import { KyselyDB, KyselyTransaction } from '@docmost/db/types/kysely.types';
|
||||
import { dbOrTx } from '@docmost/db/utils';
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { InjectKysely } from 'nestjs-kysely';
|
||||
import { sql } from 'kysely';
|
||||
|
||||
@Injectable()
|
||||
export class UserSessionRepo {
|
||||
@@ -124,4 +125,38 @@ export class UserSessionRepo {
|
||||
.where('id', '!=', currentSessionId)
|
||||
.execute();
|
||||
}
|
||||
|
||||
async deleteStale(retentionDays: number): Promise<void> {
|
||||
const cutoff = new Date(Date.now() - retentionDays * 24 * 60 * 60 * 1000);
|
||||
await this.db
|
||||
.deleteFrom('userSessions')
|
||||
.where((eb) =>
|
||||
eb.or([
|
||||
eb('revokedAt', '<', cutoff),
|
||||
eb('expiresAt', '<', cutoff),
|
||||
]),
|
||||
)
|
||||
.execute();
|
||||
}
|
||||
|
||||
async trimExcessSessions(maxPerUser: number): Promise<void> {
|
||||
const overflowed = await this.db
|
||||
.selectFrom('userSessions')
|
||||
.select(['userId', 'workspaceId'])
|
||||
.groupBy(['userId', 'workspaceId'])
|
||||
.having(sql`COUNT(*)`, '>', maxPerUser)
|
||||
.execute();
|
||||
|
||||
for (const { userId, workspaceId } of overflowed) {
|
||||
await sql`
|
||||
DELETE FROM user_sessions
|
||||
WHERE id IN (
|
||||
SELECT id FROM user_sessions
|
||||
WHERE user_id = ${userId} AND workspace_id = ${workspaceId}
|
||||
ORDER BY last_active_at DESC
|
||||
OFFSET ${maxPerUser}
|
||||
)
|
||||
`.execute(this.db);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
Submodule apps/server/src/ee updated: 759e109708...02911b3b46
Reference in New Issue
Block a user