Initial WhatToDo app

Self-hosted personal task list: FastAPI + SQLite in Docker, mobile-friendly
PWA frontend, deadlines, priorities, categories, tags, notes, checklists,
recurring tasks, single-password login, and JSON export/import.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
goyban
2026-10-07 11:43:56 +02:00
commit c5b049edba
23 changed files with 2045 additions and 0 deletions
+81
View File
@@ -0,0 +1,81 @@
import os
import sqlite3
from pathlib import Path
DB_PATH = os.environ.get("DB_PATH", "/data/whattodo.db")
SCHEMA_VERSION = 1
DEFAULT_CATEGORIES = [
("Work", "#2563eb"),
("Life", "#16a34a"),
("Personal", "#9333ea"),
("YouTube", "#dc2626"),
]
SCHEMA = """
CREATE TABLE IF NOT EXISTS meta (
key TEXT PRIMARY KEY,
value TEXT NOT NULL
);
CREATE TABLE IF NOT EXISTS categories (
id INTEGER PRIMARY KEY,
name TEXT NOT NULL UNIQUE COLLATE NOCASE,
color TEXT NOT NULL DEFAULT '#6b7280',
position INTEGER NOT NULL DEFAULT 0
);
CREATE TABLE IF NOT EXISTS tasks (
id INTEGER PRIMARY KEY,
title TEXT NOT NULL,
notes TEXT NOT NULL DEFAULT '',
category_id INTEGER REFERENCES categories(id) ON DELETE SET NULL,
priority INTEGER NOT NULL DEFAULT 0,
due_date TEXT,
tags TEXT NOT NULL DEFAULT '[]',
recurrence TEXT NOT NULL DEFAULT 'none',
recur_interval INTEGER NOT NULL DEFAULT 1,
done INTEGER NOT NULL DEFAULT 0,
done_at TEXT,
created_at TEXT NOT NULL,
updated_at TEXT NOT NULL
);
CREATE TABLE IF NOT EXISTS subtasks (
id INTEGER PRIMARY KEY,
task_id INTEGER NOT NULL REFERENCES tasks(id) ON DELETE CASCADE,
title TEXT NOT NULL,
done INTEGER NOT NULL DEFAULT 0,
position INTEGER NOT NULL DEFAULT 0
);
CREATE INDEX IF NOT EXISTS idx_subtasks_task ON subtasks(task_id);
"""
def connect() -> sqlite3.Connection:
# FastAPI may open a request's connection and run its endpoint on different threads;
# each connection is still used by only one request at a time.
conn = sqlite3.connect(DB_PATH, check_same_thread=False)
conn.row_factory = sqlite3.Row
conn.execute("PRAGMA foreign_keys = ON")
return conn
def init() -> None:
Path(DB_PATH).parent.mkdir(parents=True, exist_ok=True)
conn = connect()
try:
conn.execute("PRAGMA journal_mode = WAL")
conn.executescript(SCHEMA)
row = conn.execute("SELECT value FROM meta WHERE key = 'schema_version'").fetchone()
if row is None:
# Fresh database: seed default categories.
with conn:
for i, (name, color) in enumerate(DEFAULT_CATEGORIES):
conn.execute(
"INSERT INTO categories (name, color, position) VALUES (?, ?, ?)",
(name, color, i),
)
conn.execute(
"INSERT INTO meta (key, value) VALUES ('schema_version', ?)",
(str(SCHEMA_VERSION),),
)
finally:
conn.close()