How to develop, run, and deploy the site.
For a full description of the system’s architecture and data flow, see 03-architecture/. For environment management (Local, QA, Production), secrets schema, and GitHub Environments setup, see 08-ENVIRONMENTS.md.
| Layer | Location | Role |
|---|---|---|
| Data | source/data/*.yaml |
Single source of truth for all events |
| Build | source/build/build.js |
Generates HTML from data and Markdown |
| Server | app.js |
Serves public/ and handles the add-event API |
| Output | public/ |
Generated HTML + static CSS/JS — do not edit directly |
The server (app.js) does two things:
public/.POST /add-event, which commits the new event to GitHub via the Contents API, opens a PR, and enables auto-merge. The PR triggers CI (build only) and merges automatically — no admin step needed.npm install
npm run build
npm start
The site is available at http://localhost:3000.
The port can be overridden:
PORT=8080 npm start
If you edit a YAML data file directly (e.g. to correct an event), rebuild:
npm run build
The server does not need to be restarted after a rebuild — it reads files fresh on each request.
npm test # Run tests
npm run lint # Lint JavaScript
npm run lint:md # Lint Markdown
npm run test:update-snapshots # Regenerate schedule page snapshots
Event data lives in source/data/. One YAML file per camp.
Which camp is active is derived automatically from dates at build time and at
API request time — there is no manual active field. See
03-architecture/data-layer.md §2 “Metadata Layer”
for the canonical priority rules (today on dates → next upcoming → most
recent) and the shared resolver.
Events are sorted chronologically at build time, so their order in the YAML file does not matter. New events submitted through the form are committed to GitHub and merged via auto-merge PR — the file on disk updates when the next deploy runs.
Locations are defined centrally in source/data/local.yaml.
Never define locations inside individual camp files.
The site is deployed to shared PHP hosting (Loopia):
| Part | Deployment method | Location on host |
|---|---|---|
Static site (public/) |
SCP + SSH swap | Web root (DEPLOY_DIR/public_html) |
PHP API (api/) |
SCP (with site) | Web root (DEPLOY_DIR/public_html/api) |
The PHP API runs via Apache mod_rewrite + PHP 8.4. No process manager needed.
The api/ directory (including vendor/ from composer install --no-dev)
is uploaded alongside the static site. The api/.env file on the server is
managed manually and is not part of the deploy archive.
CI (Continuous Integration) runs automated checks on every push and pull request.
CD (Continuous Deployment) deploys to QA automatically after a merge to main;
Production deploys are triggered manually. See 08-ENVIRONMENTS.md for the full environment model.
| Workflow | Trigger | What it does |
|---|---|---|
ci.yml |
Every push and PR | Lint + test + build. Lint/test skipped for data-only commits (per-camp event files only; config files like camps.yaml and local.yaml trigger full CI). Uses fetch-depth: 0 to compare against main. |
event-data-deploy.yml |
PRs from event/, event-edit/ changing source/data/*.yaml |
Lint YAML + security scan + build + targeted deploy to both QA (SCP) and Production (FTP). Uses fetch-depth: 0 to detect changed files. |
deploy-qa.yml |
Push to main (paths-ignore data YAMLs) |
Calls deploy-reusable.yml with environment qa. Build → SCP → SSH swap → SSH restart. |
deploy-prod.yml |
Manual (workflow_dispatch) |
Calls deploy-reusable.yml with environment production. Same steps as QA. |
deploy-reusable.yml |
Called by deploy-qa.yml / deploy-prod.yml |
Shared logic: Build → SCP archive → SSH swap into web root → deploy PHP API. |
flowchart TD
A[Push branch] --> B[CI: lint · test · build]
B --> C{Passes?}
C -- No --> D[Fix and repush]
D --> B
C -- Yes --> E[Open PR · merge to main]
E --> F
E --> G
subgraph F [QA: auto-deploy on push to main]
F1[Build public/] --> F2[tar + SCP archive to QA server]
F2 --> F3[SSH: extract · swap · cleanup]
F3 --> F4[SSH: deploy PHP API · composer install]
end
subgraph G [Production: manual workflow_dispatch]
G1[Build public/] --> G2[tar + SCP archive to prod server]
G2 --> G3[SSH: extract · swap · cleanup]
G3 --> G4[SSH: deploy PHP API · composer install]
end
| Variable | Default | Description |
|---|---|---|
PORT |
3000 |
Port the HTTP server listens on |
API_URL |
— | Baked into the static form HTML as the fetch target |
ALLOWED_ORIGIN |
— | Primary origin allowed by the CORS middleware |
QA_ORIGIN |
— | Secondary origin allowed by CORS (staging/QA) |
COOKIE_DOMAIN |
— | Shared parent domain for session cookie (see note below) |
GITHUB_OWNER |
— | GitHub repository owner |
GITHUB_REPO |
— | GitHub repository name |
GITHUB_BRANCH |
— | Branch to commit events to (typically main) |
GITHUB_TOKEN |
— | Personal access token with repo write access |
ADMIN_TOKENS |
— | Comma-separated admin tokens (UUIDs). Omit to disable. |
API_URL, ALLOWED_ORIGIN, COOKIE_DOMAIN, GITHUB_*, and SSH credentials are stored as GitHub Actions secrets and server environment variables. They are not needed for local development. Without API_URL set, the built form will have no submit endpoint — this is expected in local builds. Without GITHUB_* set, event submission via the API will fail; all other functionality works normally.
COOKIE_DOMAIN: required when the API and the static site are deployed on different subdomains (e.g. api.sommar.example.com and sommar.example.com). Set it to the shared parent domain — e.g. sommar.digitalasynctransparency.com. This causes the session cookie to include Domain=<value> so that client-side JavaScript on the static site can read it. Omit the variable for single-origin deployments.
source/data/ (e.g. 2026-06-syssleback.yaml).source/data/camps.yaml with dates and archived: false.npm run build to verify the correct camp is derived as active.Minimal camp file:
camp:
id: 2026-06-syssleback
name: SB Sommar Juni 2026
location: Sysslebäck
start_date: '2026-06-28'
end_date: '2026-07-05'
events: []
Participants add events through the web form at /lagg-till.html.
The API server commits the event to GitHub, which triggers an auto-merge PR
and a full rebuild and deploy — typically live within a few minutes.
An admin can also edit the YAML file directly on GitHub and push to main
to fix or remove entries.
archived: true for the camp in source/data/camps.yaml.Admin tokens grant one or two people the ability to edit or delete any
event through the site UI — not just their own. The feature is optional;
omit ADMIN_TOKENS to disable it entirely.
npm run admin:create and follow the prompts. The script
generates a token in the format namn_uuid_epoch (where epoch is
a Unix timestamp 60 days in the future) and prints instructions.
The token is shown only once — save it immediately.ADMIN_TOKENS in all three locations (see script
output): .env, api/.env on the server, and GitHub Environment
secrets for QA/Production./admin.html, enters the token, and gains admin
status for 30 days.Remove the token from ADMIN_TOKENS and redeploy. No code change is
needed.
main — the deploy pipeline rebuilds automatically.Git history provides a full audit trail of all changes, including every event submitted through the form.
npm run build locally and read the error output.api/.env file exists and contains valid credentials.workflow_dispatch if the static site is missing.