fix(03): resolve tenant context for module activation

Controller now falls back to user.tenantId from JWT when
req.tenantId is null (SUPER_ADMIN without x-tenant-id header).
Also added error display to admin modules page.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-06-19 14:38:37 +02:00
parent dad9a779df
commit b8ef870d1a
2 changed files with 16 additions and 5 deletions
@@ -46,7 +46,7 @@ export class ModuleRegistryController {
*/ */
@Get('active') @Get('active')
async findActive(@Req() req: Request) { async findActive(@Req() req: Request) {
const tenantId = (req as any).tenantId; const tenantId = (req as any).tenantId ?? (req as any).user?.tenantId;
if (!tenantId) { if (!tenantId) {
throw new ForbiddenException('No tenant context'); throw new ForbiddenException('No tenant context');
} }
@@ -66,7 +66,7 @@ export class ModuleRegistryController {
@Param('moduleId') moduleId: string, @Param('moduleId') moduleId: string,
@Req() req: Request, @Req() req: Request,
) { ) {
const tenantId = (req as any).tenantId; const tenantId = (req as any).tenantId ?? (req as any).user?.tenantId;
if (!tenantId) { if (!tenantId) {
throw new ForbiddenException('No tenant context'); throw new ForbiddenException('No tenant context');
} }
@@ -85,7 +85,7 @@ export class ModuleRegistryController {
@Param('moduleId') moduleId: string, @Param('moduleId') moduleId: string,
@Req() req: Request, @Req() req: Request,
) { ) {
const tenantId = (req as any).tenantId; const tenantId = (req as any).tenantId ?? (req as any).user?.tenantId;
if (!tenantId) { if (!tenantId) {
throw new ForbiddenException('No tenant context'); throw new ForbiddenException('No tenant context');
} }
@@ -32,6 +32,7 @@ export default function AdminModulesPage() {
const [activations, setActivations] = useState<Map<string, boolean>>(new Map()); const [activations, setActivations] = useState<Map<string, boolean>>(new Map());
const [loading, setLoading] = useState(true); const [loading, setLoading] = useState(true);
const [toggling, setToggling] = useState<string | null>(null); const [toggling, setToggling] = useState<string | null>(null);
const [error, setError] = useState<string | null>(null);
const isAdmin = currentUser?.role === 'ADMIN' || currentUser?.role === 'SUPER_ADMIN'; const isAdmin = currentUser?.role === 'ADMIN' || currentUser?.role === 'SUPER_ADMIN';
@@ -72,6 +73,7 @@ export default function AdminModulesPage() {
const toggleModule = async (moduleId: string, currentlyActive: boolean) => { const toggleModule = async (moduleId: string, currentlyActive: boolean) => {
setToggling(moduleId); setToggling(moduleId);
setError(null);
try { try {
const action = currentlyActive ? 'deactivate' : 'activate'; const action = currentlyActive ? 'deactivate' : 'activate';
const res = await fetch(`${API_URL}/modules/${moduleId}/${action}`, { const res = await fetch(`${API_URL}/modules/${moduleId}/${action}`, {
@@ -89,9 +91,12 @@ export default function AdminModulesPage() {
} }
return next; return next;
}); });
} else {
const body = await res.text().catch(() => '');
setError(`${res.status}: ${body}`);
} }
} catch { } catch (err) {
// silently fail setError(String(err));
} finally { } finally {
setToggling(null); setToggling(null);
} }
@@ -117,6 +122,12 @@ export default function AdminModulesPage() {
<p className="mt-1 text-sm text-muted-foreground">{t('description')}</p> <p className="mt-1 text-sm text-muted-foreground">{t('description')}</p>
</div> </div>
{error && (
<div className="rounded-md border border-destructive/50 bg-destructive/10 p-3 text-sm text-destructive">
{error}
</div>
)}
{loading ? ( {loading ? (
<p className="text-muted-foreground">{tCommon('loading')}</p> <p className="text-muted-foreground">{tCommon('loading')}</p>
) : modules.length === 0 ? ( ) : modules.length === 0 ? (