- JavaScript 49.9%
- Python 31.4%
- CSS 8.9%
- Nix 6.4%
- HTML 3.2%
- Other 0.2%
|
|
||
|---|---|---|
| .forgejo/workflows | ||
| backend | ||
| frontend | ||
| .gitignore | ||
| flake.lock | ||
| flake.nix | ||
| README.md | ||
| start-mechaturk.sh | ||
Todoiam
A simple task-tracking web app with a Python/Flask backend and a vanilla HTML/CSS/JS frontend, backed by PostgreSQL. Supports OpenID Connect for authentication (optional — the app works without it in dev mode).
Project structure
├── backend/
│ └── app.py Flask API + OIDC + DB migrations
├── frontend/
│ ├── index.html Main page (login/logout UI + task list)
│ ├── script.js Client-side logic (auth + API calls)
│ └── style.css Dark green theme (Gleipnir)
├── flake.nix Nix flake build (python + frontend assets)
├── flake.lock
├── oidc-roadmap.md OIDC integration plan
└── README.md
Dependencies — all resolved by the Nix flake:
- Python:
flask,flask-cors,psycopg(v3),authlib - No npm / node required at runtime (the frontend is static HTML/CSS/JS)
Quick start (development, no auth)
You'll need a running PostgreSQL instance with a database and user both named liam:
createdb liam
createuser liam
Start the backend (OIDC is disabled by default when no provider is configured):
nix build
./result/bin/mechaturk
Open http://127.0.0.1:5000 — Flask serves the frontend, the JS, the CSS, and the API all from the same process. No separate web server needed. The task UI loads immediately when auth is disabled.
Building
nix build
Produces ./result containing:
result/
├── bin/mechaturk ← runs the Flask API
├── lib/app.py ← Python backend source
└── share/mechaturk/frontend/
├── index.html
├── script.js
└── style.css
The short git commit hash is stamped into index.html at build time (bottom-right corner).
OIDC configuration
Set these environment variables to enable authentication:
| Variable | Required | Example |
|---|---|---|
FLASK_SECRET_KEY |
yes | openssl rand -hex 32 |
OIDC_ISSUER |
yes | https://auth.example.com/realms/myrealm |
OIDC_CLIENT_ID |
yes | mechaturk |
OIDC_CLIENT_SECRET |
yes | … |
DB_HOST |
no | localhost |
DB_PORT |
no | 5432 |
DB_NAME |
no | liam |
DB_USER |
no | liam |
DB_PASSWORD |
no | … |
If OIDC_ISSUER and OIDC_CLIENT_ID are both absent, authentication is disabled and all routes are public — useful for local development.
The OIDC redirect URI that must be registered with your provider is https://<your-domain>/oidc/callback.
NixOS deployment with Caddy
Since Flask now serves the frontend directly, the simplest Caddy config just reverse-proxies everything to Flask:
services.caddy.virtualHosts."tasks.example.com".extraConfig = ''
reverse_proxy localhost:5000
'';
To offload static files to Caddy (better for production throughput), use the config below. Caddy serves the frontend directly from the Nix store and only proxies API/OIDC routes to Flask:
{ config, pkgs, ... }:
let
mechaturk = (builtins.getFlake "/path/to/mechaturk").packages.${pkgs.system}.default;
in
{
# --- PostgreSQL (if not already configured) ---
services.postgresql = {
enable = true;
ensureDatabases = [ "liam" ];
ensureUsers = [{
name = "liam";
ensureDBOwnership = true;
}];
};
# --- Caddy: static files + reverse proxy ---
services.caddy = {
enable = true;
virtualHosts."tasks.example.com" = {
extraConfig = ''
root * ${mechaturk}/share/mechaturk/frontend
# Proxy OIDC auth flows to Flask
handle /oidc/* {
reverse_proxy localhost:5000
}
# Proxy task API routes to Flask
handle_path /gettasks { reverse_proxy localhost:5000 }
handle_path /addelement { reverse_proxy localhost:5000 }
handle_path /deletetask { reverse_proxy localhost:5000 }
handle_path /checkoff { reverse_proxy localhost:5000 }
handle_path /message { reverse_proxy localhost:5000 }
# Everything else → static frontend
file_server
'';
};
};
# --- Flask backend as a systemd service ---
systemd.services.mechaturk = {
description = "Todoiam Flask API";
after = [ "network.target" "postgresql.service" ];
wantedBy = [ "multi-user.target" ];
serviceConfig = {
ExecStart = "${mechaturk}/bin/mechaturk";
Restart = "always";
User = "mechaturk";
EnvironmentFile = "/etc/mechaturk/secrets"; # OIDC + DB credentials
};
};
users.users.mechaturk = {
isSystemUser = true;
group = "mechaturk";
};
users.groups.mechaturk = {};
}
Create /etc/mechaturk/secrets (mode 0400, owned by mechaturk):
FLASK_SECRET_KEY=<output of: openssl rand -hex 32>
OIDC_ISSUER=https://auth.example.com/realms/myrealm
OIDC_CLIENT_ID=mechaturk
OIDC_CLIENT_SECRET=<your-client-secret>
DB_PASSWORD=<your-db-password>
Replace tasks.example.com with your domain, and /path/to/mechaturk with the actual repo path on disk (or reference it via a flake input).
API endpoints
| Method | Path | Auth | Description |
|---|---|---|---|
| GET | /oidc/user |
public | Returns current user info (or 401) |
| GET | /oidc/login |
public | Redirects to OIDC provider |
| GET | /oidc/callback |
public | OIDC authorization-code callback |
| POST | /oidc/logout |
public | Clears local session |
| GET | /gettasks |
required | List tasks for current user |
| POST | /addelement |
required | Create a task ({"task_name": "…"}) |
| POST | /deletetask |
required | Delete a task |
| POST | /checkoff |
required | Toggle completed state |
| POST | /message |
public | Echo endpoint (debugging) |
All protected routes return 401 when unauthenticated. The frontend automatically redirects to /oidc/login on 401.