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:
@@ -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
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user