Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions src/modules/audit/services/audit-cleanup.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { Repository, LessThan } from 'typeorm';
import { ConnectionAudit } from '../entities/connection-audit.entity';
import { FileAudit } from '../entities/file-audit.entity';
import { AlarmAudit } from '../entities/alarm-audit.entity';
import { AuditSettingsService } from '../../settings/services/audit-settings.service';
import { GeneralSettingsService } from '../../settings/services/general-settings.service';

/**
* 审计日志自动清理服务
Expand All @@ -23,13 +23,14 @@ export class AuditCleanupService {
private fileAuditRepository: Repository<FileAudit>,
@InjectRepository(AlarmAudit)
private alarmAuditRepository: Repository<AlarmAudit>,
private readonly auditSettingsService: AuditSettingsService,
private readonly generalSettingsService: GeneralSettingsService,
) {}

@Cron('0 0 * * *')
async handleCleanupExpiredAudits() {
try {
const retentionDays = await this.auditSettingsService.getRetentionDays();
const retentionDays =
await this.generalSettingsService.getAuditRetentionDays();

if (retentionDays <= 0) {
return;
Expand Down
6 changes: 6 additions & 0 deletions src/modules/settings/dto/general-settings.dto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ export class GeneralSettingsDto {
watermarkEnabled: boolean;
defaultLanguage: string;
jwtExpiryDays: number;
auditRetentionDays: number;
site: SiteSettingsDto;
webauthn: WebAuthnSettingsDto;
}
Expand All @@ -60,6 +61,11 @@ export class UpdateGeneralSettingsDto {
@Min(1)
jwtExpiryDays?: number;

@IsOptional()
@IsInt()
@Min(0)
auditRetentionDays?: number;

@IsOptional()
@ValidateNested()
@Type(() => SiteSettingsDto)
Expand Down
66 changes: 65 additions & 1 deletion src/modules/settings/general-settings.service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ const DEFAULT_SETTINGS = {
watermarkEnabled: true,
defaultLanguage: 'en-US',
jwtExpiryDays: 30,
auditRetentionDays: 0,
site: { frontendUrl: '', backendUrl: '' },
webauthn: { enabled: true, rpName: 'RustDesk Console' },
};
Expand Down Expand Up @@ -70,7 +71,7 @@ describe('GeneralSettingsService', () => {
...DEFAULT_SETTINGS,
watermarkEnabled: false,
});
await expect(repository.count()).resolves.toBe(7);
await expect(repository.count()).resolves.toBe(8);
});

it('persists jwtExpiryDays and reads it back', async () => {
Expand Down Expand Up @@ -104,6 +105,37 @@ describe('GeneralSettingsService', () => {
await expect(service.getJwtExpiryDays()).resolves.toBe(30);
});

it('persists auditRetentionDays and reads it back', async () => {
await service.updateSettings({
watermarkEnabled: true,
auditRetentionDays: 30,
});
await expect(service.getSettings()).resolves.toMatchObject({
auditRetentionDays: 30,
});
await expect(service.getAuditRetentionDays()).resolves.toBe(30);
});

it('falls back to default auditRetentionDays for invalid values', async () => {
await repository.save([
repository.create({
key: 'general.auditRetentionDays',
value: 'not-a-number',
category: 'general',
}),
]);
await expect(service.getAuditRetentionDays()).resolves.toBe(0);

await repository.save([
repository.create({
key: 'general.auditRetentionDays',
value: '-5',
category: 'general',
}),
]);
await expect(service.getAuditRetentionDays()).resolves.toBe(0);
});

it('persists defaultLanguage and validates the format', async () => {
await service.updateSettings({
watermarkEnabled: true,
Expand Down Expand Up @@ -227,6 +259,38 @@ describe('general settings HTTP contract', () => {
await expect(validate(invalidString)).resolves.not.toHaveLength(0);
});

it('validates auditRetentionDays must be a non-negative integer', async () => {
const valid = plainToInstance(UpdateGeneralSettingsDto, {
watermarkEnabled: true,
auditRetentionDays: 30,
});
await expect(validate(valid)).resolves.toHaveLength(0);

const validZero = plainToInstance(UpdateGeneralSettingsDto, {
watermarkEnabled: true,
auditRetentionDays: 0,
});
await expect(validate(validZero)).resolves.toHaveLength(0);

const invalidNegative = plainToInstance(UpdateGeneralSettingsDto, {
watermarkEnabled: true,
auditRetentionDays: -1,
});
await expect(validate(invalidNegative)).resolves.not.toHaveLength(0);

const invalidFloat = plainToInstance(UpdateGeneralSettingsDto, {
watermarkEnabled: true,
auditRetentionDays: 3.5,
});
await expect(validate(invalidFloat)).resolves.not.toHaveLength(0);

const invalidString = plainToInstance(UpdateGeneralSettingsDto, {
watermarkEnabled: true,
auditRetentionDays: '30',
});
await expect(validate(invalidString)).resolves.not.toHaveLength(0);
});

it('restricts general reads and updates to administrators', () => {
const readHandler = Object.getOwnPropertyDescriptor(
GeneralSettingsController.prototype,
Expand Down
72 changes: 0 additions & 72 deletions src/modules/settings/services/audit-settings.service.ts

This file was deleted.

24 changes: 24 additions & 0 deletions src/modules/settings/services/general-settings.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ const CATEGORY = 'general';
const WATERMARK_ENABLED_KEY = 'general.watermarkEnabled';
const DEFAULT_LANGUAGE_KEY = 'general.defaultLanguage';
const JWT_EXPIRY_DAYS_KEY = 'general.jwtExpiryDays';
const AUDIT_RETENTION_DAYS_KEY = 'general.auditRetentionDays';
const SITE_FRONTEND_URL_KEY = 'general.siteFrontendUrl';
const SITE_BACKEND_URL_KEY = 'general.siteBackendUrl';
const WEBAUTHN_ENABLED_KEY = 'general.webauthnEnabled';
Expand All @@ -22,12 +23,14 @@ const DEFAULT_BACKEND_URL = '';
const DEFAULT_RP_NAME = 'RustDesk Console';
const DEFAULT_LANGUAGE = 'en-US';
const DEFAULT_JWT_EXPIRY_DAYS = 30;
const DEFAULT_AUDIT_RETENTION_DAYS = 0;
const FALLBACK_URL = 'http://localhost:3000';

const ALL_KEYS = [
WATERMARK_ENABLED_KEY,
DEFAULT_LANGUAGE_KEY,
JWT_EXPIRY_DAYS_KEY,
AUDIT_RETENTION_DAYS_KEY,
SITE_FRONTEND_URL_KEY,
SITE_BACKEND_URL_KEY,
WEBAUTHN_ENABLED_KEY,
Expand All @@ -51,6 +54,9 @@ export class GeneralSettingsService {
),
defaultLanguage: values.get(DEFAULT_LANGUAGE_KEY) ?? DEFAULT_LANGUAGE,
jwtExpiryDays: this.readJwtExpiryDays(values.get(JWT_EXPIRY_DAYS_KEY)),
auditRetentionDays: this.readAuditRetentionDays(
values.get(AUDIT_RETENTION_DAYS_KEY),
),
site: {
frontendUrl: values.get(SITE_FRONTEND_URL_KEY) ?? DEFAULT_FRONTEND_URL,
backendUrl: values.get(SITE_BACKEND_URL_KEY) ?? DEFAULT_BACKEND_URL,
Expand Down Expand Up @@ -130,6 +136,15 @@ export class GeneralSettingsService {
return this.readJwtExpiryDays(values.get(JWT_EXPIRY_DAYS_KEY));
}

/**
* 获取审计日志保留天数(供 AuditCleanupService 消费)
* 默认 0 表示不自动清理,可通过 settings/general 接口修改
*/
async getAuditRetentionDays(): Promise<number> {
const values = await this.readValues([AUDIT_RETENTION_DAYS_KEY]);
return this.readAuditRetentionDays(values.get(AUDIT_RETENTION_DAYS_KEY));
}

