feat(drive): add drive deletion
- conditions: drive must be empty
- deletion forbidden on main personal drive
This commit is contained in:
@@ -157,6 +157,35 @@ export async function removeDriveMemberAdmin(
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* `DELETE /api/admin/drives/{id}` — admin-only drive delete (D3b).
|
||||
*
|
||||
* Bypasses the per-drive `Manage` check (the admin guard at the route
|
||||
* edge is the access control). The default-personal-drive guard and
|
||||
* the "drive must be empty" check still fire server-side — admins
|
||||
* can't accidentally wipe a populated drive or a user's home folder.
|
||||
* Throws on non-2xx so the caller can branch on `405` (default
|
||||
* personal) vs `409` (non-empty) when surfacing the failure.
|
||||
*/
|
||||
export async function deleteDriveAdmin(driveId: string): Promise<void> {
|
||||
const res = await apiFetch(`/api/admin/drives/${encodeURIComponent(driveId)}`, {
|
||||
method: 'DELETE',
|
||||
credentials: 'same-origin',
|
||||
headers: getCsrfHeaders()
|
||||
});
|
||||
if (!res.ok) {
|
||||
let detail = '';
|
||||
try {
|
||||
const parsed = (await res.json()) as { error?: string; message?: string };
|
||||
detail = parsed.error ?? parsed.message ?? '';
|
||||
} catch {
|
||||
/* response body wasn't JSON */
|
||||
}
|
||||
// 405 / 409 carry actionable messages from the backend; bubble them.
|
||||
throw new Error(detail || `delete drive failed: ${res.status}`);
|
||||
}
|
||||
}
|
||||
|
||||
// ── Users ───────────────────────────────────────────────────────────────
|
||||
|
||||
export interface AdminUsersPage {
|
||||
|
||||
@@ -104,6 +104,32 @@ export async function updateDriveMember(
|
||||
return (await res.json()) as DriveMember;
|
||||
}
|
||||
|
||||
/**
|
||||
* `DELETE /api/drives/{id}` — Owner-only drive delete (D3b).
|
||||
*
|
||||
* Refused with `405` for the default Personal drive and `409` for a
|
||||
* non-empty drive (caller must move/trash content first). Throws on
|
||||
* non-2xx with the server's detail message when present so the caller
|
||||
* can decide whether to surface a confirmation prompt vs an error.
|
||||
*/
|
||||
export async function deleteDrive(driveId: string): Promise<void> {
|
||||
const res = await apiFetch(`/api/drives/${encodeURIComponent(driveId)}`, {
|
||||
method: 'DELETE',
|
||||
credentials: 'same-origin',
|
||||
headers: getCsrfHeaders()
|
||||
});
|
||||
if (!res.ok) {
|
||||
let detail = '';
|
||||
try {
|
||||
const parsed = (await res.json()) as { error?: string; message?: string };
|
||||
detail = parsed.error ?? parsed.message ?? '';
|
||||
} catch {
|
||||
/* response body wasn't JSON */
|
||||
}
|
||||
throw new Error(detail || `delete drive failed: ${res.status}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* `DELETE /api/drives/{id}/members/{kind}/{sid}` — remove a member.
|
||||
* Idempotent (removing a non-member returns 204). Refused with 400 if it
|
||||
|
||||
Reference in New Issue
Block a user