feat(library): protect Daedalus workspace-scoped libraries from manual deletion
- Add guard in `library_delete` view to block deletion of libraries owned by a Daedalus workspace, redirecting with an error message - Disable the Delete button in `library_detail.html` for workspace- scoped libraries and show a warning alert explaining managed ownership - Add a "Daedalus workspace" badge in both `library_detail.html` and `library_list.html` to visually identify workspace-owned libraries Prevents state desync between Mnemosyne and Daedalus by ensuring workspace-scoped libraries can only be removed via the Daedalus workspace DELETE API endpoint.
This commit is contained in:
@@ -10,17 +10,46 @@
|
||||
<div class="flex justify-between items-start mb-6">
|
||||
<div>
|
||||
<h1 class="text-3xl font-bold">{{ library.name }}</h1>
|
||||
<div class="badge badge-primary mt-2">{{ library.library_type }}</div>
|
||||
<div class="flex flex-wrap gap-2 mt-2">
|
||||
<div class="badge badge-primary">{{ library.library_type }}</div>
|
||||
{% if library.workspace_id %}
|
||||
<div class="badge badge-warning gap-1"
|
||||
title="Workspace {{ library.workspace_id }}">
|
||||
Daedalus workspace
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% if library.description %}
|
||||
<p class="mt-3 opacity-80">{{ library.description }}</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="flex gap-2">
|
||||
<a href="{% url 'library:library-edit' uid=library.uid %}" class="btn btn-sm btn-outline">Edit</a>
|
||||
{% if library.workspace_id %}
|
||||
<button type="button" class="btn btn-sm btn-error btn-outline" disabled
|
||||
title="This library is managed by Daedalus. Delete it from the Daedalus workspace, not here.">
|
||||
Delete
|
||||
</button>
|
||||
{% else %}
|
||||
<a href="{% url 'library:library-delete' uid=library.uid %}" class="btn btn-sm btn-error btn-outline">Delete</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% if library.workspace_id %}
|
||||
<div class="alert alert-warning mb-6">
|
||||
<div>
|
||||
<div class="font-semibold">Managed by Daedalus</div>
|
||||
<div class="text-sm opacity-80">
|
||||
This library was created for Daedalus workspace
|
||||
<code class="font-mono">{{ library.workspace_id }}</code>.
|
||||
Items here are owned by the workspace; deleting the workspace in
|
||||
Daedalus will remove this library. Do not delete it manually.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<!-- Content-Type Configuration -->
|
||||
<div class="collapse collapse-arrow bg-base-200 mb-6">
|
||||
<input type="checkbox" />
|
||||
|
||||
@@ -31,7 +31,14 @@
|
||||
{{ lib.name }}
|
||||
</a>
|
||||
</h2>
|
||||
<div class="badge badge-outline">{{ lib.library_type }}</div>
|
||||
<div class="flex flex-wrap gap-1">
|
||||
<div class="badge badge-outline">{{ lib.library_type }}</div>
|
||||
{% if lib.workspace_id %}
|
||||
<div class="badge badge-warning gap-1" title="Managed by Daedalus workspace {{ lib.workspace_id }} — do not delete from Mnemosyne.">
|
||||
Daedalus workspace
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% if lib.description %}
|
||||
<p class="text-sm opacity-70 mt-2">{{ lib.description|truncatewords:20 }}</p>
|
||||
{% endif %}
|
||||
|
||||
@@ -299,6 +299,17 @@ def library_delete(request, uid):
|
||||
messages.error(request, f"Library not found: {e}")
|
||||
return redirect("library:library-list")
|
||||
|
||||
# Daedalus owns the lifecycle of workspace-scoped libraries — they can
|
||||
# only be deleted via DELETE /library/api/workspaces/{workspace_id}/.
|
||||
# Block the human delete path so a stray click can't desync state.
|
||||
if lib.workspace_id:
|
||||
messages.error(
|
||||
request,
|
||||
f'"{lib.name}" is managed by Daedalus workspace '
|
||||
f"{lib.workspace_id}. Delete it from Daedalus, not here.",
|
||||
)
|
||||
return redirect("library:library-detail", uid=uid)
|
||||
|
||||
if request.method == "POST":
|
||||
name = lib.name
|
||||
lib.delete()
|
||||
|
||||
Reference in New Issue
Block a user