# neuf-www The neuf.no website. Wagtail CMS backend (`dnscms/`) feeding a Next.js frontend (`web/`) over GraphQL. Tools are managed by [mise](https://mise.jdx.dev/). Run `mise install` to get python, uv, node, and prek. ## Backend (`dnscms/`) ```bash cd dnscms uv sync uv run ./manage.py migrate uv run ./manage.py runserver uv run pytest ``` GraphQL endpoint: . ## Frontend (`web/`) ```bash cd web npm install npm run dev # http://localhost:3000 npm run codegen # regenerate GraphQL types (needs the backend running) npm run build npm test # vitest (npm run test:watch for watch mode) ``` ## Pre-commit hooks [prek](https://github.com/j178/prek) runs ruff lint + format on `dnscms/**/*.py`, vitest on `web/` changes, plus a few sanity hooks. Hooks are configured in [prek.toml](prek.toml). ```bash prek install # registers the git hook prek run --all-files # run on everything ``` ## Caching and revalidation The frontend caches all pages and GraphQL responses (ISR) under a single `cms` cache tag. Wagtail purges it via a webhook on any publish/unpublish/move/delete (see `dnscms/dnscms/revalidation.py`). Date-sensitive queries (`futureEvents`, opening hours) additionally expire at Oslo midnight. `REVALIDATE_WEBHOOK_SECRET` must be set to the same value on both sides (`web/.env.local` and `dnscms/dnscms/settings/local.py` or env); unset disables the webhook. ```bash # manual purge curl -X POST -H "X-Revalidate-Secret: $SECRET" https:///api/revalidate ``` Note: ISR is off under `npm run dev`; use `npm run build && npm run start` to test caching. ## Production proxy (Caddy) Both apps run on Coolify on `ds.neuf.no` behind the `coolify-proxy` container (caddy-docker-proxy). Extra config lives in `/data/coolify/proxy/caddy/dynamic/` on the host, not in any repo: - `Caddyfile`, `default_redirect_503.caddy`: Coolify's own. - `studentfestivalen-redirects.caddy`: `studentfestivalen.no` and `studioweb.no` → `neuf.no/studio`. - `cms-neuf-no.subdirectives`: ```caddy rewrite / /admin/ handle_path /media/* { root * /site/cms.neuf.no/media file_server } ``` - `neuf-no.subdirectives`. The header replaces Next's `stale-while-revalidate`, which made browsers reuse pages from old builds and broke navigation after deploys: ```caddy handle_path /media/* { root * /site/cms.neuf.no/media file_server } @pages not path /_next/static/* /_next/image* /assets/* /media/* header @pages >Cache-Control "public, max-age=0, must-revalidate" ``` ## CMS login via Galtinn Editors log in to the CMS with their Galtinn account over OIDC (see `dnscms/dnscms/oidc.py`). Access follows membership of one Galtinn group, which is checked at every login: members become Wagtail editors, and a linked user who has left the group is deactivated. Users are identified only by their stored Galtinn ID, never by username or email, so an existing local user must be linked by hand under `/django-admin/` before they can use it. Linked users have no local password; unlinked local users still log in with theirs. Set these in `dnscms/dnscms/settings/local.py` or env. The Galtinn button only shows when the group and client ID are set. | Setting | Value | |---|---| | `GALTINN_BASE_URL` | Galtinn's public URL, defaults to `https://galtinn.neuf.no` | | `GALTINN_EDITOR_GROUP` | name of the Galtinn group that grants editor access | | `OIDC_RP_CLIENT_ID`, `OIDC_RP_CLIENT_SECRET` | the OAuth application registered in Galtinn's admin | | `WAGTAIL_BASE_URL` | must be the CMS's public URL; the callback is `/oidc/callback/` | To link existing local users in bulk, match them against Galtinn and review the result by hand: ```bash uv run manage.py galtinn_users export > cms-users.csv # in dnscms uv run manage.py match_cms_users cms-users.csv > matched.csv # in galtinn, suggests a galtinn_id per row uv run manage.py galtinn_users link matched.csv # in dnscms, dry run; add --apply to save ``` Rows with an empty `galtinn_id` stay local users. A linked user keeps their password until their first Galtinn login.