import { Body, Controller, Get, Logger, Param, Post, Put } from '@nestjs/common'; import { SetupStateService } from './setup-state.service'; import type { SetupStepName } from './setup-state.defaults'; // Public endpoint family for the onboarding wizard. Mounted under /api/config // alongside theme/widget. No auth guard yet — matches existing convention with // ThemeController. To be tightened when the staff portal admin auth is in place. // // GET /api/config/setup-state full state + isWizardRequired // PUT /api/config/setup-state/steps/:step { completed: bool, completedBy?: string } // POST /api/config/setup-state/dismiss dismiss the wizard for this workspace // POST /api/config/setup-state/reset reset all steps to incomplete (admin) @Controller('api/config') export class SetupStateController { private readonly logger = new Logger(SetupStateController.name); constructor(private readonly setupState: SetupStateService) {} @Get('setup-state') async getState() { // Use the checked variant so the platform workspace probe runs // before we serialize. Catches workspace changes (DB resets, // re-onboards) on the very first frontend GET. const state = await this.setupState.getStateChecked(); return { ...state, wizardRequired: this.setupState.isWizardRequired(), }; } @Put('setup-state/steps/:step') updateStep( @Param('step') step: SetupStepName, @Body() body: { completed: boolean; completedBy?: string }, ) { const updated = body.completed ? this.setupState.markStepCompleted(step, body.completedBy ?? null) : this.setupState.markStepIncomplete(step); // Mirror GET shape — include `wizardRequired` so the frontend // doesn't see a state object missing the field and re-render // into an inconsistent shape. return { ...updated, wizardRequired: this.setupState.isWizardRequired() }; } @Post('setup-state/dismiss') dismiss() { const updated = this.setupState.dismissWizard(); return { ...updated, wizardRequired: this.setupState.isWizardRequired() }; } @Post('setup-state/reset') reset() { const updated = this.setupState.resetState(); return { ...updated, wizardRequired: this.setupState.isWizardRequired() }; } // UI-level flags the frontend reads at app boot to tailor which admin // surfaces are available. Driven by sidecar env vars so each workspace // can be configured independently without touching the frontend build. // // setupManaged=true means "the product team handles setup for this // workspace" — hide the Settings nav, routes, and the resume-setup // banner. The wizard + setup-state APIs stay functional for ops use // (a support engineer can still PUT /steps/:step or hit the routes // directly); only the end-user admin UI is hidden. @Get('ui-flags') uiFlags() { return { setupManaged: process.env.HELIX_SETUP_MANAGED === 'true', }; } }