Any artifact can now be made publicly viewable via a share link, no login required - opt-in per artifact, toggled from the feed UI next to Keep. - Artifact.share_token: nullable, unique, high-entropy (secrets.token_urlsafe(32)). NULL = not shared; SQL treats each NULL as distinct so uniqueness still holds for the many not-shared rows. - New public route GET /s/<token> (shared_artifact_view), completely unauthenticated, serving the same rendered/raw content as the owner route via a shared serve_artifact_file() helper. - toggle_artifact_share: generates/clears the token: mirrors toggle_artifact_keep's pattern. - Fixed a real gap this surfaced: embedded images in a shared artifact's content would otherwise break for an anonymous visitor, since asset_view was owner-only. It now also allows access when the asset's parent artifact is currently shared - unattached (orphaned) assets stay private regardless. - serialize_artifact now reports is_shared/share_url so the API reflects sharing status (read-only for now; toggling stays UI-only). - Feed UI: Share button (mirrors Keep's toggle interaction), a copyable share-link box that appears when shared, and a "shared" badge on the artifact row - distinct blue accent from Keep's green so both can be visible on the same row without confusion. Verified live end-to-end (toggle on/off, public access, fake-token 404, old-link-dies-on-unshare, embedded-asset access before/after sharing) before writing the 12 automated tests in tests/views/test_sharing.py. Also fixed two unrelated stale spots noticed in docs/api.md while adding the Sharing section: the example response was missing is_kept/size_bytes, and the raw-content table didn't list json/csv (added earlier this session but never documented there). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
24 lines
1.4 KiB
Python
24 lines
1.4 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("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>", 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/artifacts", views.ArtifactListCreateView.as_view(), name="artifact-api"),
|
|
path("api/artifacts/<str:artifact_id>", views.ArtifactDetailView.as_view(), name="artifact-detail-api"),
|
|
path("settings/", views.settings_home, name="settings-home"),
|
|
path("settings/account/", views.settings_account, name="settings-account"),
|
|
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"),
|
|
]
|