diff options
| author | srdusr <[email protected]> | 2026-03-13 23:44:00 +0200 |
|---|---|---|
| committer | srdusr <[email protected]> | 2026-03-13 23:44:00 +0200 |
| commit | e37b76136e49ac90bef841e318bceeef76a07129 (patch) | |
| tree | 2b99369bceae6332965c91f8656e7e457431bdf0 /scripts | |
| parent | 4ffaeda5cf0da756ea05b6d13913ce3d775990d6 (diff) | |
| download | typerpunk-e37b76136e49ac90bef841e318bceeef76a07129.tar.gz typerpunk-e37b76136e49ac90bef841e318bceeef76a07129.zip | |
Ship the store catalogue with the client
The store read its catalogue from the server and rendered nothing without
one: a heading, a sign-in prompt and an empty page, which reads as a fault
rather than as a shop that needs an account.
It now falls back to a copy that ships with the client, the same arrangement
the text packs already use: bundled here, served from the database, and the
served copy wins. With no server the store shows all 26 cosmetics, 4 bundles
and 6 merchandise items at their real prices.
Nothing pretends to work. The buy controls become a "Soon" label rather than
a button, because offline there is nothing to buy with and nowhere to sign in
to: sending someone to the account screen would be sending them to another
screen that needs the server they have not got. A line at the top says the
shop is not open yet.
scripts/export_store.js regenerates the file. Run it after changing prices or
adding items. It shells out to psql so it needs no dependencies, matching the
other scripts here.
Verified both ways. With no backend: 26 items, 4 bundles, 6 merch, 36 Soon
labels, no live buy controls, no page errors. With the backend running: the
same catalogue, 37 live buy controls, no Soon labels, no offline notice. 3
browser tests pass.
Diffstat (limited to 'scripts')
| -rw-r--r-- | scripts/export_store.js | 53 |
1 files changed, 53 insertions, 0 deletions
diff --git a/scripts/export_store.js b/scripts/export_store.js new file mode 100644 index 0000000..d69ac29 --- /dev/null +++ b/scripts/export_store.js @@ -0,0 +1,53 @@ +#!/usr/bin/env node +// Writes the store catalogue to web/src/data/store.json. +// +// The store reads its catalogue from the server. With no server the page +// rendered its heading over an empty shop, which looks like a fault rather +// than a feature that needs signing in. This file is the same fallback the +// text packs already use: bundled with the client, served from the database +// and merged over the top once a server exists. +// +// Run it after changing prices or adding items: +// node scripts/export_store.js +// +// It shells out to psql so it needs no node dependencies, matching the rest +// of the scripts here. +const { execFileSync } = require('node:child_process'); +const { writeFileSync } = require('node:fs'); +const { join } = require('node:path'); + +const URL = process.env.DATABASE_URL + || 'postgresql://typerpunk:typerpunk@localhost/typerpunk'; + +function query(sql) { + const out = execFileSync('psql', [URL, '-At', '-c', sql], { encoding: 'utf8' }); + return JSON.parse(out.trim() || '[]'); +} + +const cosmetics = query(` + SELECT COALESCE(json_agg(row_to_json(c) ORDER BY c.category, c.price_cents), '[]'::json) + FROM (SELECT id, name, category, price_cents, value FROM cosmetics) c`); + +const bundles = query(` + SELECT COALESCE(json_agg(b ORDER BY b.sort_order), '[]'::json) FROM ( + SELECT b.id, b.name, b.description, b.price_cents, b.sort_order, + COALESCE(SUM(c.price_cents), 0)::int AS full_price_cents, + COALESCE(ARRAY_AGG(c.id ORDER BY c.id) + FILTER (WHERE c.id IS NOT NULL), '{}') AS items + FROM bundles b + LEFT JOIN bundle_items bi ON bi.bundle_id = b.id + LEFT JOIN cosmetics c ON c.id = bi.cosmetic_id + GROUP BY b.id, b.name, b.description, b.price_cents, b.sort_order + ) b`); + +const merch = query(` + SELECT COALESCE(json_agg(m ORDER BY m.sort_order), '[]'::json) FROM ( + SELECT id, name, description, price_cents, shipping_cents, kind, variants, sort_order + FROM merch WHERE available + ) m`); + +for (const row of [...bundles, ...merch]) delete row.sort_order; + +const out = join(__dirname, '..', 'web', 'src', 'data', 'store.json'); +writeFileSync(out, JSON.stringify({ cosmetics, bundles, merch }, null, 2) + '\n'); +console.log(`Wrote ${cosmetics.length} cosmetics, ${bundles.length} bundles and ${merch.length} merch items to ${out}`); |