add new lagin desktop

This commit is contained in:
2026-06-11 16:20:46 +03:30
parent ea0860d176
commit d3d61f5e5a
6 changed files with 223 additions and 0 deletions
@@ -0,0 +1,51 @@
import {
Controller,
Delete,
Get,
Post,
UploadedFile,
UseGuards,
UseInterceptors,
} from '@nestjs/common';
import { FileInterceptor } from '@nestjs/platform-express';
import { memoryStorage } from 'multer';
import { UserRole } from '../../entities/user.entity';
import { Roles } from '../auth/decorators/roles.decorator';
import { JwtAuthGuard } from '../auth/guards/jwt-auth.guard';
import { RolesGuard } from '../auth/guards/roles.guard';
import {
PlatformSettingsDto,
PlatformSettingsService,
} from './platform-settings.service';
@Controller('platform-settings')
export class PlatformSettingsController {
constructor(private readonly service: PlatformSettingsService) {}
@Get()
getSettings(): Promise<PlatformSettingsDto> {
return this.service.getSettings();
}
@Post('login-hero-image')
@UseGuards(JwtAuthGuard, RolesGuard)
@Roles(UserRole.ADMIN)
@UseInterceptors(
FileInterceptor('file', {
storage: memoryStorage(),
limits: { fileSize: 5 * 1024 * 1024 },
}),
)
uploadLoginHeroImage(
@UploadedFile() file: Express.Multer.File,
): Promise<PlatformSettingsDto> {
return this.service.uploadLoginHeroImage(file);
}
@Delete('login-hero-image')
@UseGuards(JwtAuthGuard, RolesGuard)
@Roles(UserRole.ADMIN)
removeLoginHeroImage(): Promise<PlatformSettingsDto> {
return this.service.removeLoginHeroImage();
}
}
@@ -0,0 +1,14 @@
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { PlatformSettingsEntity } from '../../entities/platform-settings.entity';
import { AuthModule } from '../auth/auth.module';
import { PlatformSettingsController } from './platform-settings.controller';
import { PlatformSettingsService } from './platform-settings.service';
@Module({
imports: [TypeOrmModule.forFeature([PlatformSettingsEntity]), AuthModule],
controllers: [PlatformSettingsController],
providers: [PlatformSettingsService],
exports: [PlatformSettingsService],
})
export class PlatformSettingsModule {}
@@ -0,0 +1,112 @@
import { BadRequestException, Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { randomUUID } from 'crypto';
import { existsSync, mkdirSync, unlinkSync } from 'fs';
import { extname, join } from 'path';
import { Repository } from 'typeorm';
import { PlatformSettingsEntity } from '../../entities/platform-settings.entity';
const ALLOWED_MIME = new Set([
'image/jpeg',
'image/png',
'image/webp',
'image/gif',
]);
const MAX_FILE_BYTES = 5 * 1024 * 1024;
const SETTINGS_ID = 1;
export type PlatformSettingsDto = {
loginHeroImageUrl: string | null;
};
@Injectable()
export class PlatformSettingsService {
private readonly uploadRoot: string;
constructor(
@InjectRepository(PlatformSettingsEntity)
private readonly repo: Repository<PlatformSettingsEntity>,
) {
this.uploadRoot = join(process.cwd(), process.env.UPLOAD_DIR ?? 'uploads');
}
async getSettings(): Promise<PlatformSettingsDto> {
const row = await this.ensureRow();
return this.toDto(row);
}
async uploadLoginHeroImage(
file: Express.Multer.File,
): Promise<PlatformSettingsDto> {
this.validateFile(file);
const row = await this.ensureRow();
const dir = join(this.uploadRoot, 'platform');
mkdirSync(dir, { recursive: true });
if (row.loginHeroImageUrl) {
this.deleteStoredFile(row.loginHeroImageUrl);
}
const ext = extname(file.originalname).toLowerCase() || '.jpg';
const filename = `login-hero-${randomUUID()}${ext}`;
const relativeUrl = `platform/${filename}`;
const absolutePath = join(this.uploadRoot, relativeUrl);
const { writeFileSync } = await import('fs');
writeFileSync(absolutePath, file.buffer);
row.loginHeroImageUrl = relativeUrl;
row.updatedAt = new Date();
await this.repo.save(row);
return this.toDto(row);
}
async removeLoginHeroImage(): Promise<PlatformSettingsDto> {
const row = await this.ensureRow();
if (row.loginHeroImageUrl) {
this.deleteStoredFile(row.loginHeroImageUrl);
row.loginHeroImageUrl = null;
row.updatedAt = new Date();
await this.repo.save(row);
}
return this.toDto(row);
}
private async ensureRow(): Promise<PlatformSettingsEntity> {
let row = await this.repo.findOne({ where: { id: SETTINGS_ID } });
if (!row) {
row = this.repo.create({ id: SETTINGS_ID, loginHeroImageUrl: null });
await this.repo.save(row);
}
return row;
}
private toDto(row: PlatformSettingsEntity): PlatformSettingsDto {
return {
loginHeroImageUrl: row.loginHeroImageUrl,
};
}
private validateFile(file: Express.Multer.File | undefined): void {
if (!file) {
throw new BadRequestException('Image file is required');
}
if (!ALLOWED_MIME.has(file.mimetype)) {
throw new BadRequestException('Only JPEG, PNG, WebP, and GIF images are allowed');
}
if (file.size > MAX_FILE_BYTES) {
throw new BadRequestException('Image must be at most 5 MB');
}
}
private deleteStoredFile(relativeUrl: string): void {
const absolutePath = join(this.uploadRoot, relativeUrl);
if (existsSync(absolutePath)) {
unlinkSync(absolutePath);
}
}
}