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:
@@ -14,6 +14,7 @@ import {
|
||||
Logger,
|
||||
Inject,
|
||||
forwardRef,
|
||||
BadRequestException,
|
||||
} from '@nestjs/common';
|
||||
import { AuthGuard } from '@nestjs/passport';
|
||||
import { FileInterceptor } from '@nestjs/platform-express';
|
||||
@@ -22,7 +23,7 @@ import { ApplicationsService } from './applications.service';
|
||||
import { CreateApplicationDto, UpdateApplicationDto, ScaleResourcesDto } from './dto/application.dto';
|
||||
import { RolesGuard } from '../common/guards/roles.guard';
|
||||
import { Roles } from '../common/decorators/roles.decorator';
|
||||
import { UserRole } from '../common/enums';
|
||||
import { UserRole, DatabaseType } from '../common/enums';
|
||||
import { KubernetesService } from '../kubernetes/kubernetes.service';
|
||||
import { DeploymentsService } from '../deployments/deployments.service';
|
||||
|
||||
@@ -60,6 +61,38 @@ export class ApplicationsController {
|
||||
return this.applicationsService.uploadCode(id, req.user.id, file);
|
||||
}
|
||||
|
||||
@Post(':id/db-upload')
|
||||
@ApiOperation({ summary: 'Upload and restore a SQL dump into the application database' })
|
||||
@ApiConsumes('multipart/form-data')
|
||||
@UseInterceptors(FileInterceptor('file', {
|
||||
limits: { fileSize: 500 * 1024 * 1024 }, // 500MB for DB dumps
|
||||
}))
|
||||
async uploadDbDump(
|
||||
@Param('id') id: string,
|
||||
@Request() req: any,
|
||||
@UploadedFile() file: Express.Multer.File,
|
||||
) {
|
||||
if (!file) {
|
||||
throw new BadRequestException('No file uploaded');
|
||||
}
|
||||
|
||||
const isStaff = req.user.role === UserRole.ADMIN || req.user.role === UserRole.TECHNICAL;
|
||||
const app = await this.applicationsService.findOne(id, isStaff ? undefined : req.user.id);
|
||||
|
||||
if (app.databaseType === DatabaseType.NONE) {
|
||||
throw new BadRequestException('This application does not have a database configured');
|
||||
}
|
||||
|
||||
this.logger.log(`DB dump upload for ${app.name} — ${(file.size / 1024).toFixed(1)} KB`);
|
||||
const result = await this.kubernetesService.restoreDatabaseDump(app, file.buffer);
|
||||
|
||||
return {
|
||||
success: result.success,
|
||||
message: result.success ? 'Database restored successfully' : 'Database restore failed',
|
||||
logs: result.logs,
|
||||
};
|
||||
}
|
||||
|
||||
@Get()
|
||||
@ApiOperation({ summary: 'List my applications' })
|
||||
async findAll(@Request() req: any) {
|
||||
|
||||
Reference in New Issue
Block a user