Architecture briefing · 18 August 2026

Global Ties Miami program tracker

An internal ops app for IVLP-style visitor programs. One staff team tracks visitors, meetings, transport, hotels, contracts, cultural days, and dinner diplomacy, plus a 28-item readiness checklist and a calendar of every program at once. Zero npm packages, no build step, Node 18+.

Purpose

Who uses it and why it exists

Global Ties Miami runs international visitor programs (typically IVLP cohorts). Staff already juggle Google Sheets, inboxes, and calendar invites. This app is the single place that answers: who is coming, what is booked, what is still open, and what happens this week.

It is not a CMS, CRM, or accounting system. Version 1 deliberately stops at operational tracking. Conflict detection, budgets, document storage, visitor-facing portals, and evaluations were discussed and deferred.

Staff

Day to day

Create a program, import a visitor roster, tick checklist items, book meetings and logistics, print an itinerary, subscribe the office calendar.

Admin

Accounts and template

Create users, disable accounts, edit the 28-item checklist template, publish or rotate the secret iCal URLs.

Viewer

Read only

See dashboards, programs, and directories. Cannot write, cannot see feed tokens, cannot delete.

Request path

How a click becomes a stored record

  1. Browser shell. public/index.html loads app.js and style.css. Hash routes pick the view: dashboard, programs, a program tab, a directory, activity, admin.
  2. Bootstrap. After login, GET /api/bootstrap returns the current user, active users, and the full schema. Forms and tables are generated from that schema, not hardcoded per entity.
  3. Writes. app.js sends JSON to /api/… with cookie session plus X-CSRF-Token copied from the readable gtm_csrf cookie.
  4. Server. server.js applies CSP and framing headers, parses the body, checks CSRF (except login), then hands off to lib/api.js.
  5. Sanitize. lib/schema.js keeps only declared fields, rejects illegal select values, caps strings at 4,000 characters. Unknown keys never hit disk.
  6. Store. Node 22.5+ writes SQLite (node:sqlite, WAL). Older Node writes atomic JSON. Same API either way. Records live as a typed row plus a JSON payload.
  7. Side effects. Creating a program stamps a 28-item checklist with due dates = arrival + offset. Activity log records field-level diffs. Directory renames backfill names on linked program records.

Domain

One schema file is the source of truth

Add a field in lib/schema.js and the form, table, CSV export, and activity log pick it up. No second list to keep in sync.

TypeScopeWhat it tracks
programGlobalName, agency, theme, arrival/departure, pax, languages, lead + optional secondary, status.
visitorProgramRoster: org, country, flights, dietary, access, emergency, room.
meetingProgramHost org, contact, address, slot, interpretation, status.
transportProgramPickup/drop, vendor, driver, vehicle, confirmation.
hotelProgramStay, rooms, rate, billing, confirmation.
culturalProgramActivity, venue, tickets, cost.
dinnerProgramDinner diplomacy host, seats, dietary notes, home address.
contractProgramCounterparty, value, sent/due/signed, document link.
checkitemProgramGenerated from template; tick stamps who and when.
orgDirectoryMeeting hosts and venues. Topics, lead time, security, access.
hostDirectoryDinner hosts. Seats, languages, pets, orientation, agreement.
vendorDirectoryHotels, transport, interpreters, caterers. W-9, COI, rating.

Directories fill themselves: type a name that is not in the list and the app offers to add it. Later forms suggest it and copy address/contact. Merge folds duplicates; an entry still in use cannot be deleted — set it Dormant / Do not use instead.

Creating a program clones the current checklist template. Later template edits do not rewrite existing programs. Individual items can be added or removed on one program without touching others. Tasks are not assigned to people; the program’s lead/secondary own the whole list.

Layout

What lives where

PathJob
server.jsHTTP, static files, CLI (--create-admin, --backup), nightly backup timer, session purge.
lib/schema.jsEntities, fields, statuses, country aliases, default 28-item template, sanitize.
lib/store.jsSQLite or JSON. Users, sessions, records, activity, settings.
lib/auth.jsscrypt, 12-hour server sessions, CSRF, admin/staff/viewer.
lib/api.jsJSON API: records, bulk import, dashboard, search, directories, template, calendar tokens.
lib/ical.jsRFC 5545 feeds. Miami wall-clock stored, UTC published.
public/app.jsEntire SPA: hash router, forms from schema, roster import, itinerary, search.
deploy/systemd unit (hardened) and nginx TLS reverse proxy.
build-review.jsSingle-file HTML for evaluation, mock backend, five sample programs.
Local

Run on Node 24

node server.js --create-admin … then HOST=127.0.0.1 PORT=8080 node server.js. Data in ./data/, gitignored.

Production

Bind localhost only

systemd as user gtm, nginx + certbot on tracker.globaltiesmiami.org. App never talks outbound and never sends email.

Review

No-server demo

node build-review.js writes a self-contained HTML file. Sample data stays in the browser; it is not the live database.

Threat model

Built for a small trusted staff, not the public internet

OK

Passwords and sessions

scrypt with per-user salt. First login forces a password change. Sessions expire after 12 hours idle and die immediately when an account is disabled or the password is reset.

OK

Writes are gated

CSRF on every mutating request except login. Schema whitelist. CSP + DENY framing. Login rate-limited in nginx. Passwords never appear in the activity log.

Careful

Calendar token is a password

Anyone with the URL can read program dates (and optionally host addresses). Rotate from Administration. Google must be able to fetch /calendar/ unauthenticated.

Do not

Plain HTTP on the public net

Sessions and passwords would travel in the clear. Always terminate TLS at nginx. Leave HOST=127.0.0.1.

Scope

What this baseline includes, and what it does not

In v1Deferred
Dashboard calendar bands, this-week feed, open checklist itemsConflict and staff-load detection
CSV roster import with country aliasesBudgets and invoices
Directories with usage history, rename, mergeDocument storage
Itinerary print + per-program .ics downloadMail-merge letters
Office-wide Google Calendar subscribeVisitor / meeting-host portals
Nightly backup to disk (copy off-box yourself)Post-program evaluations

Sources: local tree /home/abstergo/Programming/Projects/gtm-tracker — README.md, server.js, lib/schema.js, lib/api.js, lib/store.js, lib/auth.js, lib/ical.js, public/app.js, deploy/. Tests run 18 August 2026: 180 API (SQLite) + 180 API (JSON) + 91 UI + 102 review = 553 passed.