Commit Graph

9 Commits

Author SHA1 Message Date
Riyadh 2ca2c8de44 Update caching headers to ensure all devices get the latest site version
Modify Nginx configuration to include aggressive caching headers for service worker and index.html to prevent stale content on various devices.
2026-05-19 20:17:47 +00:00
Riyadh 608e15cf91 Task #589: Live build stamp + server start time in System Updates panel
Problem: After `git pull && docker compose up -d --build api` on the
Mac, the admin → System Updates panel kept showing the same version
(0.1.0-dev) and the same build (20260517.8a5bd5db) because both come
from version.json, a hand-edited file that nothing rewrites on every
build. No visible signal that a new image was actually running.

Two independent signals were added — neither needs editing version.json
by hand:

1) Auto-stamp version.json at Docker build time
   - docker/api-server.Dockerfile: new ARGs GIT_SHA and BUILD_DATE.
     A `node -e` step rewrites version.json's `build` field to
     `${YYYYMMDDTHHmm}.${sha}` (e.g. 20260518T1042.71da8e01). When
     the args are missing/"unknown", version.json is left untouched
     so plain `docker build` and Replit `pnpm dev` still work.
   - docker-compose.yml: api.build.args wires through GIT_SHA and
     BUILD_DATE with `${GIT_SHA:-unknown}` fallbacks.
   - scripts/redeploy.sh: exports GIT_SHA (git rev-parse --short HEAD)
     and BUILD_DATE (date -u +%Y-%m-%dT%H:%M:%SZ) before `docker
     compose build`, so the helper script just works.

2) startedAt timestamp from the API
   - artifacts/api-server/src/routes/system.ts: SERVER_STARTED_AT
     captured at module load (frozen for the process lifetime) and
     added to every branch of GET /system/version (not-configured,
     error, invalid-version, up-to-date, update-available, catch).
   - lib/api-spec/openapi.yaml: added `startedAt: date-time` to
     SystemVersionResponse (required). Regenerated api-client-react
     and api-zod via `pnpm --filter @workspace/api-spec run codegen`.

3) Admin UI surfacing both signals
   - artifacts/tx-os/src/pages/admin.tsx: new formatBuildStamp parses
     `YYYYMMDD[THHmm].sha` and renders a localized date next to the
     raw token. A new "Server started" line below the build number
     formats the boot timestamp via the existing formatChecked helper.
   - artifacts/tx-os/src/locales/{en,ar}.json: added
     admin.systemUpdates.serverStartedAt ("Server started" /
     "بدأ تشغيل الخادم").

4) Docs
   - replit.md: documented how to verify a real redeploy via the
     System Updates panel and the manual one-liner for `docker
     compose build` without the helper script.

Verified: codegen succeeds, API workflow restarted clean, Vite served
fine. Pre-existing typecheck errors in push.ts and font-settings are
unrelated and untouched.

Mac next steps:
  cd ~/Downloads/TX && git pull && ./scripts/redeploy.sh
Then open admin → System Updates: build line should show a new
`YYYYMMDDTHHmm.sha` stamp and "Server started" should reflect the
new boot time.
2026-05-18 10:39:17 +00:00
Riyadh ff508eeaba Task #587: Fix Docker build — copy version.json into build stage
The api-server docker build failed on the Mac with:
  ERROR: Could not resolve "../../../../version.json"
  src/routes/system.ts:4

`version.json` lives at the repo root and is tracked in git, but
`docker/api-server.Dockerfile` never copied it into the `build` stage,
so esbuild (running in /app/artifacts/api-server) couldn't resolve the
`../../../../version.json` import added by commit cb894f7b.

Fix: added `COPY version.json ./` in the build stage, right after the
tsconfig copy and before `lib`/`scripts`/`artifacts` are copied. No
runtime change is needed — esbuild inlines the JSON into the bundle
via the `with { type: "json" }` import attribute, so the runtime stage
does not need the file on disk.

Local dev (`pnpm --filter @workspace/api-server run dev`) is
unaffected because it builds outside Docker against the real repo
root. tx-os Dockerfile not touched.

Mac side after pulling this commit:
  cd ~/Downloads/TX && git pull && docker compose up -d --build api
