two database names, and a button that creates what is missing
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>
This commit is contained in:
@@ -88,6 +88,20 @@ export const fofosAdminSettings = pgTable('fofos_admin_settings', {
|
||||
*/
|
||||
rootPath: text('root_path'),
|
||||
|
||||
/**
|
||||
* The two Postgres databases the site uses, by NAME.
|
||||
*
|
||||
* Names and not URLs, deliberately. They live on the same instance Officer is already connected to —
|
||||
* that is the whole reason this is a name — so a URL here would be a second copy of a host, a port
|
||||
* and a credential that already exist in the platform's own configuration, able to drift from it and
|
||||
* able to point somewhere nobody intended.
|
||||
*
|
||||
* Null means not chosen yet. There is no default: a database name is not something to guess at, and
|
||||
* a wrong guess here is a site reading somebody else's tables.
|
||||
*/
|
||||
dbProduction: text('db_production'),
|
||||
dbDevelopment: text('db_development'),
|
||||
|
||||
createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(),
|
||||
updatedAt: timestamp('updated_at', { withTimezone: true }).notNull().defaultNow(),
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user