A text box invites an edit nobody is here to make. Named and created, the two
things somebody wants are per-database actions, so the inputs and the button go
and each database becomes a label, a table count, and what can be done to it.
Push schema runs the WEBSITE's own `db:push`. Not drizzle-kit driven from here
with a config this plugin assembles — the schema, the drizzle version and the
push command all belong to that repository, and reimplementing any of them would
be a second definition of the site's database going stale on its own schedule.
This supplies the connection and runs their script.
The connection goes in the environment, which works because src/env-file.ts
consults .env files first and process.env last. So an ambient value is used only
where no file in the checkout sets that key, and this cannot quietly override a
connection somebody wrote down.
Their name guard still applies and should: env.ts refuses to connect unless the
database name matches what FOFOS_ENV expects. A plugin that worked around that
would be defeating the one check standing between development and live data.
"Schema pushed" is a COUNT of tables in public, not a comparison against the
site's expected tables — that list would be a second copy of the site's schema,
stale the first time it gains a table, and the question being asked is only
whether anything is in there yet.
Restore is present and disabled, gated on the schema being pushed. It is not
wired: which backup it reads from is not decided, and guessing at the source of
a restore is not a thing to do with a client's data.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Two buttons was the wrong model, and it failed in the worst way: Create keyed
off the SAVED names, so on a fresh screen with both fields filled it did not
render at all — and the hint explaining why was behind the same condition. Two
filled fields, one Save, and nothing saying what came next.
Naming and creating are not separate decisions from the outside. You type two
names because you want two databases. So one button that records them and
creates whatever is missing, labelled with whichever of those it is about to do,
and disabled only when there is genuinely nothing to press.
The tick beside each field now asks pg_database about the text in the BOX rather
than the text last saved. That is the question somebody typing a name has, and
answering it about a different name was worse than silence.
The PUT that only stored names goes with the second button. Nothing called it,
and a second way to write the same two columns is a second thing that can drift.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Production and development, stored as NAMES rather than URLs. The site runs on
the same Postgres Officer is already connected to — that is the whole reason a
name is enough — and a URL here would be a second copy of a host, a port and a
credential that already exist in the platform's configuration, free to drift
from it and free to point at another machine.
Naming and creating are separate, and were worth two buttons. A database that
already exists still needs its name recorded, and making Create the only way to
save would leave nobody able to point the site at something already set up. The
tick beside each field comes from pg_database, not from what was typed — the
useful question is whether the thing exists, and that came apart from "somebody
saved a name" the moment the two actions split.
CREATE DATABASE cannot be parameterised, so the name goes into the statement as
text and the identifier check is the entire security boundary. It is therefore
strict rather than accommodating — lowercase, leading letter or underscore,
nothing else — and it is re-run immediately before the statement is built,
because the gap between storing a name and using it is exactly where a check
that happened earlier stops being a check. Postgres folds unquoted identifiers
to lowercase, so accepting `Fofos` would create `fofos` and then report a name
nobody could find; refusing is kinder than silently changing it.
Officer's own database and the Postgres templates are refused outright. Pointing
the site at Officer's database would not read as a configuration mistake — it
would read as the platform corrupting itself.
Nothing here drops a database, and nothing should: `fofos_website` and
`fofos_old` are one keystroke apart and one of them holds the client's site.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>