Until now a note was shared exactly one way: file it in a shared project, and
everyone who could read the project could read the note. That is right when the
note belongs with a body of work and wrong when it is just "let Sofie see this" —
it made you stand up a project to hand over a single page.
A note can now be shared with named people directly, at viewer or editor — the
same two roles a project membership uses, so there is one idea of a role in the
program and not two. The paths stack: a note can be loose, filed, shared with
people, or any mix, and access is the most any of them allow.
The store:
- note_shares (migration 0030): note ↔ user ↔ role, one row per person, owner
recorded. Visibility (AllNotes, and search via mayTouchNote) widens to include
notes shared directly with the reader.
- the effective role folds a direct share on top of the owner and project paths.
The rules, drawn where they protect the owner:
- read = viewer, edit text = editor, on any path.
- MOVE a note (change its project) is owner-only: an editor filing it into a
project the owner is not in would take the owner's own note away from them.
- DELETE is owner or project-editor — a direct share, however high its role,
does not reach to throwing the note away.
- SHARE and UNSHARE are the owner's alone, like a project's invitations.
- the picker is everyone on the instance (a closed, invited set) minus who is
already on the note; a made-up id and the owner themselves are refused.
The list:
- a "Delt med mig" group at the top, collapsible, that appears only when it has
something in it — the same rule Favoritter follows — so it costs nothing when
you share nothing. A shared note lives there, not also in a time bucket.
- every shared note carries a small chip in its owner's avatar colour, the
project mark's shape said for a person.
- no star or archive button on a note somebody else owns: those change the note
itself, and it is not yours to put away.
- the editor grows a "Del med personer" panel for the owner: who can see it, at
what role, and an add row drawn from the instance's other people.
The old comment in the notes footer argued against a note having an access list
of its own. That was the right call until the case it did not cover — sharing
without a project — turned out to be the one actually asked for. Updated to match.
Person now carries json tags, so a note's owner serialises as id/name/avatar_color
like every other person in the API rather than as Go field names.
Tests: a shared note reaches the other person marked and owned; a viewer reads but
cannot write; an editor writes but cannot re-share or delete; unshare removes
access and is owner-only; the candidate list excludes who is already on and the
owner; and a made-up id and self are refused. OpenAPI covers the three new routes.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
31 lines
1.6 KiB
SQL
31 lines
1.6 KiB
SQL
-- Sharing a note with a person, rather than only by filing it in a shared project.
|
|
--
|
|
-- Until now a note was shared exactly one way: put it in a project, and everyone
|
|
-- who could read the project could read the note. That is the right tool when the
|
|
-- note belongs with a body of work, and the wrong one when it is just "let Sofie
|
|
-- see this" — it makes you stand up a project to hand over a single page.
|
|
--
|
|
-- A note_shares row is that direct grant: this note, this person, this role. It
|
|
-- sits alongside the project path, not instead of it — a note can be loose, filed,
|
|
-- shared with people, or any combination, and access is the most that any of those
|
|
-- allow. Only the note's owner (its created_by) writes these rows.
|
|
--
|
|
-- role is 'viewer' or 'editor', the same words a project membership uses, so there
|
|
-- is one idea of what a role means in this program and not two.
|
|
CREATE TABLE note_shares (
|
|
note_id TEXT NOT NULL REFERENCES notes (id) ON DELETE CASCADE,
|
|
user_id TEXT NOT NULL REFERENCES users (id) ON DELETE CASCADE,
|
|
role TEXT NOT NULL DEFAULT 'viewer',
|
|
created_by TEXT REFERENCES users (id) ON DELETE SET NULL,
|
|
created_at INTEGER NOT NULL,
|
|
|
|
-- One share per person per note: sharing again changes the role, it does not
|
|
-- add a second row that the first would then quietly disagree with.
|
|
PRIMARY KEY (note_id, user_id)
|
|
);
|
|
|
|
-- "Which notes are shared with me" is asked on every visit to the notes page, to
|
|
-- build the Delt med mig group and to widen what the list may show. It is a lookup
|
|
-- by user_id, so that is what carries the index.
|
|
CREATE INDEX note_shares_by_user ON note_shares (user_id);
|