maestro/src/bridge/users-api.ts
oss-sync 454d6f957b
Some checks failed
CI / build-and-test (push) Has been cancelled
sync: update from private repo (caeb95c)
2026-06-09 12:59:57 +00:00

69 lines
3.0 KiB
TypeScript

import express, { type Application, type Request, type Response, type RequestHandler } from 'express';
import type { Repository } from '../db/repository.js';
import { requireAuth } from './auth.js';
const passthrough: RequestHandler = (_req, _res, next) => next();
/**
* /api/users/me endpoints.
*
* When `authActive` is true, routes are gated by `requireAuth` and rely on
* `req.user.id` being populated by passport. When auth is disabled (tests or
* standalone mode), the guard is skipped and callers are expected to inject
* `req.user` via their own middleware (e.g. the test harness).
*/
export function mountUsersApi(app: Application, repo: Repository, authActive = true): void {
const guard = authActive ? requireAuth : passthrough;
// Viewer's orgs for the visibility ('org' scope) picker: Gitea orgs (cached
// at OAuth callback) + local orgs (membership). Returning both here means
// every picker (CreateTaskDialog / Schedules / DetailPanel / Preferences)
// lists local orgs without any client change.
app.get('/api/users/me/orgs', guard, (req: Request, res: Response) => {
const user = req.user as Express.User | undefined;
if (!user) {
// Defensive: with authActive=false and no injected user, return 401-shaped error.
res.status(401).json({ error: 'Unauthorized' });
return;
}
const gitea = repo.listUserGiteaOrgs(user.id);
const local = repo.listUserLocalOrgs(user.id).map(o => ({ orgId: o.orgId, orgName: o.name, fetchedAt: '' }));
res.json({ orgs: [...gitea, ...local] });
});
// Update viewer's per-user preferences (currently just default visibility).
app.patch('/api/users/me/preferences', guard, express.json(), (req: Request, res: Response) => {
const user = req.user as Express.User | undefined;
if (!user) {
res.status(401).json({ error: 'Unauthorized' });
return;
}
const body = (req.body ?? {}) as {
defaultVisibility?: unknown;
defaultVisibilityOrgId?: unknown;
};
const { defaultVisibility, defaultVisibilityOrgId } = body;
if (defaultVisibility !== undefined && defaultVisibility !== null &&
!['private', 'org', 'public'].includes(defaultVisibility as string)) {
res.status(400).json({ error: 'invalid defaultVisibility' });
return;
}
if (defaultVisibility === 'org') {
const scopeId = typeof defaultVisibilityOrgId === 'string' ? defaultVisibilityOrgId : '';
if (!scopeId) {
res.status(400).json({ error: 'default_visibility_org_id is required when defaultVisibility is "org"' });
return;
}
if (!user.orgIds.includes(scopeId)) {
res.status(400).json({ error: 'default_visibility_org_id must be one of your orgs' });
return;
}
}
repo.updateUser(user.id, {
defaultVisibility: (defaultVisibility as 'private' | 'org' | 'public' | undefined) ?? undefined,
defaultVisibilityOrgId: (defaultVisibilityOrgId as string | null | undefined) ?? null,
});
res.json({ ok: true });
});
}