Delete now moves an artifact to a recoverable Trash instead of hard-removing.
- Model: Artifact.deleted_at (nullable, indexed) = in Trash.
- Soft-delete everywhere: DELETE /api/artifacts/{id} sets deleted_at
(idempotent 204); session POST /a/{id}/delete for the feed's hover Delete
icon; POST /a/{id}/undelete restores; POST /feed/empty-trash purges.
- Trashed artifacts are off-quota (deleting frees quota) and eviction-protected
(never evicted early); hidden from feed, sources, counts, and all API reads
(list/detail/raw).
- Sidebar: "Deleted" and "Keep" system folders at the bottom (counts, hidden
when empty); ?folder=deleted / ?folder=keep views reuse the row rendering.
Deleted view has hover Undelete + an Empty Trash button. Keep = view over
is_kept. Live-poll disabled in folder views.
- Docs (api.md delete section, CLAUDE.md), 10 new/updated tests. 150 passing;
API soft-delete lifecycle verified live.
Purge is manual (Empty Trash) this release; auto purge-on-delete (background,
~3-day retention) is the planned fast-follow.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
34 lines
2.2 KiB
Python
34 lines
2.2 KiB
Python
from django.urls import path
|
|
|
|
from . import views
|
|
|
|
|
|
urlpatterns = [
|
|
path("", views.feed, name="feed"),
|
|
path("feed/older", views.feed_older, name="feed-older"),
|
|
path("feed/updates", views.feed_updates, name="feed-updates"),
|
|
path("a/<str:artifact_id>/read", views.mark_artifact_read, name="artifact-mark-read"),
|
|
path("a/<str:artifact_id>/keep", views.toggle_artifact_keep, name="artifact-toggle-keep"),
|
|
path("a/<str:artifact_id>/share", views.toggle_artifact_share, name="artifact-toggle-share"),
|
|
path("a/<str:artifact_id>/delete", views.delete_artifact, name="artifact-delete"),
|
|
path("a/<str:artifact_id>/undelete", views.undelete_artifact, name="artifact-undelete"),
|
|
path("feed/empty-trash", views.empty_trash, name="feed-empty-trash"),
|
|
path("a/<str:artifact_id>", views.artifact_view, name="artifact"),
|
|
path("s/<str:share_token>", views.shared_artifact_view, name="artifact-shared"),
|
|
path("assets/<str:asset_id>", views.asset_view, name="asset"),
|
|
path("api/assets", views.AssetUploadView.as_view(), name="asset-upload-api"),
|
|
path("api/capabilities", views.CapabilitiesView.as_view(), name="capabilities-api"),
|
|
path("api/skill", views.SkillView.as_view(), name="skill-api"),
|
|
path("api/keys", views.ApiKeyListCreateView.as_view(), name="api-keys"),
|
|
path("api/keys/<int:key_id>", views.ApiKeyDetailView.as_view(), name="api-key-detail"),
|
|
path("api/artifacts", views.ArtifactListCreateView.as_view(), name="artifact-api"),
|
|
path("api/artifacts/<str:artifact_id>", views.ArtifactDetailView.as_view(), name="artifact-detail-api"),
|
|
path("api/artifacts/<str:artifact_id>/raw", views.ArtifactRawView.as_view(), name="artifact-raw-api"),
|
|
path("settings/", views.settings_home, name="settings-home"),
|
|
path("settings/account/", views.settings_account, name="settings-account"),
|
|
path("settings/api-keys/", views.settings_api_keys, name="settings-api-keys"),
|
|
path("settings/users/", views.settings_users, name="settings-users"),
|
|
path("settings/demo-data/", views.settings_demo_data, name="settings-demo-data"),
|
|
path("settings/storage/", views.settings_storage, name="settings-storage"),
|
|
path("settings/about/", views.settings_about, name="settings-about"),
|
|
]
|