| app | ||
| docs | ||
| migrations | ||
| tests | ||
| .dockerignore | ||
| .gitignore | ||
| .python-version | ||
| alembic.ini | ||
| babel.cfg | ||
| Dockerfile | ||
| pyproject.toml | ||
| README.md | ||
| uv.lock | ||
Cruciform -- yet another Google Forms clone
Roadmap
- In-app versioning, CI/CD releasing
- Form modification
- Conditionals
- E2EE
Stack
Backend utilities:
FlaskWeb serverflask-babeli18nflask-sqlalchemyORM integrationflask-pydanticPydantic integrationflask-loginSSR authflask-jwt-extendedAPI authflask-wtfWTForms integration
Jinja2TemplatesSQLAlchemyORMPydanticAPI validation & data representationWTFormsSSR validation & SSR form renders
Frontend utilities:
Bootstrapfor layout and several elementsMaterial Styleon top of Bootstrap for stylingSortableJSfor block reordering in form sandboxHTMXfor hydrating certain templatesChart.jsfor form answers
Dev utilities:
ruffcode lintertyLSP
Design is inspired by Google Forms
Docker and reverse proxy
The Docker image defaults to FLASK_ENV=production. In that mode, SSR auth and CSRF rely on secure cookies.
Production also requires an explicit SECRET_KEY. Do not let workers generate their own random secrets.
If you open the container directly over plain HTTP on your LAN, set:
SECRET_KEY=replace-with-a-long-random-value
SESSION_COOKIE_SECURE=false
REMEMBER_COOKIE_SECURE=false
PREFERRED_URL_SCHEME=http
Without that, the browser will refuse to send the session cookie over HTTP, and Flask-WTF will fail CSRF validation because the session-side token never comes back.
For an internet-facing deployment behind Caddy TLS termination, keep secure cookies enabled and trust exactly one proxy hop:
SECRET_KEY=replace-with-a-long-random-value
SESSION_COOKIE_SECURE=true
REMEMBER_COOKIE_SECURE=true
PREFERRED_URL_SCHEME=https
PROXY_FIX_X_FOR=1
PROXY_FIX_X_PROTO=1
PROXY_FIX_X_HOST=1
PROXY_FIX_X_PORT=1
TRUSTED_HOSTS=forms.example.com
Do not publish the Gunicorn port directly to the internet when PROXY_FIX_* is enabled. Only expose Caddy, and let Caddy reach the app over an internal Docker network.
Database migrations
Schema changes are managed with Alembic.
Initialize or upgrade the local database:
alembic upgrade head
Create a new migration after changing SQLAlchemy models:
alembic revision --autogenerate -m "describe change"
Review autogenerated migrations before applying them. This project currently uses SQLite, so some schema changes may require manual migration edits.