Compare commits
1 commit
main
...
convoy/rec
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
77be685d2e |
1 changed files with 101 additions and 0 deletions
101
migrations/1781000000003_reconcile-collaboration-tables.js
Normal file
101
migrations/1781000000003_reconcile-collaboration-tables.js
Normal file
|
|
@ -0,0 +1,101 @@
|
|||
/**
|
||||
* Reconcile collaboration tables missed by the initial-schema backfill.
|
||||
*
|
||||
* Captures the DDL half of `scripts/add-collaboration-features.js` (a
|
||||
* historical no-go-zone script) that the `initial-schema` backfill missed:
|
||||
*
|
||||
* 1. `collection_permissions` (collaboration roles + invite tokens)
|
||||
* 2. `collection_activity` (audit-trail with JSONB details)
|
||||
* 3. `users.is_pending` (column for invited-but-not-yet-accepted users)
|
||||
* 4. 4 indexes for query hot-paths
|
||||
*
|
||||
* `lib/permission-middleware.js` reads `collection_permissions` in
|
||||
* `withCollectionPermission` and writes `collection_activity` from
|
||||
* `logCollectionActivity` — both are runtime invariants, so a fresh Neon
|
||||
* branch onboarded by `npm run setup-db` MUST land these tables. Prod was
|
||||
* brought to this shape by `add-collaboration-features.js` running
|
||||
* historically; this migration brings fresh envs to parity per
|
||||
* `.convoys/reconcile-historical-add-scripts.md` § Brief outline → B3.
|
||||
*
|
||||
* Idempotent against:
|
||||
* - A brand-new Neon branch (creates everything fresh).
|
||||
* - Any long-lived env where `add-collaboration-features.js` already ran
|
||||
* (every CREATE TABLE / ADD COLUMN / CREATE INDEX is `IF NOT EXISTS`-
|
||||
* guarded → no-op except recording the `pgmigrations` row).
|
||||
*
|
||||
* The owner-permission DML backfill from the historical script is NOT
|
||||
* captured: a fresh env has no pre-existing collections needing
|
||||
* back-fill, and prod's backfill is already applied.
|
||||
*
|
||||
* @type {import('node-pg-migrate').ColumnDefinitions | undefined}
|
||||
*/
|
||||
export const shorthands = undefined;
|
||||
|
||||
/**
|
||||
* @param {import('node-pg-migrate').MigrationBuilder} pgm
|
||||
* @returns {void}
|
||||
*/
|
||||
export const up = (pgm) => {
|
||||
pgm.sql(`
|
||||
CREATE TABLE IF NOT EXISTS collection_permissions (
|
||||
id SERIAL PRIMARY KEY,
|
||||
collection_id INTEGER REFERENCES collections(id) ON DELETE CASCADE,
|
||||
user_id INTEGER REFERENCES users(id) ON DELETE CASCADE,
|
||||
role VARCHAR(20) NOT NULL CHECK (role IN ('owner', 'editor', 'viewer')),
|
||||
status VARCHAR(20) DEFAULT 'active' CHECK (status IN ('active', 'pending', 'declined')),
|
||||
invite_token VARCHAR(255) UNIQUE,
|
||||
invited_by INTEGER REFERENCES users(id),
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
UNIQUE(collection_id, user_id)
|
||||
)
|
||||
`);
|
||||
|
||||
pgm.sql(`
|
||||
CREATE TABLE IF NOT EXISTS collection_activity (
|
||||
id SERIAL PRIMARY KEY,
|
||||
collection_id INTEGER REFERENCES collections(id) ON DELETE CASCADE,
|
||||
user_id INTEGER REFERENCES users(id) ON DELETE SET NULL,
|
||||
action VARCHAR(50) NOT NULL,
|
||||
details JSONB,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
)
|
||||
`);
|
||||
|
||||
pgm.sql(`
|
||||
ALTER TABLE users
|
||||
ADD COLUMN IF NOT EXISTS is_pending BOOLEAN DEFAULT false
|
||||
`);
|
||||
|
||||
pgm.sql(`
|
||||
CREATE INDEX IF NOT EXISTS idx_collection_permissions_collection_id
|
||||
ON collection_permissions(collection_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_collection_permissions_user_id
|
||||
ON collection_permissions(user_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_collection_activity_collection_id
|
||||
ON collection_activity(collection_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_collections_visibility
|
||||
ON collections(visibility);
|
||||
`);
|
||||
};
|
||||
|
||||
/**
|
||||
* Down-migration is a hard stub. Dropping `collection_permissions` /
|
||||
* `collection_activity` would erase every collaboration role assignment
|
||||
* and every audit-trail row in the database; dropping `users.is_pending`
|
||||
* would discard the invited-user state for every pending invite. If a
|
||||
* future schema correction needs to mutate one of these surfaces, write a
|
||||
* NEW dated migration with a real `down` — do NOT remove this stub.
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
export const down = () => {
|
||||
throw new Error(
|
||||
'[migration:1781000000003_reconcile-collaboration-tables] Refusing to drop ' +
|
||||
'collection_permissions / collection_activity / users.is_pending. Rolling back ' +
|
||||
'this migration would erase every collaboration role assignment, every audit-trail ' +
|
||||
'row, and every invited-user pending state in the database. If you need a clean ' +
|
||||
'schema for testing, branch the Neon database and run `npm run migrate up` against ' +
|
||||
'the branch instead.'
|
||||
);
|
||||
};
|
||||
Loading…
Reference in a new issue