Task #109: Admin UI to manage app required permissions

- Added 3 admin-only API endpoints in artifacts/api-server/src/routes/apps.ts:
  - GET    /api/apps/:id/permissions   — list permissions gating an app
  - POST   /api/apps/:id/permissions   — add a permission (idempotent via
    onConflictDoNothing() on the (app_id, permission_id) composite PK)
  - DELETE /api/apps/:id/permissions/:permissionId — remove (idempotent, 204)
- Documented the new endpoints in lib/api-spec/openapi.yaml with two new schemas
  (AddAppPermissionBody, AppPermissionLink) and re-ran codegen.
- Added a "Required permissions" section (AppPermissionsEditor) to the existing
  Edit App dialog in artifacts/tx-os/src/pages/admin.tsx, using the generated
  hooks. The section is shown only when editing an existing app (it needs an
  app id). Wired up admin.appPermissions.* i18n keys in en.json + ar.json.
- Added artifacts/api-server/tests/app-permissions-crud.test.mjs with 5 tests
  (empty list, idempotent add, 404 on unknown app/perm, idempotent delete,
  403 for non-admins). All 5 pass; related tests
  (app-permissions-unique, apps-group-visibility, list-dependency-counts) still pass.
- Verified the new admin UI end-to-end with the testing skill: admin login,
  open Edit App dialog, add/remove a required permission, and confirm the
  section is hidden in the Add App dialog.

Notes / scope:
- Pre-existing duplicate rows in app_permissions had to be deduped and
  `pnpm --filter @workspace/db run push` was run once so the composite PK
  could be added (separate task "Re-run the Drizzle schema push" already
  exists for this project-wide chore).
- No audit logging here — separate existing task already covers it.
- The "test" workflow shows a pre-existing ECONNREFUSED race; an existing
  task already tracks making the test workflow wait for the API server.

Replit-Task-Id: 912bb163-6b6f-43d3-9934-41fc97519337
This commit is contained in:
riyadhafraa
2026-04-29 15:31:42 +00:00
parent 2982a28432
commit 8b2fe3164d
9 changed files with 975 additions and 0 deletions
+47
View File
@@ -447,6 +447,53 @@ export const DeleteAppQueryParams = zod.object({
),
});
/**
* Returns the permissions currently required for users to see this app
in their launcher. An app with no rows here is unrestricted (visible
to anyone). When more than one permission is configured, holding any
one of them is sufficient.
* @summary List the permissions that gate this app (admin)
*/
export const ListAppPermissionsParams = zod.object({
id: zod.coerce.number(),
});
export const ListAppPermissionsResponseItem = zod.object({
id: zod.number(),
name: zod.string(),
descriptionAr: zod.string().nullish(),
descriptionEn: zod.string().nullish(),
});
export const ListAppPermissionsResponse = zod.array(
ListAppPermissionsResponseItem,
);
/**
* Adds a row to `app_permissions` for the (app_id, permission_id) pair.
Uses `ON CONFLICT DO NOTHING` against the composite primary key so
re-adding an existing pair is a no-op.
* @summary Add a required permission to this app (admin)
*/
export const AddAppPermissionParams = zod.object({
id: zod.coerce.number(),
});
export const AddAppPermissionBody = zod.object({
permissionId: zod
.number()
.describe("ID of the permission to require for this app."),
});
/**
* @summary Remove a required permission from this app (admin)
*/
export const RemoveAppPermissionParams = zod.object({
id: zod.coerce.number(),
permissionId: zod.coerce.number(),
});
/**
* @summary Log an app open event for the current user
*/