feat: database management — custom credentials, dump upload/restore
Backend: - Add dbUsername/dbPassword columns to Application entity - Add optional DB credential fields to CreateApplicationDto - Auto-generate dbPassword (crypto.randomBytes) and default dbUsername='appuser' when databaseType != 'none' on app creation - Store both username and password in K8s DB secret (was password-only) - Read DB_USER/POSTGRES_USER/MYSQL_USER from secretKeyRef instead of hardcoded - New restoreDatabaseDump() in KubernetesService: creates K8s Job with psql/mysql client to restore uploaded SQL dump, waits for completion, returns logs - New POST /applications/:id/db-upload endpoint with 500MB file limit Frontend: - Add dbUsername/dbPassword to Application and CreateApplicationDto types - Deploy page: show username/password fields when database is selected, with generate-random-password button and show/hide toggle - App detail page: new Database section with connection info (host, port, db name, username, password with copy-to-clipboard), SQL dump upload area with drag-and-drop, and restore output logs display Security: - Database remains ClusterIP only (no external exposure) - Credentials stored in K8s Secrets (base64-encoded) - Dump file uploaded as temporary K8s Secret, auto-cleaned after restore
This commit is contained in:
@@ -4,10 +4,11 @@ import { Repository } from 'typeorm';
|
||||
import { ConfigService } from '@nestjs/config';
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
import * as crypto from 'crypto';
|
||||
import { Application } from './entities/application.entity';
|
||||
import { CreateApplicationDto, UpdateApplicationDto } from './dto/application.dto';
|
||||
import { ClustersService } from '../clusters/clusters.service';
|
||||
import { UserRole } from '../common/enums';
|
||||
import { UserRole, DatabaseType } from '../common/enums';
|
||||
|
||||
@Injectable()
|
||||
export class ApplicationsService {
|
||||
@@ -66,11 +67,22 @@ export class ApplicationsService {
|
||||
this.logger.log(`Manual cluster assignment for app "${dto.name}" → cluster ${clusterId}`);
|
||||
}
|
||||
|
||||
// Generate database credentials if a database is requested
|
||||
let dbUsername: string | undefined;
|
||||
let dbPassword: string | undefined;
|
||||
if (dto.databaseType && dto.databaseType !== DatabaseType.NONE) {
|
||||
dbUsername = dto.dbUsername?.trim() || 'appuser';
|
||||
dbPassword = dto.dbPassword?.trim() || crypto.randomBytes(16).toString('hex');
|
||||
this.logger.log(`Generated DB credentials for app "${dto.name}" — user: ${dbUsername}`);
|
||||
}
|
||||
|
||||
const app = this.appsRepository.create({
|
||||
...dto,
|
||||
userId,
|
||||
clusterId,
|
||||
poolId,
|
||||
dbUsername,
|
||||
dbPassword,
|
||||
subdomain: `${dto.name}-${userId.split('-')[0]}`,
|
||||
});
|
||||
return this.appsRepository.save(app);
|
||||
|
||||
Reference in New Issue
Block a user