async updateSettings(
dto: UpdateGeneralSettingsDto,
): Promise<GeneralSettingsDto> {
Expand All @@ -138,6 +153,8 @@ export class GeneralSettingsService {
watermarkEnabled: dto.watermarkEnabled,
defaultLanguage: dto.defaultLanguage ?? current.defaultLanguage,
jwtExpiryDays: dto.jwtExpiryDays ?? current.jwtExpiryDays,
auditRetentionDays:
dto.auditRetentionDays ?? current.auditRetentionDays,
site: {
frontendUrl: dto.site?.frontendUrl ?? current.site.frontendUrl,
backendUrl: dto.site?.backendUrl ?? current.site.backendUrl,
Expand All @@ -152,6 +169,7 @@ export class GeneralSettingsService {
[WATERMARK_ENABLED_KEY, String(merged.watermarkEnabled)],
[DEFAULT_LANGUAGE_KEY, merged.defaultLanguage ?? DEFAULT_LANGUAGE],
[JWT_EXPIRY_DAYS_KEY, String(merged.jwtExpiryDays)],
[AUDIT_RETENTION_DAYS_KEY, String(merged.auditRetentionDays)],
[SITE_FRONTEND_URL_KEY, merged.site.frontendUrl ?? ''],
[SITE_BACKEND_URL_KEY, merged.site.backendUrl ?? ''],
[WEBAUTHN_ENABLED_KEY, String(merged.webauthn.enabled)],
Expand Down Expand Up @@ -201,4 +219,10 @@ export class GeneralSettingsService {
if (Number.isNaN(parsed) || parsed < 1) return DEFAULT_JWT_EXPIRY_DAYS;
return parsed;
}

private readAuditRetentionDays(value: string | undefined): number {
const parsed = parseInt(value ?? '', 10);
if (Number.isNaN(parsed) || parsed < 0) return DEFAULT_AUDIT_RETENTION_DAYS;
return parsed;
}
}
13 changes: 4 additions & 9 deletions src/modules/settings/settings.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,19 @@ import { TypeOrmModule } from '@nestjs/typeorm';
import { SystemSetting } from './entities/system-setting.entity';
import { SettingsController } from './settings.controller';
import { SmtpSettingsService } from './services/smtp-settings.service';
import { AuditSettingsService } from './services/audit-settings.service';
import { GeneralSettingsController } from './general-settings.controller';
import { FrontendSettingsController } from './frontend-settings.controller';
import { GeneralSettingsService } from './services/general-settings.service';

/**
* 系统设置模块
* 管理系统配置,包括 SMTP 配置、审计日志保留策略等
* 管理系统配置,包括 SMTP 配置、通用设置等
*
* 使用通用 SystemSetting 表存储各类设置项
*
* 导出服务:
* - SmtpSettingsService(供 EmailModule 等其他模块使用)
* - AuditSettingsService(供 AuditModule 等其他模块使用)
* - GeneralSettingsService(供 AuditModule / AuthModule 等其他模块使用)
*/
@Module({
imports: [TypeOrmModule.forFeature([SystemSetting])],
Expand All @@ -25,11 +24,7 @@ import { GeneralSettingsService } from './services/general-settings.service';
GeneralSettingsController,
FrontendSettingsController,
],
providers: [
SmtpSettingsService,
AuditSettingsService,
GeneralSettingsService,
],
exports: [SmtpSettingsService, AuditSettingsService, GeneralSettingsService],
providers: [SmtpSettingsService, GeneralSettingsService],
exports: [SmtpSettingsService, GeneralSettingsService],
})
export class SettingsModule {}