feat: add Docker deployment, web installer, and local test environment
- Multi-stage Dockerfiles for API (NestJS) and Web (Next.js standalone) - docker-compose.prod.yml: full production stack (postgres, redis, keycloak, api, web) with optional Caddy/Let's Encrypt via --profile ssl - docker-compose.local.yml: identical local test stack, all ports exposed - docker/postgres/init.sql: auto-creates tos_app DB on first start - Caddyfile: reverse proxy for app domain + auth subdomain - install.sh: interactive installer (domain, SSL mode, secret generation) - NestJS SetupModule: @Public() endpoints for /setup/status, /setup/admin, /setup/branding, /setup/complete with setup-token guard - Web installer: 4-step flow (system check, admin creation, branding, complete) at /[locale]/setup/* with public middleware bypass - i18n: installer namespace added to de.json and en.json - CORS: x-setup-token header allowed in main.ts Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
67
apps/api/src/modules/setup/setup.controller.ts
Normal file
67
apps/api/src/modules/setup/setup.controller.ts
Normal file
@@ -0,0 +1,67 @@
|
||||
import { Controller, Get, Post, Put, Body, Headers } from '@nestjs/common';
|
||||
import { ApiTags, ApiOperation, ApiHeader, ApiResponse } from '@nestjs/swagger';
|
||||
import { Public } from '../../auth/decorators/public.decorator';
|
||||
import { SetupService } from './setup.service';
|
||||
import { CreateAdminDto } from './dto/create-admin.dto';
|
||||
import { SaveBrandingDto } from './dto/save-branding.dto';
|
||||
|
||||
@ApiTags('setup')
|
||||
@Public()
|
||||
@Controller('setup')
|
||||
export class SetupController {
|
||||
constructor(private readonly setupService: SetupService) {}
|
||||
|
||||
@Get('status')
|
||||
@ApiOperation({ summary: 'Get current setup status' })
|
||||
@ApiResponse({
|
||||
status: 200,
|
||||
description: 'Returns whether setup is completed and token is configured',
|
||||
schema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
completed: { type: 'boolean', example: false },
|
||||
tokenConfigured: { type: 'boolean', example: true },
|
||||
},
|
||||
},
|
||||
})
|
||||
getStatus() {
|
||||
return this.setupService.getStatus();
|
||||
}
|
||||
|
||||
@Post('admin')
|
||||
@ApiOperation({ summary: 'Create initial admin user in Keycloak and local DB' })
|
||||
@ApiHeader({ name: 'x-setup-token', required: true })
|
||||
@ApiResponse({ status: 201, description: 'Admin user created' })
|
||||
@ApiResponse({ status: 401, description: 'Invalid setup token' })
|
||||
@ApiResponse({ status: 403, description: 'Setup already completed' })
|
||||
@ApiResponse({ status: 409, description: 'User already exists in Keycloak' })
|
||||
createAdmin(
|
||||
@Body() dto: CreateAdminDto,
|
||||
@Headers('x-setup-token') token: string,
|
||||
) {
|
||||
return this.setupService.createAdmin(dto, token ?? '');
|
||||
}
|
||||
|
||||
@Put('branding')
|
||||
@ApiOperation({ summary: 'Save branding settings (app name, company, logo)' })
|
||||
@ApiHeader({ name: 'x-setup-token', required: true })
|
||||
@ApiResponse({ status: 200, description: 'Branding settings saved' })
|
||||
@ApiResponse({ status: 401, description: 'Invalid setup token' })
|
||||
@ApiResponse({ status: 403, description: 'Setup already completed' })
|
||||
saveBranding(
|
||||
@Body() dto: SaveBrandingDto,
|
||||
@Headers('x-setup-token') token: string,
|
||||
) {
|
||||
return this.setupService.saveBranding(dto, token ?? '');
|
||||
}
|
||||
|
||||
@Post('complete')
|
||||
@ApiOperation({ summary: 'Mark setup as completed' })
|
||||
@ApiHeader({ name: 'x-setup-token', required: true })
|
||||
@ApiResponse({ status: 201, description: 'Setup marked as completed' })
|
||||
@ApiResponse({ status: 401, description: 'Invalid setup token' })
|
||||
@ApiResponse({ status: 403, description: 'Setup already completed' })
|
||||
completeSetup(@Headers('x-setup-token') token: string) {
|
||||
return this.setupService.completeSetup(token ?? '');
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user