- 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>
68 lines
2.5 KiB
TypeScript
68 lines
2.5 KiB
TypeScript
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 ?? '');
|
|
}
|
|
}
|