mirror of
https://origin.cursor.com/mrdevmx/panels.git
synced 2026-10-09 18:03:18 +00:00
<!-- CURSOR_AGENT_PR_BODY_BEGIN --> ## Validación de endpoints y SPs Se probaron todos los endpoints nuevos (gastos, almacén, control presupuestal, IAM) y sus RPCs asociados. ### Bugs corregidos en migraciones/SQL 1. **IAM grants en schema core** — `iam-004e` intentaba `GRANT` sobre `core` con rol `iam_owner` (sin permiso). Los grants cruzados se movieron a `core-027-iam-rpc-cross-grants.sql`. 2. **`_cost_settings` ambiguo** — columnas `budget_warn_pct` etc. colisionaban con `RETURNS TABLE` en PL/pgSQL, rompiendo `fn_expense_create` y control presupuestal. Corregido en `core-028-fix-cost-settings-ambiguous.sql`. ### Tests añadidos - `api/cost_modules_test.ts` — CRUD RPC gastos, flujo almacén completo, control presupuestal, IAM permisos - `scripts/crud-smoke-test.sh` — smoke HTTP de todos los endpoints nuevos ### Resultados - `deno test cost_modules_test.ts` — 5/5 OK - `./scripts/crud-smoke-test.sh` — todos los checks OK (gastos CRUD, IVA, cost-settings, almacén, transferencias, cost-control, IAM) <!-- CURSOR_AGENT_PR_BODY_END --> <div><a href="https://cursor.com/agents/bc-eeec3b5c-f789-43e8-a353-0755e4706377?cursor_ref=pr_footer&cursor_cta=open_in_web"><picture><source media="(prefers-color-scheme: dark)" srcset="https://cursor.com/assets/images/open-in-web-dark.png"><source media="(prefers-color-scheme: light)" srcset="https://cursor.com/assets/images/open-in-web-light.png"><img alt="Open in Web" width="114" height="28" src="https://cursor.com/assets/images/open-in-web-dark.png"></picture></a> <a href="https://cursor.com/background-agent?bcId=bc-eeec3b5c-f789-43e8-a353-0755e4706377&cursor_ref=pr_footer&cursor_cta=open_in_cursor"><picture><source media="(prefers-color-scheme: dark)" srcset="https://cursor.com/assets/images/open-in-cursor-dark.png"><source media="(prefers-color-scheme: light)" srcset="https://cursor.com/assets/images/open-in-cursor-light.png"><img alt="Open in Cursor" width="131" height="28" src="https://cursor.com/assets/images/open-in-cursor-dark.png"></picture></a> </div>
65 lines
2.9 KiB
TypeScript
65 lines
2.9 KiB
TypeScript
import type { Hono } from "hono";
|
|
import type { AuthUser } from "./auth.ts";
|
|
import { requireAuth, tenantScope } from "./auth.ts";
|
|
import type { Db } from "./db.ts";
|
|
import { respondRpc } from "./http_errors.ts";
|
|
import { requirePermission, callIamFn } from "./permissions.ts";
|
|
|
|
type App = Hono<{ Variables: { user: AuthUser; db: Db } }>;
|
|
|
|
export function registerIamRoutes(app: App) {
|
|
app.get("/v1/iam/users", requireAuth, requirePermission("manage_users"), async (c) => {
|
|
const tid = tenantScope(c.get("user"));
|
|
const row = await callIamFn(tid, "iam.fn_user_list", { tenant_id: tid });
|
|
if (!row) {
|
|
return respondRpc(c, { ok: false, code: "INTERNAL", layer: "api", message: "No se pudo consultar usuarios IAM", data: null, errors: null });
|
|
}
|
|
return respondRpc(c, row as Parameters<typeof respondRpc>[1]);
|
|
});
|
|
|
|
app.get("/v1/iam/permissions", requireAuth, requirePermission("manage_users"), async (c) => {
|
|
const tid = tenantScope(c.get("user"));
|
|
const row = await callIamFn(tid, "iam.fn_permission_list", {});
|
|
if (!row) {
|
|
return respondRpc(c, { ok: false, code: "INTERNAL", layer: "api", message: "No se pudo consultar permisos IAM", data: null, errors: null });
|
|
}
|
|
return respondRpc(c, row as Parameters<typeof respondRpc>[1]);
|
|
});
|
|
|
|
app.get("/v1/iam/roles/:role/permissions", requireAuth, requirePermission("manage_users"), async (c) => {
|
|
const tid = tenantScope(c.get("user"));
|
|
const row = await callIamFn(tid, "iam.fn_role_permissions_get", { role_code: c.req.param("role") });
|
|
if (!row) {
|
|
return respondRpc(c, { ok: false, code: "INTERNAL", layer: "api", message: "No se pudo consultar permisos del rol", data: null, errors: null });
|
|
}
|
|
return respondRpc(c, row as Parameters<typeof respondRpc>[1]);
|
|
});
|
|
|
|
app.put("/v1/iam/roles/:role/permissions", requireAuth, requirePermission("manage_users"), async (c) => {
|
|
const body = await c.req.json<{ permissions?: string[] }>();
|
|
const tid = tenantScope(c.get("user"));
|
|
const row = await callIamFn(tid, "iam.fn_role_permissions_save", {
|
|
role_code: c.req.param("role"),
|
|
permissions: body.permissions ?? [],
|
|
});
|
|
if (!row) {
|
|
return respondRpc(c, { ok: false, code: "INTERNAL", layer: "api", message: "No se pudo guardar permisos del rol", data: null, errors: null });
|
|
}
|
|
return respondRpc(c, row as Parameters<typeof respondRpc>[1]);
|
|
});
|
|
|
|
app.get("/v1/me/permissions", requireAuth, async (c) => {
|
|
const user = c.get("user");
|
|
const { userPermissions } = await import("./permissions.ts");
|
|
const role = user.role === "tenant_admin" ? "tenant_admin" : "user";
|
|
const perms = await userPermissions(user.tenant_id, role);
|
|
return respondRpc(c, {
|
|
ok: true,
|
|
code: "OK",
|
|
layer: "api",
|
|
message: `Permisos del usuario ${user.username}`,
|
|
data: { permissions: [...perms], role: user.role },
|
|
errors: null,
|
|
});
|
|
});
|
|
}
|