Files
SnapOtter/apps/api/package.json
T
SnapOtterandGitHub dadf766899 fix(migrator): correct and harden the 1.x to 2.0 SQLite import (#434)
* feat(api): parse DATA_DIR from env for 1.x import auto-detection

Claude-Session: https://claude.ai/code/session_01721WHAUGxnVk22qEeTub7w

* test(migrator): build 1.17.2 fixtures by replaying legacy migrations

Discovered the legacy migrations seed a Default team (0005) and builtin roles
(0007), so the replayed fixture carries them. Seed uses a distinct custom team.

Claude-Session: https://claude.ai/code/session_01721WHAUGxnVk22qEeTub7w

* fix(migrator): self-adjusting column copy, jobs.status map, drop sessions, advisory lock

The importer now inserts only the intersection of source and live target columns,
so the three analytics_* columns 2.x dropped no longer break the first users INSERT
(and future dropped columns are handled generically). jobs.status is mapped onto the
2.x enum (error->failed). Sessions are no longer migrated. A pg_advisory_xact_lock
serializes concurrent replicas. Includes login-after-migrate and library assertions.

Claude-Session: https://claude.ai/code/session_01721WHAUGxnVk22qEeTub7w

* test(migrator): CI drift guard fails when a required column is unfillable from 1.17.2

Introspects every NOT-NULL-no-default column of each migrated table in the current
schema and asserts the engine can fill it from a real 1.17.2 source. Turns a future
breaking schema change into a PR-time failure instead of a production import break.

Claude-Session: https://claude.ai/code/session_01721WHAUGxnVk22qEeTub7w

* feat(migrator): orchestrator with detection, boot states, marker, blob count

sqlite-import.ts owns source resolution (explicit path, 'off' sentinel, DATA_DIR
probe), the four boot states (import/leftover/locked/none), the persisted
sqlite_import marker, and a read-only library-blob count. runBootImport wires them
together and catches TargetNonEmptyError as a benign multi-replica skip.

Claude-Session: https://claude.ai/code/session_01721WHAUGxnVk22qEeTub7w

* feat(api): route boot through the 1.x import orchestrator; hide marker from non-admins

index.ts now calls runBootImport (which owns detection + the four boot states)
instead of the inline SQLITE_MIGRATE_PATH block. The sqlite_import marker is added
to SENSITIVE_KEYS (but not REDACTED_KEYS) so admins see the counts for the banner
while non-admins don't see the key at all.

Claude-Session: https://claude.ai/code/session_01721WHAUGxnVk22qEeTub7w

* feat(migrator): add analyzeSqlite + dry-run/verify CLI

analyzeSqlite is a read-only pre-flight (no live Postgres): per-table row counts,
library-blob presence, and out-of-enum job statuses. The migrate:sqlite CLI now
lives in the orchestrator and supports --dry-run/--verify (prints the analysis and
exits without writing) alongside the existing import and --force.

Claude-Session: https://claude.ai/code/session_01721WHAUGxnVk22qEeTub7w

* docs: add 1.x to 2.0 upgrade guide; fix volume-name casing

New apps/docs upgrade guide covering auto-detect, the SQLITE_MIGRATE_PATH override +
off opt-out, the dry-run, what carries over, locked-state recovery, and non-destructive
rollback. Leads with 'back up the WHOLE /data volume, not just snapotter.db' because
1.x WAL mode leaves data in snapotter.db-wal (surfaced by the real-image upgrade test).
Standardizes README/DOCKERHUB compose volume names on the canonical SnapOtter-data
casing so they match the repo compose and don't orphan an upgrader's volume.

Claude-Session: https://claude.ai/code/session_01721WHAUGxnVk22qEeTub7w

* feat(web): admin 1.x migration banner + 21-locale strings

A one-time admin banner reads the sqlite_import marker from /v1/settings and shows
the import result (user + saved-file counts) on success, or a warning when a 1.x
database was found but not imported. Dismissal persists to a sqlite_import.dismissedAt
settings key. shouldShowMigrationBanner/parseMigrationMarker sit in feedback.ts with
the other shouldShow helpers; strings added to en.ts and all 20 other locales.

Claude-Session: https://claude.ai/code/session_01721WHAUGxnVk22qEeTub7w

* style(landing): biome-format Hero.astro trustBadges array

Pre-existing formatting drift on main (its Lint check was skipped on the merge that
introduced it); this PR's full Lint run surfaced it. Formatting-only, applied via
the repo's own biome formatter to unblock the required Lint check.

Claude-Session: https://claude.ai/code/session_01721WHAUGxnVk22qEeTub7w
2026-07-04 15:15:39 +00:00

97 lines
3.0 KiB
JSON

{
"name": "@snapotter/api",
"version": "2.0.0",
"private": true,
"type": "module",
"scripts": {
"dev": "PORT=13490 tsx watch --import ./src/tracing.ts --import ./src/instrument.ts src/index.ts",
"build": "tsc",
"start": "tsx --import ./src/tracing.ts --import ./src/instrument.ts src/index.ts",
"lint": "biome check src/",
"typecheck": "tsc --noEmit",
"clean": "rm -rf dist",
"migrate:sqlite": "tsx src/db/sqlite-import.ts"
},
"dependencies": {
"@fastify/busboy": "^3.2.0",
"@fastify/cookie": "^11.0.2",
"@fastify/cors": "^11.0.0",
"@fastify/multipart": "^9.0.0",
"@fastify/rate-limit": "^10.2.0",
"@fastify/static": "^9.1.3",
"@neplex/vectorizer": "^0.1.0",
"@node-saml/node-saml": "^5.1.0",
"@opentelemetry/api": "^1.9.1",
"@opentelemetry/exporter-trace-otlp-http": "^0.219.0",
"@opentelemetry/instrumentation-aws-sdk": "^0.74.0",
"@opentelemetry/instrumentation-fastify": "^0.57.0",
"@opentelemetry/instrumentation-http": "^0.219.0",
"@opentelemetry/instrumentation-ioredis": "^0.67.0",
"@opentelemetry/instrumentation-pg": "^0.71.0",
"@opentelemetry/resources": "^2.8.0",
"@opentelemetry/sdk-node": "^0.219.0",
"@opentelemetry/sdk-trace-base": "^2.8.0",
"@opentelemetry/semantic-conventions": "^1.41.1",
"@scalar/fastify-api-reference": "^1.62.0",
"@sentry/node": "^10.62.0",
"@snapotter/ai": "workspace:*",
"@snapotter/doc-engine": "workspace:*",
"@snapotter/enterprise": "workspace:*",
"@snapotter/image-engine": "workspace:*",
"@snapotter/media-engine": "workspace:*",
"@snapotter/shared": "workspace:*",
"archiver": "^7.0.1",
"better-sqlite3": "^11.7.0",
"bullmq": "^5.79.2",
"bwip-js": "^4.11.1",
"dotenv": "^16.4.0",
"drizzle-orm": "^0.45.2",
"exceljs": "^4.4.0",
"exif-reader": "^2.0.3",
"fast-xml-parser": "^5.9.3",
"fastify": "^5.9.0",
"fflate": "^0.8.3",
"ioredis": "^5.11.1",
"ipaddr.js": "^2.4.0",
"js-yaml": "^4.3.0",
"mupdf": "^1.27.0",
"openid-client": "^6.8.4",
"opentype.js": "^2.0.0",
"otpauth": "^9.5.1",
"p-queue": "^9.3.0",
"papaparse": "^5.5.4",
"pdfkit": "^0.19.1",
"pg": "^8.22.0",
"pino": "^10.3.1",
"pino-roll": "^4.0.0",
"playwright": "^1.61.1",
"posthog-node": "^5.38.6",
"potrace": "^2.1.8",
"prom-client": "^15.1.3",
"qrcode": "^1.5.4",
"sharp": "^0.35.2",
"tar": "^7.5.19",
"tsx": "^4.22.4",
"yauzl": "^3.4.0",
"zod": "^3.24.0",
"zxing-wasm": "^3.1.0"
},
"devDependencies": {
"@types/archiver": "^7.0.0",
"@types/better-sqlite3": "^7.6.0",
"@types/js-yaml": "^4.0.9",
"@types/node": "^22.20.0",
"@types/opentype.js": "^1.3.10",
"@types/papaparse": "^5.5.2",
"@types/pdfkit": "^0.17.6",
"@types/pg": "^8.20.0",
"@types/potrace": "^2.1.5",
"@types/qrcode": "^1.5.6",
"@types/tar": "^7.0.87",
"@types/yauzl": "^3.4.0",
"drizzle-kit": "^0.31.0",
"typescript": "^5.7.0"
},
"license": "AGPL-3.0"
}