Bündelt die Neuentwicklung in AssigmentMonitorV2 (eigene Ports/DB), die Umstellung auf numerische Bewertungen, Meeting-Checklisten, KI-Tagging und die geplante Feedback-Kaskade — als Basis für Versionsverwaltung in Gitea. Co-authored-by: Cursor <cursoragent@cursor.com>
75 lines
3.0 KiB
TypeScript
75 lines
3.0 KiB
TypeScript
import { DatabaseSync } from 'node:sqlite'
|
|
import { mkdirSync } from 'node:fs'
|
|
import { dirname, join, resolve, sep } from 'node:path'
|
|
import { fileURLToPath } from 'node:url'
|
|
import { TABLE_DEFINITIONS } from './db/tableRegistry'
|
|
|
|
const __dirname = dirname(fileURLToPath(import.meta.url))
|
|
const projectRoot = resolve(join(__dirname, '..'))
|
|
|
|
/** V2-Defaults weichen bewusst von Produktiv (assignment-monitor.sqlite3) ab. */
|
|
const sqliteFilename = process.env.SQLITE_FILENAME?.trim() || 'assignment-monitor-v2-dev.sqlite3'
|
|
if (sqliteFilename.includes('/') || sqliteFilename.includes('\\') || sqliteFilename.includes('..')) {
|
|
throw new Error(
|
|
`[db] SAFETY: SQLITE_FILENAME darf nur ein Dateiname sein (kein Pfad): "${sqliteFilename}"`,
|
|
)
|
|
}
|
|
|
|
const dbPath = resolve(join(__dirname, 'data', sqliteFilename))
|
|
|
|
/**
|
|
* Harte Absicherung: DB darf nur unter diesem V2-Projekt liegen und niemals unter
|
|
* dem Produktiv-Ordner c:\dev\AssigmentMonitor (ohne V2-Suffix).
|
|
*/
|
|
function assertDevDbPath(resolvedDbPath: string): void {
|
|
const dbNorm = resolvedDbPath.toLowerCase()
|
|
const rootNorm = projectRoot.toLowerCase()
|
|
const rootPrefix = rootNorm.endsWith(sep) ? rootNorm : rootNorm + sep
|
|
|
|
if (!dbNorm.startsWith(rootPrefix) && dbNorm !== rootNorm) {
|
|
throw new Error(
|
|
`[db] SAFETY: DB-Pfad liegt außerhalb von AssignmentMonitorV2.\n` +
|
|
` DB: ${resolvedDbPath}\n` +
|
|
` Root: ${projectRoot}`,
|
|
)
|
|
}
|
|
|
|
// Produktiv-Sibling: ...\AssigmentMonitor\server\data\... — V2-Pfad enthält "assigmentmonitorv2"
|
|
const looksLikeProdData =
|
|
/[/\\]assigmentmonitor[/\\]server[/\\]data[/\\]/.test(dbNorm) &&
|
|
!/[/\\]assigmentmonitorv2[/\\]/.test(dbNorm)
|
|
if (looksLikeProdData) {
|
|
throw new Error(
|
|
`[db] SAFETY: Versuch, die Produktiv-Datenbank zu öffnen wurde blockiert:\n ${resolvedDbPath}`,
|
|
)
|
|
}
|
|
}
|
|
|
|
assertDevDbPath(dbPath)
|
|
mkdirSync(dirname(dbPath), { recursive: true })
|
|
|
|
export const db = new DatabaseSync(dbPath)
|
|
|
|
/**
|
|
* `CREATE TABLE IF NOT EXISTS` legt eine fehlende Tabelle vollständig an, migriert aber niemals eine
|
|
* bereits bestehende — neue Spalten, die tableRegistry.ts für eine vorhandene Tabelle ergänzt (z.B.
|
|
* suggestedAchievements auf feedbackDimensionTexts, 2026-07-07), fehlen dort sonst dauerhaft und
|
|
* führen erst zur Laufzeit zu "no column named ..."-Fehlern. Deshalb zusätzlich per PRAGMA table_info
|
|
* abgleichen und fehlende Spalten per ALTER TABLE nachziehen.
|
|
*/
|
|
for (const table of TABLE_DEFINITIONS) {
|
|
const columnsSql = table.columns.map(c => `"${c.name}" ${c.sqlType}`).join(', ')
|
|
db.exec(`CREATE TABLE IF NOT EXISTS "${table.name}" (id INTEGER PRIMARY KEY AUTOINCREMENT, ${columnsSql})`)
|
|
|
|
const existingColumns = new Set(
|
|
(db.prepare(`PRAGMA table_info("${table.name}")`).all() as { name: string }[]).map(c => c.name),
|
|
)
|
|
for (const col of table.columns) {
|
|
if (!existingColumns.has(col.name)) {
|
|
db.exec(`ALTER TABLE "${table.name}" ADD COLUMN "${col.name}" ${col.sqlType}`)
|
|
}
|
|
}
|
|
}
|
|
|
|
console.log(`[db] DEV-SQLite bereit unter ${dbPath}`)
|