69 lines
3.0 KiB
TypeScript
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 });
|
|
});
|
|
}
|