Files
helix-engage-server/src/config/setup-state.controller.ts
saridsa2 6adb3985cb
Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed
feat(config): ui-flags endpoint driven by HELIX_SETUP_MANAGED
Per-tenant flag that hides self-serve setup surfaces when the product team owns onboarding for a workspace. Set HELIX_SETUP_MANAGED=true on the sidecar env for that tenant; the frontend reads this endpoint at boot, hides the Settings nav + Setup banner, and blocks /settings/* routes. Setup-state APIs stay live so ops can still drive the wizard remotely.
2026-04-15 18:55:25 +05:30

73 lines
3.1 KiB
TypeScript

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',
};
}
}