2026-05-18 09:54:14 +00:00
Riyadh 7f86db26db Add bilingual support for notifications and automatic HTTPS
Implement bilingual text for push notifications, allowing users to receive them in their preferred language. Automatically configure HTTPS with Let's Encrypt for custom domains to ensure persistent subscriptions.
2026-05-16 16:28:42 +00:00
Riyadh d1d4f6baaa feat(setup): Stage 1 first-time setup wizard backend (no UI)
Task #534 — backend, infra, tooling. UI ships in Stage 2 (#535).

Backend
- New system_settings table (id=1 singleton): installed flag, base_url,
  local_domain, local_ip, https_mode, app_version. Pushed to dev DB.
- New /api/setup/status (open) and /api/setup/{validate,complete}
  (gated by requireSetupOpen — 409 once installed).
- completeInstall is fully transactional: pg_advisory_xact_lock
  serializes concurrent callers, double-gates on installed flag and
  admin existence, then atomically creates the admin user, assigns
  admin role + Admins/Everyone groups, and flips system_settings to
  installed=true. Rolls back on any failure.
- Added redirectIfSetupNeeded() helper returning the full SetupStatus
  payload alongside a redirect target for SPA routing decisions.
- Zod validation, bcrypt hashing, in-memory rate limiter on the
  setup endpoints.

Backward compat
- scripts/src/seed.ts now branches on installed flag + admin
  existence + SEED_*_PASSWORD env vars. Legacy installs (admin
  exists, system_settings empty) get backfilled to installed=true
  via ON CONFLICT DO UPDATE so they are never forced through the
  wizard. When env passwords are unset and no admin exists, the
  seed prints a wizard hint instead of seeding.

Infra
- docker-compose.yml: replaced nginx edge with a Caddy service that
  mounts ./certs and ./docker/Caddyfile{,.skip}. The web service no
  longer publishes a port directly — Caddy is the only public ingress.
- Caddy entrypoint picks Caddyfile.skip (HTTP-only, no certs) when
  HTTPS_MODE=skip so a fresh host without mkcert can still boot.
- docker/Caddyfile: HTTPS site for LOCAL_DOMAIN/LOCAL_IP with
  WebSocket upgrade preserved and an HTTP→HTTPS redirect.
- start.sh: preserved. Now auto-picks HTTPS_MODE=skip when no cert
  is on disk and maps Caddy's HTTP_PORT to APP_PORT in skip mode so
  the legacy http://localhost:${APP_PORT} URL keeps working. In
  local/byo mode it prints the https://${LOCAL_DOMAIN} URL.
- .env.example: added LOCAL_DOMAIN, LOCAL_IP, BASE_URL, HTTP_PORT,
  HTTPS_PORT, HTTPS_MODE; SEED_*_PASSWORD now optional.

Tooling
- scripts/local-setup.sh: idempotent OS-aware bootstrap (.env upsert,
  mkcert hint, cert SAN check, dry-run via LOCAL_SETUP_DRY_RUN).

Tests
- artifacts/api-server/tests/setup-wizard.test.mjs: 7/7 pass.
- scripts/tests/local-setup.test.mjs: 2/2 pass.

Constraints honored: no force-push, no destructive ops, start.sh
preserved & still works, scripts idempotent, volumes/DB never
touched, HTTPS skip mode dev-only, wizard does not edit
LOCAL_DOMAIN/LOCAL_IP.

Out of scope / not addressed: pre-existing TS errors in
routes/users.ts and pre-existing failure in
executive-meetings-postpone-race.test.mjs.
2026-05-14 07:49:54 +00:00
Riyadh 3a698aeca8 feat(setup): Stage 1 first-time setup wizard backend (no UI)
Task #534 — backend, infra, tooling. UI ships in Stage 2 (#535).

Backend
- New system_settings table (id=1 singleton): installed flag, base_url,
  local_domain, local_ip, https_mode, app_version. Pushed to dev DB.
- New /api/setup/status (open) and /api/setup/{validate,complete}
  (gated by requireSetupOpen — 409 once installed).
- completeInstall is fully transactional: pg_advisory_xact_lock
  serializes concurrent callers, double-gates on installed flag and
  admin existence, then atomically creates the admin user, assigns
  admin role + Admins/Everyone groups, and flips system_settings to
  installed=true. Rolls back on any failure.
- Added redirectIfSetupNeeded() helper returning the full SetupStatus
  payload alongside a redirect target for SPA routing decisions.
