panels-origin/api/cost_control_http.ts
Cursor Agent aed2f4531b
Implement client IAM v2: roles per tenant, CRUD matrix, scope, and panel UI
- Add IAM v2 auth with role_id, is_owner, status checks, and API key without tenant_admin bypass
- Enforce CRUD permissions and project/warehouse scope across API routes
- Rewrite usuarios.vue with ribbon, tree, user/role management, and permission matrix
- Add dynamic menu, route guards, usePermissions/useScope composables
- Apply role templates on create; filter project docs by category; hide cost tab without permission
- Add iam_test unit tests; update bootstrap and SaaS tenant admin to use role_id

Co-authored-by: alberto.martinez <alberto.martinez@mrdev.mx>
2026-09-08 18:16:59 +00:00

64 lines
2.4 KiB
TypeScript

import type { Hono } from "hono";
import type { AuthUser } from "./auth.ts";
import { tenantScope } from "./auth.ts";
import { requireCoreAuth } from "./scope.ts";
import type { Db } from "./db.ts";
import { callCoreFn } from "./rpc.ts";
import { respondRpc, routeLabel } from "./http_errors.ts";
import { requirePermission } from "./permissions.ts";
import { denyUnlessProjectScope } from "./scope_enforcement.ts";
type App = Hono<{ Variables: { user: AuthUser; db: Db } }>;
function tid(c: { get: (k: "user") => AuthUser }): number {
return tenantScope(c.get("user")) ?? 0;
}
export function registerCostControlRoutes(app: App) {
const auth = [...requireCoreAuth, requirePermission("cost_control.view")] as const;
app.get("/v1/projects/:id/cost-control/summary", ...auth, async (c) => {
const projectId = Number(c.req.param("id"));
if (!await denyUnlessProjectScope(c, projectId)) return;
const db = c.get("db");
const env = await callCoreFn(db, "core.fn_cost_control_project_summary", {
tenant_id: tid(c),
project_id: projectId,
}, { route: routeLabel(c) });
return respondRpc(c, env);
});
app.get("/v1/projects/:id/cost-control/items", ...auth, async (c) => {
const projectId = Number(c.req.param("id"));
if (!await denyUnlessProjectScope(c, projectId)) return;
const db = c.get("db");
const env = await callCoreFn(db, "core.fn_cost_control_by_item", {
tenant_id: tid(c),
project_id: projectId,
}, { route: routeLabel(c) });
return respondRpc(c, env);
});
app.get("/v1/projects/:id/cost-control/chapters", ...auth, async (c) => {
const projectId = Number(c.req.param("id"));
if (!await denyUnlessProjectScope(c, projectId)) return;
const db = c.get("db");
const env = await callCoreFn(db, "core.fn_cost_control_by_chapter", {
tenant_id: tid(c),
project_id: projectId,
}, { route: routeLabel(c) });
return respondRpc(c, env);
});
app.get("/v1/projects/:id/cost-control/deviations", ...auth, async (c) => {
const projectId = Number(c.req.param("id"));
if (!await denyUnlessProjectScope(c, projectId)) return;
const db = c.get("db");
const env = await callCoreFn(db, "core.fn_cost_control_top_deviations", {
tenant_id: tid(c),
project_id: projectId,
limit: c.req.query("limit"),
}, { route: routeLabel(c) });
return respondRpc(c, env);
});
}