feat(dares): optional photo/audio proof of completion

The sender can require photo or audio proof when sending any dare,
catalogue or custom. The recipient captures and uploads the proof as a
new couple-scoped 'dare_proof' media kind and attaches it on submit;
the server enforces ownership, kind, couple, and MIME-type match.
The proof renders inline on the dare card for the sender to review
before confirming completion.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
jjbruntonandClaude Fable 5 committed 2026-08-30 19:36:11 +01:00
1 parent f22d4a3649
commit 504eddaf68
21 files changed
+668 -28

No files matched your search

+1 -1
View File
@@ -595,7 +595,7 @@ Features observed in competitor apps (Desire, Kindu, Spicer, Lovewick):
| Decline Option | ✅ | No negative impact |
| Dare History | ✅ | Via `sent_dares` table |
| Chat per Dare | ✅ | `dare_messages` table |
| Proof/Verification | ❌ | Not in v1 (could add photo proof later) |
| Proof/Verification | ✅ | Optional photo/audio proof, chosen by the sender per dare |
| Daily/Random Dares | ❌ | Not in v1 (could add "random dare" button) |
| Dare Rewards | ❌ | Not in v1 (could unlock content) |
| Streak Tracking | ❌ | Not in v1 (could add later) |
+14 -3
View File
@@ -109,15 +109,26 @@ and validated server-side.
| GET | `/v1/dares?filter=active\|history` | Couple-scoped; `direction` is relative to the caller |
| GET | `/v1/dares/stats` | Shared scoreboard |
| GET | `/v1/dares/durations` | Preset list the send sheet renders |
| POST | `/v1/dares` | `dare_id` **or** `custom_dare_text`, never both |
| POST | `/v1/dares` | `dare_id` **or** `custom_dare_text`, never both; optional `proof_type` (`none`/`photo`/`audio`) |
| POST | `/v1/dares/:dareId/respond` | `{ action: "accept" \| "decline" }` |
| POST | `/v1/dares/:dareId/submit` | Recipient reports done |
| POST | `/v1/dares/:dareId/submit` | Recipient reports done; `{ proof_media_id }` required when the dare has a proof requirement |
| POST | `/v1/dares/:dareId/complete` | Sender confirms |
| POST | `/v1/dares/:dareId/cancel` | Sender withdraws |
The actor always comes from the bearer identity; ownership fields in a request body are
rejected.
## Proof of completion
The sender may optionally require proof when sending any dare, catalogue or custom:
`sent_dares.proof_type` is `'none'` (default), `'photo'`, or `'audio'`. The recipient
uploads the proof first (`POST /v1/media/dare_proof` — images and audio, 25MB cap,
couple-scoped so the sender can view it) and passes the resulting media id to `submit`.
The server enforces the requirement: a proofed dare cannot be submitted without media of
the matching type, and the media must be a `dare_proof` object owned by the recipient in
the same couple. Voluntary proof on a `'none'` dare is accepted. The proof renders inline
on the dare card (photo, or an audio player) via the standard signed-URL flow.
## Mobile
Reached from the home screen's `DaresTile`, which badges pending incoming dares. The
@@ -142,4 +153,4 @@ actually changed, or on an explicit refresh or a mutation. See
Per-dare chat. `dare_messages` exists and is wired into the schema, but a second inbox
split from the main match chat was cut in favour of status transitions plus the sender's
note. Photo proof, random-dare, and streaks remain unbuilt.
note. Random-dare and streaks remain unbuilt.