@@ -0,0 +1,71 @@ |
| | 1 | +-- SPDX-License-Identifier: AGPL-3.0-or-later |
| | 2 | +-- |
| | 3 | +-- Canonical event log. The S26 spec called for a generic `events` |
| | 4 | +-- table; the S00-S25 audit's forward-plan finding flagged that S29 |
| | 5 | +-- (notifications) and S33 (webhooks) would also want their own log |
| | 6 | +-- table. Per the audit's recommendation we land the unified shape |
| | 7 | +-- here from day 1 — `domain_events` — so S29 and S33 don't have to |
| | 8 | +-- migrate the schema later. |
| | 9 | +-- |
| | 10 | +-- Schema columns: |
| | 11 | +-- actor_user_id — who did it (NULL for system events) |
| | 12 | +-- kind — short string identifying the event type |
| | 13 | +-- ("star", "unstar", "issue_comment_created", …) |
| | 14 | +-- repo_id — the repo the event is scoped to (NULL for |
| | 15 | +-- user-scoped events; reserved for org-scoped |
| | 16 | +-- events when S30 lands) |
| | 17 | +-- source_kind — what kind of object is the source/target |
| | 18 | +-- ("repo", "issue", "pull", "user", …) |
| | 19 | +-- source_id — the source object's id |
| | 20 | +-- public — whether this event is visible in public feeds. |
| | 21 | +-- Public-repo events default true; private-repo |
| | 22 | +-- events default false; user-scoped events follow |
| | 23 | +-- the user's profile-visibility setting. |
| | 24 | +-- payload — event-specific JSON. Keep small (<4 KiB); |
| | 25 | +-- bigger payloads belong in the source object |
| | 26 | +-- (referenced via source_kind/id). |
| | 27 | +-- |
| | 28 | +-- Read patterns: |
| | 29 | +-- * notifications fan-out (S29) consumes by polling on |
| | 30 | +-- created_at >= last_processed. |
| | 31 | +-- * webhooks (S33) the same. |
| | 32 | +-- * activity feeds (post-MVP) read public events sliced by repo |
| | 33 | +-- or by actor. |
| | 34 | + |
| | 35 | +-- +goose Up |
| | 36 | +CREATE TABLE domain_events ( |
| | 37 | + id bigserial PRIMARY KEY, |
| | 38 | + actor_user_id bigint REFERENCES users(id) ON DELETE SET NULL, |
| | 39 | + kind text NOT NULL, |
| | 40 | + repo_id bigint REFERENCES repos(id) ON DELETE CASCADE, |
| | 41 | + source_kind text NOT NULL, |
| | 42 | + source_id bigint NOT NULL, |
| | 43 | + public boolean NOT NULL DEFAULT false, |
| | 44 | + payload jsonb NOT NULL DEFAULT '{}'::jsonb, |
| | 45 | + created_at timestamptz NOT NULL DEFAULT now(), |
| | 46 | + |
| | 47 | + CONSTRAINT domain_events_kind_length CHECK (char_length(kind) BETWEEN 1 AND 64), |
| | 48 | + CONSTRAINT domain_events_source_kind_length CHECK (char_length(source_kind) BETWEEN 1 AND 32) |
| | 49 | +); |
| | 50 | + |
| | 51 | +-- Notifications/webhooks consumers poll by created_at; partial-on |
| | 52 | +-- created_at would gain little since both consumers process every row. |
| | 53 | +CREATE INDEX domain_events_created_at_idx ON domain_events (created_at); |
| | 54 | + |
| | 55 | +-- Activity feed: events for a repo, recency-sorted. |
| | 56 | +CREATE INDEX domain_events_repo_created_idx |
| | 57 | + ON domain_events (repo_id, created_at DESC) |
| | 58 | + WHERE repo_id IS NOT NULL; |
| | 59 | + |
| | 60 | +-- Activity feed: events by an actor, recency-sorted. |
| | 61 | +CREATE INDEX domain_events_actor_created_idx |
| | 62 | + ON domain_events (actor_user_id, created_at DESC) |
| | 63 | + WHERE actor_user_id IS NOT NULL; |
| | 64 | + |
| | 65 | +-- Public-feed slice: only public rows, recency-sorted. |
| | 66 | +CREATE INDEX domain_events_public_created_idx |
| | 67 | + ON domain_events (created_at DESC) |
| | 68 | + WHERE public = true; |
| | 69 | + |
| | 70 | +-- +goose Down |
| | 71 | +DROP TABLE IF EXISTS domain_events; |