Deployed: seshlog-production.up.railway.app
Pomodoro timer with a RESTful FastAPI and PostgreSQL backend. Sessions have a many-to-many relationship with tags, and the API tracks work sessions, statistics, and history per user with JWT authentication. You can start sessions, tag them by topic, pause and resume them, filter your history by tag, and view all-time totals alongside a 30-day activity chart. A TypeScript frontend ties it all together. The backend is covered by pytest integration tests that run against a real test database. Containerrised with Docker and deployed via Railway with a basic CI/CD setup in Github Actions.
- Create, pause, resume, and complete work sessions
- Assign multiple tags to a session and filter history by tag
- All-time totals and a 30-day activity chart, both filterable by tag
- Expired sessions are auto-completed when the next request comes in
- Countdown timer with an animated SVG progress ring
- Active session is restored when the page is reloaded
- Timer corrects itself when switching tabs and auto-completes if it ran out while the tab was hidden
- Browser notification when the timer finishes
- Cookie-based JWT authentication; each user can only access their own data
- Account deletion removes the user and all their sessions; the demo account is protected
- Rate limiting, input validation, and security headers on all endpoints
- Auto-generated interactive API docs at
/docs - Containerised with Docker Compose (backend + PostgreSQL)
- Deployed to Railway with a PostgreSQL service and environment variable management
- GitHub Actions pipeline runs the full pytest suite on every push and blocks merges if tests fail
- Passing builds on
maintrigger an automatic redeploy on Railway
| Layer | Technology |
|---|---|
| API | FastAPI |
| ORM | SQLModel (built on SQLAlchemy) |
| Database | PostgreSQL |
| Validation | Pydantic v2 |
| Auth | JWT (python-jose) + bcrypt |
| Rate limiting | slowapi |
| Frontend | TypeScript |
| Testing | pytest + FastAPI TestClient |
| Containerisation | Docker |
| CI/CD | GitHub Actions |
| Deployment | Railway |
| Method | Endpoint | Description |
|---|---|---|
POST |
/auth/register |
Register a new user, sets an auth cookie |
POST |
/auth/login |
Log in with email and password, sets an auth cookie |
POST |
/auth/logout |
Log out, clears the auth cookie |
DELETE |
/auth/account |
Delete the authenticated user's account and all their data, clears the auth cookie |
Authentication is cookie-based. After login or register, the server sets an HttpOnly cookie (access_token) that is sent automatically with subsequent requests.
| Method | Endpoint | Description |
|---|---|---|
POST |
/sessions |
Create a new session |
GET |
/sessions |
List all sessions (optional ?tag= filter) |
GET |
/sessions/{id} |
Get a single session |
PATCH |
/sessions/{id} |
Update status or tags |
DELETE |
/sessions/{id} |
Delete a session |
| Method | Endpoint | Description |
|---|---|---|
GET |
/tags |
List all tags that have at least one session |
| Method | Endpoint | Description |
|---|---|---|
GET |
/statistics |
All-time totals + 30-day daily breakdown (?tag= filter) |
UserTable: id, email, hashed_password
PomodoroSession: id, user_id (FK to UserTable), duration_minutes, started_at, status, paused_at, total_paused_seconds
Tag: id, name
SessionTagLink (join table): session_id (FK), tag_id (FK)
A user has many sessions. A session has many tags and a tag can belong to many sessions, linked through SessionTagLink. Tags are stored once and reused. Deleting a session automatically cleans up orphaned tag links.
pomodoro-app/
βββ app/
β βββ api/
β β βββ auth.py # register, login, and logout endpoints
β β βββ frontend.py # serves the frontend pages (/, /imprint, /privacy)
β β βββ health.py # health check endpoint
β β βββ sessions.py # session CRUD endpoints
β β βββ statistics.py # statistics endpoint
β β βββ tags.py # tags endpoint
β βββ core/
β β βββ config.py # environment/settings
β β βββ limiter.py # rate limiter setup
β β βββ logging.py # logging setup
β β βββ security.py # JWT creation, decoding, and auth dependency
β βββ db/
β β βββ schema.py # database table definitions
β βββ models/
β β βββ session.py # session request/response models
β β βββ statistics.py # statistics response models
β β βββ tag.py # tag request/response models
β β βββ token.py # TokenResponse model
β β βββ user.py # UserCreate and User models
β βββ services/
β β βββ auth_service.py # registration and login logic
β β βββ session_service.py # session business logic
β β βββ statistics_service.py # statistics queries and aggregation
β β βββ tag_service.py # tag business logic
β βββ main.py # app entry point, middleware setup
βββ frontend/
β βββ src/
β β βββ app.ts # TypeScript source for the demo UI
β β βββ types.ts # shared TypeScript interfaces
β βββ static/
β β βββ index.html # main app page
β β βββ imprint.html # legal notice page
β β βββ privacy.html # privacy policy page
β β βββ app.js # compiled frontend JS
β β βββ types.js # compiled types
β β βββ styles.css # styles
β βββ package.json
β βββ tsconfig.json
βββ tests/
β βββ conftest.py # fixtures and test database setup
β βββ test_auth.py # auth endpoint tests
β βββ test_frontend.py # frontend route tests
β βββ test_sessions.py # session endpoint tests
β βββ test_statistics.py # statistics endpoint tests
β βββ test_tags.py # tags endpoint tests
βββ scripts/
β βββ seed_demo.py # creates the demo account and populates it with sample sessions
βββ .env # local environment variables (not committed)
βββ .env.test # test database config (not committed)
βββ pytest.ini # pytest configuration
βββ requirements.txt
βββ Dockerfile # builds the backend image
βββ docker-compose.yml # defines the backend and database services
βββ .dockerignore
βββ README.md
Requires Docker with the Compose plugin.
git clone https://github.com/Latfoo/pomodoro-app
cd pomodoro-appCreate a .env file in the project root:
DB_USER=your_user
DB_PASSWORD=your_password
DB_NAME=pomodoro
SECRET_KEY=your-secret-key
APP_ENV=developmentAPP_ENV=development disables the Secure flag on the auth cookie so it works over plain HTTP on localhost. Set it to production when deploying over HTTPS.
docker compose up --buildThis starts two containers: the FastAPI backend and a PostgreSQL database. The API runs at http://localhost:8000 and the interactive docs are at http://localhost:8000/docs.
To seed the demo account, run once after the containers are up:
docker compose exec backend python3 scripts/seed_demo.pyRequires Python 3.12+ and PostgreSQL installed locally.
git clone https://github.com/Latfoo/pomodoro-app
cd pomodoro-app
python -m venv venv
source venv/bin/activate
pip install -r requirements.txtsudo -u postgres psqlCREATE USER your_user WITH PASSWORD 'your_password';
CREATE DATABASE pomodoro OWNER your_user;
\qCreate a .env file in the project root:
DB_USER=your_user
DB_PASSWORD=your_password
DB_NAME=pomodoro
SECRET_KEY=your-secret-key
APP_ENV=developmentAPP_ENV=development disables the Secure flag on the auth cookie so it works over plain HTTP on localhost. Set it to production when deploying over HTTPS.
uvicorn app.main:app --reloadThe API runs at http://localhost:8000 and the interactive docs are at http://localhost:8000/docs.
To seed the demo account, run:
python3 scripts/seed_demo.pyThe test suite uses pytest and FastAPI's TestClient. Tests run against a real PostgreSQL database that gets wiped and recreated before each test, so they never touch your development data.
sudo -u postgres psqlCREATE DATABASE pomodoro_test OWNER your_user;
\qCreate a .env.test file in the project root pointing at the test database:
DB_USER=your_user
DB_PASSWORD=your_password
DB_NAME=pomodoro_test
SECRET_KEY=any-secret-key
APP_ENV=development
RATELIMIT_ENABLED=falsepytest| File | What it tests |
|---|---|
test_auth.py |
Registering, logging in, logging out, account deletion, tokens |
test_sessions.py |
Creating, editing, and deleting sessions; pausing and resuming; users can't see each other's data (authorization) |
test_statistics.py |
Totals and tag filtering; users only see their own stats |
test_tags.py |
Tag listing and cleanup when a session is deleted |
A "Try Demo" button in the header logs straight into a pre-populated demo account so anyone visiting the deployed app can explore it without registering.
To set up the demo account, run the seed script once after the containers are up:
docker compose exec backend python3 scripts/seed_demo.pyThis creates the user demo@example.com with password demo1234 and inserts 37 completed sessions spread across the last four weeks, tagged with coding, deep-work, studying, reading, and planning. The script is idempotent and will skip creation if the account already exists.
curl -c cookies.txt -X POST http://localhost:8000/auth/register \
-H "Content-Type: application/json" \
-d '{"email": "you@example.com", "password": "yourpassword"}'curl -b cookies.txt -X POST http://localhost:8000/sessions \
-H "Content-Type: application/json" \
-d '{"duration_minutes": 25, "tags": ["work", "backend"]}'curl -b cookies.txt http://localhost:8000/sessions?tag=backendcurl -b cookies.txt -X DELETE http://localhost:8000/auth/accountcurl -b cookies.txt -X PATCH http://localhost:8000/sessions/1 \
-H "Content-Type: application/json" \
-d '{"status": "completed"}'