A Google Forms clone
Find a file
2026-05-15 13:04:47 +03:00
app Added source and set up Flask-limiter and Flask-cors 2026-05-15 13:04:47 +03:00
docs Brushed up abstract form models and patched example.json 2026-05-05 00:56:24 +03:00
migrations pretty much file uploads done 2026-05-11 16:13:08 +03:00
tests some wsgi shenangigans ig 2026-05-14 15:15:32 +03:00
.dockerignore Added source and set up Flask-limiter and Flask-cors 2026-05-15 13:04:47 +03:00
.gitignore Syncing up to develop on other device 2026-05-03 12:23:12 +03:00
.python-version Progress in user CRUD 2026-05-01 21:02:27 +03:00
alembic.ini pretty much file uploads done 2026-05-11 16:13:08 +03:00
babel.cfg improved UI, translation, dockerfile, etc 2026-05-14 00:00:40 +03:00
Dockerfile improved UI, translation, dockerfile, etc 2026-05-14 00:00:40 +03:00
pyproject.toml Added source and set up Flask-limiter and Flask-cors 2026-05-15 13:04:47 +03:00
README.md some wsgi shenangigans ig 2026-05-14 15:15:32 +03:00
uv.lock Added source and set up Flask-limiter and Flask-cors 2026-05-15 13:04:47 +03:00

Cruciform -- yet another Google Forms clone

Roadmap

  • In-app versioning, CI/CD releasing
  • Form modification
  • Conditionals
  • E2EE

Stack

Backend utilities:

  • Flask Web server
    • flask-babel i18n
    • flask-sqlalchemy ORM integration
    • flask-pydantic Pydantic integration
    • flask-login SSR auth
    • flask-jwt-extended API auth
    • flask-wtf WTForms integration
  • Jinja2 Templates
  • SQLAlchemy ORM
  • Pydantic API validation & data representation
  • WTForms SSR validation & SSR form renders

Frontend utilities:

  • Bootstrap for layout and several elements
  • Material Style on top of Bootstrap for styling
  • SortableJS for block reordering in form sandbox
  • HTMX for hydrating certain templates
  • Chart.js for form answers

Dev utilities:

  • ruff code linter
  • ty LSP

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.