mirror of
https://github.com/Such-Software/smirk-backend-core.git
synced 2026-10-17 15:31:38 +02:00
feat(tips): targeted tips, off until an operator asks for one
A targeted tip is addressed to a Smirk handle rather than to whoever holds a share URL. The v3 port shipped public-only and the schema said so: there was no recipient column anywhere, so create_social_tip rejected is_public=false outright and the two inbox endpoints returned an empty list unconditionally. This is the missing half. The migration adds recipient_user_id plus two CHECKs that keep each kind of tip well formed: a targeted tip must name its recipient, and must carry the ciphertext, because with no URL fragment that ciphertext is the only way to claim it. Without the second one a draft could be created that looks healthy and is permanently unclaimable. Both are satisfied by every existing row, which is public, so nothing is backfilled or rewritten. is_public is derived from the recipient in the INSERT rather than passed alongside it. Two independent sources for "who may claim this" is how a tip ends up addressed to one person and claimable by anyone. Claiming was already guarded by is_public = TRUE, so no targeted tip could be claimed at all; that becomes "public, or the caller is the recipient". The claimable query repeats exactly the conditions the claim call enforces, so the inbox cannot show a Claim button the backend then refuses. It is off by default and separately from FEATURE_TIPS, because it is a distinct surface: an inbox, and a claim authorised by identity rather than by holding a secret. It also needs the migration to have run, so an instance must opt in rather than acquire it by upgrading. The capability is advertised only when tipping is also on, since a targeted tip is still a tip, and recipient resolution checks the flag before anything else: a backend that cannot serve this must refuse, never quietly fall back to creating a public tip from a targeted request, which would hand a tip meant for one person to anyone with the URL. The config fixture moves to Config::test_default so a test of what a flag does can live next to the flag.
This commit is contained in:
@@ -0,0 +1,37 @@
|
||||
-- Targeted social tips: a tip addressed to a Smirk user rather than to whoever
|
||||
-- holds a share URL.
|
||||
--
|
||||
-- The v3 port shipped public-only and the table says so: there is no recipient
|
||||
-- anywhere, so a targeted tip had no one to be addressed to and the create path
|
||||
-- rejected it outright. This adds the missing column and the two constraints
|
||||
-- that keep each kind of tip well formed.
|
||||
--
|
||||
-- Additive and reversible in practice: every existing row is public, so the new
|
||||
-- column is NULL for all of them and the new CHECK is satisfied by is_public.
|
||||
-- Nothing is dropped, rewritten or backfilled.
|
||||
|
||||
ALTER TABLE social_tips
|
||||
ADD COLUMN IF NOT EXISTS recipient_user_id UUID REFERENCES users(id) ON DELETE SET NULL;
|
||||
|
||||
-- A targeted tip must name its recipient. The mirror of public_tip_has_hash:
|
||||
-- between them, every row carries whichever claim path it actually uses, and a
|
||||
-- row that carries neither cannot be inserted.
|
||||
ALTER TABLE social_tips
|
||||
DROP CONSTRAINT IF EXISTS targeted_tip_has_recipient;
|
||||
ALTER TABLE social_tips
|
||||
ADD CONSTRAINT targeted_tip_has_recipient
|
||||
CHECK (is_public = TRUE OR recipient_user_id IS NOT NULL);
|
||||
|
||||
-- A targeted tip's ciphertext is sealed to the recipient's key and there is no
|
||||
-- URL fragment, so encrypted_key is the only way to claim it. Without this a
|
||||
-- draft could be created that is permanently unclaimable while looking healthy.
|
||||
ALTER TABLE social_tips
|
||||
DROP CONSTRAINT IF EXISTS targeted_tip_has_encrypted_key;
|
||||
ALTER TABLE social_tips
|
||||
ADD CONSTRAINT targeted_tip_has_encrypted_key
|
||||
CHECK (is_public = TRUE OR encrypted_key IS NOT NULL);
|
||||
|
||||
-- The inbox query: a recipient's tips, newest first.
|
||||
CREATE INDEX IF NOT EXISTS idx_social_tips_recipient
|
||||
ON social_tips (recipient_user_id, created_at DESC)
|
||||
WHERE recipient_user_id IS NOT NULL;
|
||||
Reference in New Issue
Block a user