- Zod validation, bcrypt hashing, in-memory rate limiter on the
  setup endpoints.

Backward compat
- scripts/src/seed.ts now branches on installed flag + admin
  existence + SEED_*_PASSWORD env vars. Legacy installs (admin
  exists, system_settings empty) get backfilled to installed=true
  via ON CONFLICT DO UPDATE so they are never forced through the
  wizard. When env passwords are unset and no admin exists, the
  seed prints a wizard hint instead of seeding.

Infra
- docker-compose.yml: replaced nginx edge with a Caddy service that
  mounts ./certs and ./docker/Caddyfile{,.skip}. The web service no
  longer publishes a port directly — Caddy is the only public ingress.
- Caddy entrypoint picks Caddyfile.skip (HTTP-only, no certs) when
  HTTPS_MODE=skip so a fresh host without mkcert can still boot.
- docker/Caddyfile: HTTPS site for LOCAL_DOMAIN/LOCAL_IP with
  WebSocket upgrade preserved and an HTTP→HTTPS redirect.
- start.sh: preserved. Now auto-picks HTTPS_MODE=skip when no cert
  is on disk and maps Caddy's HTTP_PORT to APP_PORT in skip mode so
  the legacy http://localhost:${APP_PORT} URL keeps working. In
  local/byo mode it prints the https://${LOCAL_DOMAIN} URL.
- .env.example: added LOCAL_DOMAIN, LOCAL_IP, BASE_URL, HTTP_PORT,
  HTTPS_PORT, HTTPS_MODE; SEED_*_PASSWORD now optional.

Tooling
- scripts/local-setup.sh: idempotent OS-aware bootstrap (.env upsert,
  mkcert hint, cert SAN check, dry-run via LOCAL_SETUP_DRY_RUN).

Tests
- artifacts/api-server/tests/setup-wizard.test.mjs: 7/7 pass.
- scripts/tests/local-setup.test.mjs: 2/2 pass.

Constraints honored: no force-push, no destructive ops, start.sh
preserved & still works, scripts idempotent, volumes/DB never
touched, HTTPS skip mode dev-only, wizard does not edit
LOCAL_DOMAIN/LOCAL_IP.

Out of scope / not addressed: pre-existing TS errors in
routes/users.ts and pre-existing failure in
executive-meetings-postpone-race.test.mjs.
2026-05-14 07:36:52 +00:00
Riyadh b5b1c554b9 feat(setup): Stage 1 first-time setup wizard backend (no UI)
Task #534 — backend, infra, and tooling only. UI ships in Stage 2.

Backend
- New system_settings table (id=1 singleton): installed flag, base_url,
  local_domain, local_ip, https_mode, app_version. Pushed to dev DB.
- New /api/setup/status (open) and /api/setup/{validate,complete}
  (gated by requireSetupOpen — 409 once installed).
- completeInstall is fully transactional: pg_advisory_xact_lock
  serializes concurrent callers, double-gates on installed flag and
  admin existence, then atomically creates the admin user, assigns
  admin role + Admins/Everyone groups, and flips system_settings to
  installed=true. Rolls back on any failure.
- Zod validation, bcrypt hashing, in-memory rate limiter for the
  setup endpoints.

Backward compat
- scripts/src/seed.ts now branches on installed flag + admin
  existence + SEED_*_PASSWORD env vars. Legacy installs (admin
  exists, system_settings empty) get backfilled to installed=true
  via ON CONFLICT DO UPDATE so they are never forced through the
  wizard. When env passwords are unset and no admin exists, the
  seed prints a wizard hint instead of seeding.

Infra
- docker-compose.yml: replaced nginx edge with a Caddy service that
  mounts ./certs and ./docker/Caddyfile. The web service no longer
  publishes a port directly — Caddy is the only public ingress.
- docker/Caddyfile: HTTPS site for LOCAL_DOMAIN/LOCAL_IP with
  WebSocket upgrade preserved and a plaintext :80 fallback when
  HTTPS_MODE=skip (dev-only).
- .env.example: added LOCAL_DOMAIN, LOCAL_IP, BASE_URL, HTTP_PORT,
  HTTPS_PORT, HTTPS_MODE; SEED_*_PASSWORD now optional.

