sharing: share a note from the web, at view or edit, with anyone on the instance
CI & Build / Python lint (push) Successful in 3s
CI & Build / Build now, or wait for Android? (push) Successful in 4s
Android / Build, or is the channel already serving this? (push) Successful in 4s
Android / Kotlin + Rust (APK) (push) Skipped
Desktop (Tauri) / Build, or is the channel already serving this? (push) Successful in 2s
CI & Build / Web typecheck and unit tests (push) Successful in 9s
CI & Build / Python tests (push) Successful in 14s
CI & Build / integration (push) Failing after 54s
CI & Build / Build & push image (push) Skipped
Desktop (Tauri) / Web tests, clippy, Rust tests and rustfmt (push) Successful in 2m6s
Desktop (Tauri) / Update manifest (push) Canceled after 0s
Desktop (Tauri) / Windows installer (cross-compiled) (push) Canceled after 2m25s
Desktop (Tauri) / Tauri desktop (Linux) (push) Canceled after 2m33s
CI & Build / Python lint (push) Successful in 3s
CI & Build / Build now, or wait for Android? (push) Successful in 4s
Android / Build, or is the channel already serving this? (push) Successful in 4s
Android / Kotlin + Rust (APK) (push) Skipped
Desktop (Tauri) / Build, or is the channel already serving this? (push) Successful in 2s
CI & Build / Web typecheck and unit tests (push) Successful in 9s
CI & Build / Python tests (push) Successful in 14s
CI & Build / integration (push) Failing after 54s
CI & Build / Build & push image (push) Skipped
Desktop (Tauri) / Web tests, clippy, Rust tests and rustfmt (push) Successful in 2m6s
Desktop (Tauri) / Update manifest (push) Canceled after 0s
Desktop (Tauri) / Windows installer (cross-compiled) (push) Canceled after 2m25s
Desktop (Tauri) / Tauri desktop (Linux) (push) Canceled after 2m33s
The ACL has gated every read since M0, but nothing could write a share. Server: - shares_api: GET /api/users/directory (everyone but you, signed-in only), GET/POST /api/notes/<id>/shares and DELETE …/shares/<share_id>, owner only. Sharing again with the same person changes the permission (ON CONFLICT on the new unique index). - acl.visible_to_user takes permission=; granted_to and shared_ids feed the serializer. - Edit covers body and checklist (_get_editable). Everything else stays _get_owned. A view share's write is a 404 like a stranger's (#1984). An editor's PATCH naming anything but body is a 403. - Serialized notes carry permission, shared and shared_by. A recipient never gets the owner's labels, and a #tag an editor types files under the owner's (it always went to note.owner_id). - ?shared=with_me, also allowed in saved views. Trash and reminders are the owner's. purge_note drops the note's shares. - Migration 0034: one share per note and person (and per group), permission limited to view and edit, an index for "shared with me". Web: - ShareDialog (one, mounted by the shell): pick a member, Can view or Can edit, change or remove existing shares, with loading, error and empty states. - Card: "Shared by X" or "Shared" chip; owner-only actions and reminder buttons hidden for recipients; checkboxes inert at view. - Editor: read-only at view; text and checklist only at edit; Share button for the owner. - FilterBar: Shared with me. Repo seam gains `shares`; the offline desktop shows none of it (#5175 brings sharing there). Tests: owner, recipient and stranger across reads, every write at view and edit, tag filing, unshare, trash, delete and validation; web unit tests for the facet and permission helpers. #5174. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,50 @@
|
||||
"""shares: one grant per note and person, and only the permissions the app knows
|
||||
|
||||
Revision ID: 0034
|
||||
Revises: 0033
|
||||
Create Date: 2026-10-07
|
||||
|
||||
Nothing wrote a share until #5174, so the table never needed these. Now the Share
|
||||
dialog does:
|
||||
- A unique index on (resource_type, resource_id, shared_with_user_id) where a user is
|
||||
the target, so sharing a note with someone twice updates the one grant rather than
|
||||
stacking two (the same for a group, ahead of step 15).
|
||||
- A check that `permission` is `view` or `edit`.
|
||||
- An index on `shared_with_user_id` for "Shared with me".
|
||||
|
||||
## Downgrade
|
||||
|
||||
Drops the indexes and the check. The rows stay.
|
||||
"""
|
||||
from alembic import op
|
||||
|
||||
revision = "0034"
|
||||
down_revision = "0033"
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
op.create_index(
|
||||
"uq_shares_resource_user",
|
||||
"shares",
|
||||
["resource_type", "resource_id", "shared_with_user_id"],
|
||||
unique=True,
|
||||
postgresql_where="shared_with_user_id IS NOT NULL",
|
||||
)
|
||||
op.create_index(
|
||||
"uq_shares_resource_group",
|
||||
"shares",
|
||||
["resource_type", "resource_id", "shared_with_group_id"],
|
||||
unique=True,
|
||||
postgresql_where="shared_with_group_id IS NOT NULL",
|
||||
)
|
||||
op.create_index("ix_shares_user", "shares", ["shared_with_user_id"])
|
||||
op.create_check_constraint("ck_shares_permission", "shares", "permission IN ('view', 'edit')")
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
op.drop_constraint("ck_shares_permission", "shares", type_="check")
|
||||
op.drop_index("ix_shares_user", table_name="shares")
|
||||
op.drop_index("uq_shares_resource_group", table_name="shares")
|
||||
op.drop_index("uq_shares_resource_user", table_name="shares")
|
||||
Reference in New Issue
Block a user