Add per-artifact public sharing
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>
This commit is contained in:
parent
e137725cab
commit
91ea19d26b
10 changed files with 414 additions and 4 deletions
|
|
@ -575,6 +575,24 @@ h1 {
|
|||
display: none;
|
||||
}
|
||||
|
||||
.shared-badge {
|
||||
align-items: center;
|
||||
background: #eaf2fb;
|
||||
border-radius: 4px;
|
||||
color: #1d4d85;
|
||||
display: inline-flex;
|
||||
font-size: 0.66rem;
|
||||
font-weight: 700;
|
||||
justify-content: center;
|
||||
line-height: 1;
|
||||
padding: 0.14rem 0.3rem;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.shared-badge[hidden] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.artifact-source {
|
||||
align-items: center;
|
||||
color: var(--muted);
|
||||
|
|
@ -888,6 +906,45 @@ kbd {
|
|||
color: #28583a;
|
||||
}
|
||||
|
||||
.share-button.shared {
|
||||
background: #eaf2fb;
|
||||
border-color: #9cc0ed;
|
||||
color: #1d4d85;
|
||||
}
|
||||
|
||||
.share-link-box {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
gap: 0.35rem;
|
||||
padding: 0 0.9rem 0.6rem;
|
||||
}
|
||||
|
||||
.share-link-box[hidden] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.share-link-box input {
|
||||
background: var(--surface-alt);
|
||||
border: 1px solid var(--control-border);
|
||||
border-radius: 4px;
|
||||
color: var(--muted);
|
||||
flex: 1 1 auto;
|
||||
font: inherit;
|
||||
font-size: 0.76rem;
|
||||
padding: 0.35rem 0.55rem;
|
||||
}
|
||||
|
||||
.share-link-box button {
|
||||
background: var(--surface);
|
||||
border: 1px solid var(--control-border);
|
||||
border-radius: 4px;
|
||||
color: var(--text);
|
||||
cursor: pointer;
|
||||
font: inherit;
|
||||
font-size: 0.76rem;
|
||||
padding: 0.35rem 0.65rem;
|
||||
}
|
||||
|
||||
.details-panel {
|
||||
background: var(--surface-alt);
|
||||
border-bottom: 1px solid var(--grid-line);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue