chore: clean up repo state and improve dev tooling

- Add IF NOT EXISTS to all migration DDL for idempotency
- Support PORT env var in backend (for compose flexibility)
- Support HOST_PORT env var in docker-compose
- Improve seed-demo to apply migrations before seeding
- Gitignore .claude/ session cache
This commit is contained in:
2026-04-29 01:39:00 +02:00
parent b252128c37
commit 4aea0f4427
5 changed files with 36 additions and 23 deletions

1
.gitignore vendored
View File

@@ -1,4 +1,5 @@
.gemini
.claude/
.worktrees/
docs/

View File

@@ -21,5 +21,12 @@ compose-up:
docker-compose up --build
seed-demo:
@echo "Seeding demo data..."
sqlite3 $${DATABASE_URL#sqlite:} < backend/demo/demo_seed.sql
@mkdir -p data
@echo "Applying migrations and seeding demo data..."
@DB_PATH=$${DATABASE_URL:-sqlite:data/attendance.db}; \
DB_FILE=$${DB_PATH#sqlite:}; \
for f in backend/migrations/*.sql; do \
echo "Applying $$f..."; \
sqlite3 $$DB_FILE < $$f; \
done; \
sqlite3 $$DB_FILE < backend/demo/demo_seed.sql

View File

@@ -1,35 +1,35 @@
CREATE TABLE courses (
CREATE TABLE IF NOT EXISTS courses (
id INTEGER PRIMARY KEY,
name TEXT NOT NULL,
semester TEXT NOT NULL
);
CREATE TABLE tutors (
CREATE TABLE IF NOT EXISTS tutors (
id INTEGER PRIMARY KEY,
name TEXT NOT NULL,
email TEXT UNIQUE NOT NULL,
password_hash TEXT NOT NULL
);
CREATE TABLE tutor_courses (
CREATE TABLE IF NOT EXISTS tutor_courses (
tutor_id INTEGER NOT NULL REFERENCES tutors(id),
course_id INTEGER NOT NULL REFERENCES courses(id),
PRIMARY KEY (tutor_id, course_id)
);
CREATE TABLE students (
CREATE TABLE IF NOT EXISTS students (
id INTEGER PRIMARY KEY,
course_id INTEGER NOT NULL REFERENCES courses(id),
name TEXT NOT NULL
);
CREATE TABLE rooms (
CREATE TABLE IF NOT EXISTS rooms (
id INTEGER PRIMARY KEY,
name TEXT NOT NULL,
layout_json TEXT NOT NULL
);
CREATE TABLE sessions (
CREATE TABLE IF NOT EXISTS sessions (
id INTEGER PRIMARY KEY,
course_id INTEGER NOT NULL REFERENCES courses(id),
week_nr INTEGER NOT NULL,
@@ -37,7 +37,7 @@ CREATE TABLE sessions (
UNIQUE(course_id, week_nr)
);
CREATE TABLE slots (
CREATE TABLE IF NOT EXISTS slots (
id INTEGER PRIMARY KEY,
session_id INTEGER NOT NULL REFERENCES sessions(id),
room_id INTEGER REFERENCES rooms(id),
@@ -48,7 +48,7 @@ CREATE TABLE slots (
code TEXT UNIQUE
);
CREATE TABLE attendances (
CREATE TABLE IF NOT EXISTS attendances (
id INTEGER PRIMARY KEY,
slot_id INTEGER NOT NULL REFERENCES slots(id),
student_id INTEGER NOT NULL REFERENCES students(id),
@@ -58,7 +58,7 @@ CREATE TABLE attendances (
UNIQUE(slot_id, seat_id)
);
CREATE TABLE notes (
CREATE TABLE IF NOT EXISTS notes (
id INTEGER PRIMARY KEY,
slot_id INTEGER NOT NULL REFERENCES slots(id),
student_id INTEGER NOT NULL REFERENCES students(id),
@@ -69,11 +69,11 @@ CREATE TABLE notes (
);
-- Indexes on high-frequency FK columns (SQLite does not auto-index FKs)
CREATE INDEX idx_students_course ON students(course_id);
CREATE INDEX idx_sessions_course ON sessions(course_id);
CREATE INDEX idx_slots_session ON slots(session_id);
CREATE INDEX idx_slots_tutor ON slots(tutor_id);
CREATE INDEX idx_attendances_slot ON attendances(slot_id);
CREATE INDEX idx_attendances_student ON attendances(student_id);
CREATE INDEX idx_notes_slot ON notes(slot_id);
CREATE INDEX idx_notes_student ON notes(student_id);
CREATE INDEX IF NOT EXISTS idx_students_course ON students(course_id);
CREATE INDEX IF NOT EXISTS idx_sessions_course ON sessions(course_id);
CREATE INDEX IF NOT EXISTS idx_slots_session ON slots(session_id);
CREATE INDEX IF NOT EXISTS idx_slots_tutor ON slots(tutor_id);
CREATE INDEX IF NOT EXISTS idx_attendances_slot ON attendances(slot_id);
CREATE INDEX IF NOT EXISTS idx_attendances_student ON attendances(student_id);
CREATE INDEX IF NOT EXISTS idx_notes_slot ON notes(slot_id);
CREATE INDEX IF NOT EXISTS idx_notes_student ON notes(student_id);

View File

@@ -28,8 +28,12 @@ async fn main() {
.fallback(ServeFile::new(format!("{static_dir}/index.html")))
);
let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await
.expect("failed to bind :3000");
tracing::info!("listening on :3000");
let port = std::env::var("PORT")
.unwrap_or_else(|_| "3000".into());
let addr = format!("0.0.0.0:{port}");
let listener = tokio::net::TcpListener::bind(&addr).await
.expect("failed to bind");
tracing::info!("listening on :{}", port);
axum::serve(listener, app).await.expect("server error");
}

View File

@@ -2,11 +2,12 @@ services:
app:
build: .
ports:
- "3000:3000"
- "${HOST_PORT:-3000}:3000"
volumes:
- ./data:/data
environment:
- DATABASE_URL=sqlite:/data/attendance.db
- STATIC_DIR=/app/frontend/build
- JWT_SECRET=${JWT_SECRET:-dev_secret_for_demo}
- PORT=3000
restart: always