From a73dec7c13f0e73761bae9583669bb542d33ba06 Mon Sep 17 00:00:00 2001 From: Devin Zuczek Date: Wed, 5 Aug 2026 15:39:25 -0400 Subject: [PATCH] extract published field for convenience --- .../migrations/0008_invention_visibility.sql | 21 ++++++++++++++ apps/api/src/inventions-db.ts | 28 +++++++++++-------- 2 files changed, 37 insertions(+), 12 deletions(-) create mode 100644 apps/api/migrations/0008_invention_visibility.sql diff --git a/apps/api/migrations/0008_invention_visibility.sql b/apps/api/migrations/0008_invention_visibility.sql new file mode 100644 index 0000000..5727966 --- /dev/null +++ b/apps/api/migrations/0008_invention_visibility.sql @@ -0,0 +1,21 @@ +-- Break the two visibility flags out of the invention JSON blob into queryable +-- generated columns, the same way 0003 did for `IsFeatured`. `IsPublished` and +-- `HideFromPlayer` are always tested together — every feed, the search/browse list and +-- the per-room list ask for "published and not hidden" — so they move together. +-- Generated from src/inventions-db.ts (SCHEMA_DDL) — keep in sync. +-- +-- SQLite allows ALTER TABLE ADD COLUMN only for VIRTUAL generated columns (a STORED one +-- would need rewriting existing rows), which is what we want anyway: the value stays +-- derived from `data`, so nothing can drift out of sync with it. json_extract of a JSON +-- `true` is 1, so both columns read 1/0 — and NULL for a blob missing the key, which is +-- neither 1 nor 0 and so fails both filters exactly as the json_extract predicates it +-- replaces did. This is a rename, not a behaviour change. +-- +-- No index: both columns are booleans that are overwhelmingly one value (nearly every +-- invention is published and not hidden), so an index on them would be read past rather +-- than used. The selective one is idx_invention_featured, added in 0003, which stays. + +ALTER TABLE invention + ADD COLUMN is_published INTEGER GENERATED ALWAYS AS (json_extract(data, '$.IsPublished')) VIRTUAL; +ALTER TABLE invention + ADD COLUMN hide_from_player INTEGER GENERATED ALWAYS AS (json_extract(data, '$.HideFromPlayer')) VIRTUAL; diff --git a/apps/api/src/inventions-db.ts b/apps/api/src/inventions-db.ts index d3d1d3d..b5961a9 100644 --- a/apps/api/src/inventions-db.ts +++ b/apps/api/src/inventions-db.ts @@ -1,8 +1,8 @@ /** * Saved-invention storage on the shared `recflare` D1 database. Each invention is - * a single JSON blob in the `data` column; queryable fields (Id, CreatorPlayerId) - * are SQLite generated (virtual) columns extracted from that JSON — the same - * JSON-blob pattern the image/rooms/accounts tables use. + * a single JSON blob in the `data` column; queryable fields (Id, CreatorPlayerId, the + * visibility flags) are SQLite generated (virtual) columns extracted from that JSON — + * the same JSON-blob pattern the image/rooms/accounts tables use. * * The `api` worker owns this schema/migration (migrations/0002_invention.sql, * applied under its own `migrations_table`). The invention's data file itself is @@ -20,16 +20,20 @@ import { getOwnedInventionIds } from '@repo/domain' /** - * Schema DDL (mirror of migrations/0002_invention.sql + 0003_invention_featured.sql, - * sans any seed rows). `is_featured` backs the featured feed's query; json_extract - * of a JSON `true` is 1, so the column is 1/0. + * Schema DDL (mirror of migrations/0002_invention.sql + 0003_invention_featured.sql + + * 0008_invention_visibility.sql, sans any seed rows). `is_featured` backs the featured + * feed's query and `is_published`/`hide_from_player` the "may anyone see this" filter + * every feed shares; json_extract of a JSON `true` is 1, so those columns are 1/0 — and + * NULL when the key is missing, which fails a `= 1` or `= 0` test either way. */ export const SCHEMA_DDL: string[] = [ `CREATE TABLE IF NOT EXISTS invention ( data TEXT NOT NULL, id INTEGER GENERATED ALWAYS AS (json_extract(data, '$.InventionId')) VIRTUAL, creator_player_id INTEGER GENERATED ALWAYS AS (json_extract(data, '$.CreatorPlayerId')) VIRTUAL, - is_featured INTEGER GENERATED ALWAYS AS (json_extract(data, '$.IsFeatured')) VIRTUAL + is_featured INTEGER GENERATED ALWAYS AS (json_extract(data, '$.IsFeatured')) VIRTUAL, + is_published INTEGER GENERATED ALWAYS AS (json_extract(data, '$.IsPublished')) VIRTUAL, + hide_from_player INTEGER GENERATED ALWAYS AS (json_extract(data, '$.HideFromPlayer')) VIRTUAL )`, `CREATE UNIQUE INDEX IF NOT EXISTS idx_invention_id ON invention (id)`, `CREATE INDEX IF NOT EXISTS idx_invention_creator ON invention (creator_player_id)`, @@ -335,12 +339,12 @@ export async function searchInventions( * ones via the indexed `is_featured` column. */ async function publicInventions(db: D1Database, featuredOnly = false): Promise { - // json_extract of a JSON `true` is 1, so these filters stay in SQL. + // All three are generated columns off the JSON blob, so the filter stays in SQL. const { results } = await db .prepare( `SELECT data FROM invention - WHERE json_extract(data, '$.IsPublished') = 1 - AND json_extract(data, '$.HideFromPlayer') = 0 + WHERE is_published = 1 + AND hide_from_player = 0 ${featuredOnly ? 'AND is_featured = 1' : ''}` ) .all() @@ -614,8 +618,8 @@ export async function getInventionsByRoom( .prepare( `SELECT data FROM invention WHERE json_extract(data, '$.CreationRoomId') = ?1 - AND json_extract(data, '$.IsPublished') = 1 - AND json_extract(data, '$.HideFromPlayer') = 0` + AND is_published = 1 + AND hide_from_player = 0` ) .bind(roomId) .all()