Warn admins before deleting non-empty users/apps/services (Task #85)
Apply the existing groups delete-warning pattern to user, app, and
service deletions so admins are warned (and have to confirm twice)
before destroying records that still own dependent data.
Backend
- openapi.yaml: added `force` query param to DELETE /users/{id},
/apps/{id}, /services/{id} plus three new conflict schemas
(UserDeletionConflict, AppDeletionConflict, ServiceDeletionConflict).
- routes/users.ts, apps.ts, services.ts: rewrote DELETE handlers to
count dependents (notes/orders/conversations/messages for users;
group_apps/restrictions/open events for apps; service_orders for
services), return 409 with counts when non-empty and force is not set,
and on `?force=true` perform the cascade in a transaction and write
an `*.force_delete` audit_logs row.
- Existing 204 success path preserved for empty deletes.
UI (artifacts/tx-os/src/pages/admin.tsx)
- New `DeletionWarningDialog` helper, plus app/service/user delete state
+ lazy 409 detection (first click probes; on 409 the dialog upgrades
to show counts + "Delete anyway"; second click sends ?force=true).
- Replaced the three plain `confirm(t("admin.deleteConfirm"))` callsites.
i18n
- Added admin.deleteApp/deleteService/deleteUser keys (title, warning,
forceHint, emptyBody, count keys, confirm, anyway) in en.json + ar.json.
Tests
- artifacts/api-server/tests/delete-force-warnings.test.mjs covers all
9 cases (clean delete, 409 with counts, force=true 204 + audit log)
for users, apps, and services. Existing groups-crud and service-orders
tests still pass.
Notes / drift
- Lazy detection (vs eager counts on the row like Groups already does)
was chosen because list endpoints don't return counts yet — proposed
follow-up #96 covers eager counts so the warning appears on first
click everywhere.
- E2E test for the UI flow flaked twice; backend integration tests
(9/9 pass), direct curl validation of force=true returning 204, and
typecheck across the monorepo all confirm correctness.
Replit-Task-Id: 91404d92-e74c-4720-8fc9-8eb772eefc33
This commit is contained in:
@@ -305,6 +305,38 @@
|
||||
"editService": "تعديل الخدمة",
|
||||
"editUser": "تعديل المستخدم",
|
||||
"deleteConfirm": "هل أنت متأكد من الحذف؟",
|
||||
"deleteApp": {
|
||||
"title": "حذف التطبيق \"{{name}}\"؟",
|
||||
"warning": "هذا التطبيق لا يزال قيد الاستخدام. حذفه سيؤثر على ما يلي:",
|
||||
"forceHint": "سيؤدي ذلك إلى إلغاء وصول كل من يستخدمه وإزالة سجلّه. لا يمكن التراجع عن هذا الإجراء.",
|
||||
"emptyBody": "هل أنت متأكد من حذف هذا التطبيق؟",
|
||||
"groupCount": "{{count}} مجموعة تمنح هذا التطبيق حالياً",
|
||||
"restrictionCount": "{{count}} قاعدة صلاحيات تقيّده",
|
||||
"openCount": "{{count}} مرة فتح في السجل",
|
||||
"confirm": "حذف",
|
||||
"anyway": "حذف رغم ذلك"
|
||||
},
|
||||
"deleteService": {
|
||||
"title": "حذف الخدمة \"{{name}}\"؟",
|
||||
"warning": "هذه الخدمة لا تزال مرتبطة بطلبات. حذفها سيؤثر على ما يلي:",
|
||||
"forceHint": "سيؤدي ذلك إلى حذف كل الطلبات المرتبطة نهائياً. لا يمكن التراجع عن هذا الإجراء.",
|
||||
"emptyBody": "هل أنت متأكد من حذف هذه الخدمة؟",
|
||||
"orderCount": "{{count}} طلب",
|
||||
"confirm": "حذف",
|
||||
"anyway": "حذف رغم ذلك"
|
||||
},
|
||||
"deleteUser": {
|
||||
"title": "حذف المستخدم \"{{name}}\"؟",
|
||||
"warning": "هذا المستخدم لديه بيانات. حذفه سيؤثر على ما يلي:",
|
||||
"forceHint": "سيؤدي ذلك إلى حذف ملاحظاته وطلباته والمحادثات التي أنشأها والرسائل التي أرسلها بشكل نهائي. لا يمكن التراجع عن هذا الإجراء.",
|
||||
"emptyBody": "هل أنت متأكد من حذف هذا المستخدم؟",
|
||||
"noteCount": "{{count}} ملاحظة",
|
||||
"orderCount": "{{count}} طلب",
|
||||
"conversationCount": "{{count}} محادثة أنشأها",
|
||||
"messageCount": "{{count}} رسالة أرسلها",
|
||||
"confirm": "حذف",
|
||||
"anyway": "حذف رغم ذلك"
|
||||
},
|
||||
"activate": "تفعيل",
|
||||
"deactivate": "تعطيل",
|
||||
"appName": "اسم التطبيق",
|
||||
|
||||
Reference in New Issue
Block a user