Tooling
- scripts/local-setup.sh: idempotent OS-aware bootstrap (.env upsert,
  mkcert hint, cert SAN check, dry-run via LOCAL_SETUP_DRY_RUN).
  start.sh untouched.

Tests
- artifacts/api-server/tests/setup-wizard.test.mjs: 7/7 pass
  (snapshot/restore admin role + system_settings around tests).
- scripts/tests/local-setup.test.mjs: 2/2 pass (first-run bootstrap
  + second-run no-op idempotency with mkcert/openssl stubs).

Constraints honored: no force-push, no destructive ops, start.sh
preserved, scripts idempotent, volumes/DB never touched, HTTPS skip
mode dev-only, wizard does not edit LOCAL_DOMAIN/LOCAL_IP.

Out of scope / not addressed: pre-existing TS errors in
routes/users.ts and pre-existing failure in
executive-meetings-postpone-race.test.mjs.
2026-05-14 07:32:58 +00:00
Riyadh 7fcf516141 Set up Docker environment for local development and deployment
Configure Docker Compose, Dockerfiles for API and web servers, and a startup script to enable local development and deployment of the application.
2026-05-13 19:47:05 +00:00
Riyadh 06bb505e02 Task #526: Off-Replit migration & GitHub-ready cleanup
Fully decoupled Tx OS from the Replit hosted environment so the project
can be cloned and run on any Linux VPS with `docker compose up`.

Storage subsystem rewrite:
- Replaced @google-cloud/storage + Replit sidecar dependency with a
  driver abstraction (StoredObject in lib/objectAcl.ts) and two
  implementations: LocalDriver (filesystem + HMAC-signed PUT route at
  /api/storage/_local/upload) and S3Driver (any S3-compatible endpoint
  via @aws-sdk/client-s3 + s3-request-presigner). Driver auto-selected
  by STORAGE_DRIVER / S3_ENDPOINT env vars.
- Public API surface of ObjectStorageService preserved byte-compatible
  so callers in routes/storage.ts and routes/executive-meetings.ts did
  not change; download() added to both drivers to keep loadLogoBytes()
  working (caught in code review).
- Storage object-authz tests A-L (incl. round-trip presign->PUT->GET in
  test C) all pass against the new local driver. Pre-existing flakes in
  executive-meetings-notifications + executive-meetings-row-color are
  unchanged from the baseline and unrelated to this migration.

Infrastructure:
- Dockerfile (5 targets: deps/build/api/web/migrate). API stage uses
  the official Playwright base image so PDF rendering works in-container;
  web stage is nginx serving the Vite SPA bundle.
- docker-compose.yml: postgres + minio + minio-init (creates buckets) +
  api + web + one-shot migrate runner. Healthchecks on every long-lived
  service.
- docker/nginx.conf: SPA fallback, /api proxy, /api/socket.io websocket
  upgrade ordering.
- .env.example: every runtime env var documented with comments.
- README.md replaces replit.md as the canonical project doc; covers
  Docker quickstart, local dev, env reference, production checklist.
- MIGRATION_REPORT.md: file-by-file diff of what changed and why.

Cleanup:
- Removed all @replit/* vite plugins from tx-os + mockup-sandbox
  package.json + vite.config.ts + pnpm-workspace.yaml catalog.
- Removed @google-cloud/storage and google-auth-library from api-server.
- Deleted attached_assets/ (23MB), sedMkjeJm temp file, stale dist/ and
  *.tsbuildinfo build artefacts, scripts/post-merge.sh.
- Stripped Replit references from threat_model.md (now describes the
  self-hosted topology).
- New comprehensive .gitignore: Replit configs (.replit, replit.nix,
  replit.md), agent state (.local/, .canvas/, .agents/, .cache/, .config/,
  .upm/), local storage/, .env*, build artefacts. .replit and replit.nix
  remain on disk (sandbox-protected) but will not ship to GitHub.
- scripts/src/seed.ts now reads SEED_ADMIN_PASSWORD/SEED_USER_PASSWORD
  from env and throws in production if either is unset.

Drift from plan: replit.md was deleted (per off-Replit scope) so its
"do-not-touch files" preference list is moot. .replit/replit.nix kept on
disk only because they are sandbox-protected from edit/delete in this
environment, but they are git-ignored so they will not appear in a
fresh clone.
2026-05-13 14:08:11 +00:00