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:
@@ -405,6 +405,97 @@ paths:
|
||||
schema:
|
||||
$ref: "#/components/schemas/AppDeletionConflict"
|
||||
|
||||
/apps/{id}/permissions:
|
||||
get:
|
||||
operationId: listAppPermissions
|
||||
tags: [apps]
|
||||
summary: List the permissions that gate this app (admin)
|
||||
description: |
|
||||
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.
|
||||
parameters:
|
||||
- name: id
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
type: integer
|
||||
responses:
|
||||
"200":
|
||||
description: Permissions gating the app
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
$ref: "#/components/schemas/Permission"
|
||||
"404":
|
||||
description: App not found
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/ErrorResponse"
|
||||
post:
|
||||
operationId: addAppPermission
|
||||
tags: [apps]
|
||||
summary: Add a required permission to this app (admin)
|
||||
description: |
|
||||
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.
|
||||
parameters:
|
||||
- name: id
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
type: integer
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/AddAppPermissionBody"
|
||||
responses:
|
||||
"201":
|
||||
description: Permission requirement added (or already present)
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/AppPermissionLink"
|
||||
"404":
|
||||
description: App or permission not found
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/ErrorResponse"
|
||||
|
||||
/apps/{id}/permissions/{permissionId}:
|
||||
delete:
|
||||
operationId: removeAppPermission
|
||||
tags: [apps]
|
||||
summary: Remove a required permission from this app (admin)
|
||||
parameters:
|
||||
- name: id
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
type: integer
|
||||
- name: permissionId
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
type: integer
|
||||
responses:
|
||||
"204":
|
||||
description: Removed (or no row existed for the pair)
|
||||
"404":
|
||||
description: App not found
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/ErrorResponse"
|
||||
|
||||
/apps/{id}/open:
|
||||
post:
|
||||
operationId: logAppOpen
|
||||
@@ -2429,6 +2520,26 @@ components:
|
||||
- id
|
||||
- name
|
||||
|
||||
AddAppPermissionBody:
|
||||
type: object
|
||||
properties:
|
||||
permissionId:
|
||||
type: integer
|
||||
description: ID of the permission to require for this app.
|
||||
required:
|
||||
- permissionId
|
||||
|
||||
AppPermissionLink:
|
||||
type: object
|
||||
properties:
|
||||
appId:
|
||||
type: integer
|
||||
permissionId:
|
||||
type: integer
|
||||
required:
|
||||
- appId
|
||||
- permissionId
|
||||
|
||||
ReplaceRolePermissionsBody:
|
||||
type: object
|
||||
properties:
|
||||
|
||||
Reference in New Issue
Block a user