Warn admins before deleting non-empty groups
Original task: Task #79 — Make DELETE /api/groups/:id refuse to wipe a non-empty group unless explicitly forced, surface the impacted counts in the admin UI, and log forced deletions to an audit trail. Changes: - API: DELETE /api/groups/:id now computes member/app/role counts. If any are non-zero and `?force=true` is not passed, it returns 409 with { error, memberCount, appCount, roleCount }. With `force=true` it deletes and writes an entry to the new `audit_logs` table (action='group.force_delete', target_type='group', target_id, metadata). System group guard and 404 behaviour preserved. - DB: Added `audit_logs` table (lib/db/src/schema/audit-logs.ts) with actor, action, target type/id, jsonb metadata, timestamp. Pushed via drizzle-kit. - OpenAPI: Added `force` query param, 409 response, and `GroupDeletionConflict` schema. Re-ran orval codegen for api-client-react and api-zod. - api-client-react: Re-exported `ApiError` and `ErrorType` so the UI can inspect 409 responses. - Admin UI (artifacts/teaboy-os/src/pages/admin.tsx GroupsPanel): Replaced the bare `confirm()` with a confirmation dialog that shows the group name and impacted member/app/role counts. Empty groups get a simple "Delete" confirmation; non-empty groups get a warning + "Delete anyway" button which sends `force=true`. If the server returns 409 (race), the dialog updates to show the server-reported counts. - Locales: Added en/ar strings for the new dialog (deleteTitle, deleteWarning, deleteForceHint, deleteEmptyBody, deleteConfirm, deleteAnyway). Verified end-to-end: 409 on first DELETE, dialog warning visible, force deletion succeeds, single audit_logs row recorded with correct metadata.
This commit is contained in:
@@ -1357,6 +1357,13 @@ paths:
|
||||
required: true
|
||||
schema:
|
||||
type: integer
|
||||
- name: force
|
||||
in: query
|
||||
required: false
|
||||
description: Force deletion of a non-empty group (records an audit log entry).
|
||||
schema:
|
||||
type: boolean
|
||||
default: false
|
||||
responses:
|
||||
"204":
|
||||
description: Deleted
|
||||
@@ -1366,6 +1373,12 @@ paths:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/ErrorResponse"
|
||||
"409":
|
||||
description: Group is not empty; pass force=true to confirm deletion.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/GroupDeletionConflict"
|
||||
|
||||
# Stats
|
||||
/stats/home:
|
||||
@@ -1910,6 +1923,23 @@ components:
|
||||
- roleCount
|
||||
- createdAt
|
||||
|
||||
GroupDeletionConflict:
|
||||
type: object
|
||||
properties:
|
||||
error:
|
||||
type: string
|
||||
memberCount:
|
||||
type: integer
|
||||
appCount:
|
||||
type: integer
|
||||
roleCount:
|
||||
type: integer
|
||||
required:
|
||||
- error
|
||||
- memberCount
|
||||
- appCount
|
||||
- roleCount
|
||||
|
||||
GroupDetail:
|
||||
type: object
|
||||
properties:
|
||||
|
||||
Reference in New Issue
Block a user