feat(server): port the UO models, utils and schema fragment
The data half of the extraction: 8 model directories, 13 utils, the shard
stream catalog and the 27-table schema fragment with its purge.
server/core.js is what makes the port a one-line import change per file rather
than a signature change per function. Ported code requires its dependencies at
file scope -- `const { query } = require('../../core')` -- which runs before
register() has been called and before any ctx exists. So every member is a
stable function that resolves ctx when CALLED, and nothing may be destructured
off ctx at init either, because core is free to hand over a getter.
Two helpers are vendored rather than taken from ctx, and the line between them
is the point. utils/excerpt.js is core's deriveExcerpt -- nine lines of pure
text handling. Core's sanitiser next to it was NOT copied: a second copy of a
security control diverges silently the moment either is fixed. announceLinks.js
vendors legError and articleUrl the same way, but baseUrl could not be: core's
reads APP_BASE_URL, and §2.7 forbids a module reading core's environment, so it
comes off ctx.site.baseUrl.
The schema fragment is core's 27 shard_*/uo_link_* statements, verbs CREATE,
ALTER and UPDATE only, every CREATE TABLE guarded. Two of its tables carry a
foreign key INTO users, which is allowed and is why the replay order matters --
core's schema is in place before this runs. The reverse never occurs and must
not: it would make core unable to boot without a module installed.
One real port bug caught by the integration run, not by tests: the atlas art
map resolved `../../../db/data`, which pointed at core's tree when this file
lived there and points outside server/ now. A path that happens to resolve is
exactly what survives a green suite, because the absent-file branch returns {}
and looks like the normal case.
Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
35
server/model/singletonConfigDb.js
Normal file
35
server/model/singletonConfigDb.js
Normal file
@@ -0,0 +1,35 @@
|
||||
const { query } = require('../core')
|
||||
|
||||
// Factory for the singleton config tables (bot_config, email_config,
|
||||
// uo_link_config). Each is a one-row table keyed on id = 1: `get()` returns the
|
||||
// row (or null before the admin first saves it), and `upsert()` writes only the
|
||||
// columns the model prepared, leaving the rest untouched. The three tables share
|
||||
// this shape exactly, so the DB layer is generated rather than copy-pasted —
|
||||
// only the table name and column list differ.
|
||||
//
|
||||
// `fields` are already prepared by the model (secrets pre-encrypted); ordering
|
||||
// and encryption stay a model-layer concern.
|
||||
function singletonConfigDb(table, cols) {
|
||||
async function get() {
|
||||
const rows = await query(`SELECT ${cols} FROM ${table} WHERE id = 1 LIMIT 1`)
|
||||
return rows[0] || null
|
||||
}
|
||||
|
||||
async function upsert(fields) {
|
||||
const columns = Object.keys(fields)
|
||||
const vals = columns.map((c) => fields[c])
|
||||
const insertCols = ['id', ...columns].map((c) => `\`${c}\``).join(', ')
|
||||
const placeholders = ['1', ...columns.map(() => '?')].join(', ')
|
||||
const updates = columns.map((c) => `\`${c}\` = VALUES(\`${c}\`)`).join(', ')
|
||||
await query(
|
||||
`INSERT INTO ${table} (${insertCols}) VALUES (${placeholders})
|
||||
ON DUPLICATE KEY UPDATE ${updates}`,
|
||||
vals,
|
||||
)
|
||||
return get()
|
||||
}
|
||||
|
||||
return { get, upsert }
|
||||
}
|
||||
|
||||
module.exports = singletonConfigDb
|
||||
Reference in New Issue
Block a user