Skip to content

Repository files navigation

Broedplaats de Createur: CMS

Flask + MariaDB CMS for the collective's website. The plan and all decisions are in PLAN.md.

PoC with Docker Compose

App (Gunicorn) + MariaDB + Mailpit, with the demo content loaded on first start:

docker compose up --build -d
  • Site: http://localhost:8080 (demo logins at /auth/demo)
  • Mail inbox: http://localhost:8025 (magic links land here)
  • Reset the demo: docker compose exec app flask seed-demo --reset (or log in as Sleutelhouder: Demo terugzetten on the start page)
  • Start from scratch, including the database: docker compose down -v

On a machine other than your own, put at least this in the .env next to compose.yaml (login links in the mails are built from POC_BASE_URL):

APP_PORT=8088
POC_BASE_URL=http://<hostname-or-ip>:8088
DEMO_HOSTS=localhost,127.0.0.1,<hostname-or-ip>

Settings you may want to override in the shell or in .env: APP_PORT, MAILPIT_PORT, POC_BASE_URL (the public URL, e.g. https://createur.<demolabs-domain>), DEMO_HOSTS (must include that hostname, or the app refuses to start), PROXY_HOPS=1 behind a reverse proxy, SECRET_KEY, DB_PASSWORD.

While developing: no rebuild per update

compose.dev.yaml mounts app/ and migrations/ from the checkout (read-only) and runs Gunicorn with --reload:

docker compose -f compose.yaml -f compose.dev.yaml up -d --build   # first time
git pull                                                          # code/templates/CSS: live
docker compose restart app                                        # after .po or migration changes
docker compose up -d --build                                      # only when dependencies change

Tip: put COMPOSE_FILE=compose.yaml:compose.dev.yaml in .env, then plain docker compose uses both.

Local development without Docker

Tonight's setup runs on SQLite; the STRATO-like VM with MariaDB comes next (PLAN §10b).

# once
curl -LsSf https://astral.sh/uv/install.sh | sh     # installs uv in ~/.local/bin
uv sync --python 3.12
cp .env.example .env
uv run pybabel compile -d app/translations          # .mo files are not committed
FLASK_APP=app uv run flask seed-demo --reset        # demo content + generated photos

# every time
./tools/mailpit --listen 127.0.0.1:8025 --smtp 127.0.0.1:1025 &   # mail inbox: http://localhost:8025
FLASK_APP=app uv run flask run                                     # site: http://localhost:5000

Mailpit is a single binary: download mailpit-darwin-arm64.tar.gz from github.com/axllent/mailpit/releases into tools/.

Demo users (DEMO_MODE=1 only)

/auth/demo has one-click logins. All demo users share the password in DEMO_PASSWORD (default createur-demo); the magic link works too, and the mail lands in Mailpit.

Who E-mail Can
Jan jan@demo.example.org maker (own page)
Sanne, Bo sanne@ / bo@demo.example.org makers sharing one page (duo)
Noor noor@demo.example.org maker + webmaster
Sleutelhouder sleutel@demo.example.org superadmin

Seeded states: /pieter-smid is hidden (named "no longer active" 404), /henk-houtwerk is a tombstone with a name, /oud-atelier one without (deletion request), and /en/contact has no English version, so it shows the Dutch page with a notice.

Page cache

Public pages are stored as HTML files on their first visit and served from there afterwards (PLAN §8). Every content change clears the whole cache, and so does every container start. The X-Page-Cache response header says MISS (just rendered) or HIT (served from the file).

flask page-cache clear    # after a deploy: templates may have changed
flask page-cache warm     # render every public page ahead of visitors

It lives in $INSTANCE_PATH/cache unless PAGE_CACHE_ROOT says otherwise; PAGE_CACHE=0 switches it off. With TEMPLATES_AUTO_RELOAD=1 (compose.dev.yaml) edited code or templates clear it on the next visit, so a git pull needs no extra step.

Daily tasks

All periodic jobs run in one go (PLAN D17): today the page cache warm-up, and the nightly demo reset when DEMO_NIGHTLY_RESET=1; the purge of removed makers joins after the opening.

flask run-tasks                    # all tasks; exit 1 if one failed, 2 if another run is busy
flask run-tasks --task cache_rebuild
flask run-tasks --if-due           # only when the last run is over a day old

In production a cron job on another server calls the signed task API once a day (STRATO Basic has no cron). deploy/call-tasks.sh is that caller; it needs the site's TASK_SECRET:

CREATEUR_TASK_SECRET=... deploy/call-tasks.sh https://<domain>            # all tasks
CREATEUR_TASK_SECRET=... deploy/call-tasks.sh https://<domain> cache_rebuild

Safety net: when the last run is over a day old, the next request that reaches Python runs the tasks after its response has gone out (TASK_FALLBACK=0 switches that off). Set HEALTHCHECK_URL to get an e-mail from Healthchecks.io when a daily run fails or doesn't happen. The key holder's start page shows when it last ran.

Tests

uv run pytest

Translations

Interface strings are English msgids with a Dutch catalog, which follows the plain-language word list in PLAN §6a.

uv run pybabel extract -F babel.cfg -k _l -o app/translations/messages.pot .
uv run pybabel update -i app/translations/messages.pot -d app/translations
# edit app/translations/nl/LC_MESSAGES/messages.po, then compile
uv run pybabel compile -d app/translations

Database migrations

flask seed-demo --reset uses create_all for speed. For MariaDB/STRATO use Alembic:

DATABASE_URL='mysql+pymysql://user:pw@host/createur?charset=utf8mb4' FLASK_APP=app uv run flask db upgrade

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages