DB Import und Export sowie Backup und kleinere Fehlerbeseitigung
This commit is contained in:
1 parent
91cc898ec7
commit
1c176ea621
24 files changed
+1081
-96
No files matched your search
@@ -42,6 +42,9 @@ user-facing text.
|
||||
│ │ ├── showmode.js # Show-Mode (Präsentationsmodus)
|
||||
│ │ ├── tabs-auth.js # showTab, confirmPassword (SHA256-Passwortschutz)
|
||||
│ │ ├── pdf.js # PDF-Export (pdfmake, A3 quer)
|
||||
│ │ ├── csv.js # exportCSV (lädt ZIP von GET /export-csv)
|
||||
│ │ ├── backup.js # downloadBackup (lädt SQLite-Snapshot von GET /backup)
|
||||
│ │ ├── import.js # importDb (Backup-Wiederherstellung über POST /import, passwortgeschützt)
|
||||
│ │ └── init.js # renderAll/initUiState + Seiteneinstieg (nur Test.html)
|
||||
│ │ ├── board/ # tab-specific: Projektboard
|
||||
│ │ │ └── board.js # Projektboard-Rendering, Status-Editor, Anwesenheit, Zuordnung
|
||||
@@ -94,13 +97,44 @@ docker compose up --build # Docker variant (compose file is at repo root)
|
||||
(`/js/...`, `/styles.css`) so they resolve on nested routes.
|
||||
- `GET /load-all?department=` → `{weeks:[weekObj], people:[personObj]}` with the
|
||||
global people list in `people`; weeks are full JSON week objects (id, label,
|
||||
date, locked, people, attendance, projects), sorted by label.
|
||||
- `POST /save-week {department, week}` → upserts one week (JSON object, keyed by
|
||||
department + label). Used by `sendWeekToServer()`.
|
||||
date, locked, updatedAt, people, attendance, projects), sorted by label.
|
||||
- `POST /save-week {department, week, baseUpdatedAt}` → upserts one week (JSON
|
||||
object, keyed by department + label). Used by `sendWeekToServer()`. The client
|
||||
sends its local revision in `baseUpdatedAt` (the `updatedAt` value from
|
||||
`load-all`); the server compares it to `weeks.updated_at` before writing and
|
||||
answers `409 {ok:false, conflict:true, error, updatedAt}` if someone else
|
||||
saved the week in the meantime. A save with a **non-empty** base for a week
|
||||
the server no longer knows is also rejected with 409 ("wurde gelöscht") so a
|
||||
deleted week can't silently resurrect; only new weeks (empty/missing base)
|
||||
pass. On success it returns `{ok:true, updatedAt}` (the new revision), which
|
||||
the client stores as the new sync base. Clients WITHOUT `baseUpdatedAt` (old
|
||||
versions) always pass. The check runs synchronously in-process before the
|
||||
write transaction (no race between check and write).
|
||||
- `POST /save-people {department, people}` → replaces the department's global
|
||||
people list (JSON array).
|
||||
- `POST /delete-week {department, label}` → deletes the week; missing label
|
||||
yields 400, missing week tolerates with `deleted:false`.
|
||||
- `GET /export-csv?department=` → ZIP with one readable CSV per logical entity
|
||||
(wochen, personen, sitzungsteilnehmer, projekte, zuordnungen,
|
||||
leistungsphasen, aktuelle-leistungsphasen, aufwand-je-leistungsphase,
|
||||
projektplaene, todos, meilensteine) for that department. CSV: UTF-8 with BOM,
|
||||
CRLF, `;` separator by default (overridable via `?sep=`; German Excel opens
|
||||
it directly). ZIP is built in pure Node (`zlib.deflateRawSync`, manual CRC32,
|
||||
no external library). Used by `exportCSV()` in `js/csv.js`.
|
||||
- `GET /backup` → consistent SQLite snapshot (`VACUUM INTO`, WAL-safe) as a
|
||||
`.db` file download (temp file in `os.tmpdir()`, deleted after send). Used by
|
||||
`downloadBackup()` in `js/backup.js`. Additionally, on server startup the
|
||||
server writes a timestamped copy to `sessions/backups/` (also via
|
||||
`VACUUM INTO`) and keeps only the `MAX_BACKUPS` newest ones (default 10).
|
||||
- `POST /import` → restores the whole database from a `.db` backup file sent as
|
||||
a raw binary body (`express.raw`, limit 100 MB). Validates the schema
|
||||
(`pragma_table_info` on every table, exact column match vs. live DB), then
|
||||
swaps all data via `ATTACH DATABASE …` + DELETE/INSERT-SELECT inside one
|
||||
transaction (rollback on error). Immediately BEFORE the restore the server
|
||||
saves a fresh auto-backup of the current state via `runAutoBackup()` (so a
|
||||
restore can be undone), then performs the swap. Used by `importDb()` in
|
||||
`js/import.js`, which is password-gated (`requestedAction` type `'importDb'`
|
||||
in `js/tabs-auth.js`) like week deletion.
|
||||
- Valid departments hard-coded as `ELT|HLS|ARCH` in `server.js`
|
||||
(`VALID_DEPARTMENTS`); anything else silently falls back to `ELT`. Same set in
|
||||
`DEPARTMENTS` in `js/globals.js`.
|
||||
@@ -112,7 +146,9 @@ docker compose up --build # Docker variant (compose file is at repo root)
|
||||
- Data lives in `sessions/projektuebersicht.db` (`node:sqlite` `DatabaseSync`,
|
||||
WAL mode, `PRAGMA foreign_keys = ON`), created automatically. The schema is
|
||||
**fully relational** (18 tables) instead of one big JSON document per week:
|
||||
- `weeks` (department, label, week_id, date, locked; PK `(department,label)`)
|
||||
- `weeks` (department, label, week_id, date, locked, updated_at; PK
|
||||
`(department,label)`) — `updated_at` is the revision timestamp for the
|
||||
single-user conflict handling (see `/save-week` above)
|
||||
- `week_people` / `week_people_roles` — person copies per week (`array_order`
|
||||
keeps the list order, `role_order` the role array order)
|
||||
- `attendance` — présence per week/person
|
||||
@@ -129,12 +165,16 @@ docker compose up --build # Docker variant (compose file is at repo root)
|
||||
tables inside a transaction, `loadWeeks()` / `loadGlobalPeople()`
|
||||
(re)assemble them on read. `saveWeekRelational()` deletes the week's rows
|
||||
first (`deleteWeekData()`, note `weeks` uses `label`, all child tables
|
||||
`week_label`).
|
||||
`week_label`) and returns the new `updated_at` revision written by
|
||||
`serializeWeek()`.
|
||||
- Migration on every start (idempotent):
|
||||
`migrateLegacySchema()` converts a DB from the old single-JSON-document format
|
||||
(detected via a `data` column in `weeks`): it reads the documents, drops all
|
||||
tables, recreates the relational schema and inserts everything in one
|
||||
transaction.
|
||||
transaction. Afterwards `migrateWeeksUpdatedAt()` adds the `updated_at`
|
||||
column to `weeks` (idempotent, `ALTER TABLE … ADD COLUMN`).
|
||||
Note: legacy rows keep `updated_at = ''`, so the first client save with an
|
||||
empty `baseUpdatedAt` matches and only then a real revision is written.
|
||||
- The DB file sits in `sessions/`, so the existing Docker bind-mount
|
||||
`./sessions:/app/sessions` keeps it persistent. The path is overridable via
|
||||
the `DB_PATH` environment variable (used for tests), production default is
|
||||
@@ -166,10 +206,21 @@ docker compose up --build # Docker variant (compose file is at repo root)
|
||||
`openProjectPlan()` builds `/HLS/Projektplan/<ProjName>?weekLabel=…&projNr=…`,
|
||||
`findProjectForPlan` resolves them. Person IDs follow
|
||||
`personIdForName(name)` → `p_<lowercased alnum>`.
|
||||
- `save()` debounces 500 ms then `syncAllToServer()` writes **all** weeks (via
|
||||
`sendWeekToServer()`) plus the people list (via `sendPeopleToServer()`) — not
|
||||
just the changed week. Exception: `js/plan/plan-state.js` `savePlan()` bypasses
|
||||
`save()` and writes only the current week directly via `sendWeekToServer()`.
|
||||
- `save()` debounces 500 ms then `syncAllToServer()` writes **only dirty weeks**
|
||||
(`dirtyWeeks` – track `markWeekDirty()`) via `sendWeekToServer()`, plus the
|
||||
people list `markPeopleDirty()` via `sendPeopleToServer()` if marked dirty.
|
||||
An in-flight guard (`syncInFlight`/`syncQueued`) prevents overlapping syncs
|
||||
from double-POSTing the same week with the same revision (which would look
|
||||
like a spurious 409). On success the server-provided `updatedAt` revision is
|
||||
adopted as the new sync base; weeks whose save failed (network errors) stay
|
||||
dirty and are retried. A `409` conflict response drops the local changes for
|
||||
that week and `handleWeekConflict()` reloads **only that week** from the
|
||||
server (`reloadWeekFromServer()`), leaving all other dirty weeks and the
|
||||
people list untouched. Exception: `js/plan/plan-state.js` `savePlan()`
|
||||
bypasses `save()` and writes only the current week directly via
|
||||
`sendWeekToServer()` (with the same in-flight/trailing-save guard), adopting
|
||||
the returned `updatedAt` and showing a toast on conflict. `switchWeek()` (in
|
||||
`js/weeks.js`) does NOT trigger a save – only actual edits do.
|
||||
- `switchDepartment()` cancels a pending save debounce before switching, and
|
||||
`load()` discards stale `/load-all` responses after a department switch
|
||||
(frozen `requestedDepartment`), so late responses can never render or persist
|
||||
|
||||
Reference in new issue
Block a user