feat: SQLite migrations and db pool with FK pragma
This commit is contained in:
@@ -0,0 +1,69 @@
|
||||
CREATE TABLE courses (
|
||||
id INTEGER PRIMARY KEY,
|
||||
name TEXT NOT NULL,
|
||||
semester TEXT NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE tutors (
|
||||
id INTEGER PRIMARY KEY,
|
||||
name TEXT NOT NULL,
|
||||
email TEXT UNIQUE NOT NULL,
|
||||
password_hash TEXT NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE tutor_courses (
|
||||
tutor_id INTEGER REFERENCES tutors(id),
|
||||
course_id INTEGER REFERENCES courses(id),
|
||||
PRIMARY KEY (tutor_id, course_id)
|
||||
);
|
||||
|
||||
CREATE TABLE students (
|
||||
id INTEGER PRIMARY KEY,
|
||||
course_id INTEGER NOT NULL REFERENCES courses(id),
|
||||
name TEXT NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE rooms (
|
||||
id INTEGER PRIMARY KEY,
|
||||
name TEXT NOT NULL,
|
||||
layout_json TEXT NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE sessions (
|
||||
id INTEGER PRIMARY KEY,
|
||||
course_id INTEGER NOT NULL REFERENCES courses(id),
|
||||
week_nr INTEGER NOT NULL,
|
||||
date TEXT NOT NULL CHECK (date GLOB '????-??-??'),
|
||||
UNIQUE(course_id, week_nr)
|
||||
);
|
||||
|
||||
CREATE TABLE slots (
|
||||
id INTEGER PRIMARY KEY,
|
||||
session_id INTEGER NOT NULL REFERENCES sessions(id),
|
||||
room_id INTEGER REFERENCES rooms(id),
|
||||
tutor_id INTEGER NOT NULL REFERENCES tutors(id),
|
||||
start_time TEXT NOT NULL,
|
||||
end_time TEXT NOT NULL,
|
||||
status TEXT NOT NULL DEFAULT 'closed',
|
||||
code TEXT UNIQUE
|
||||
);
|
||||
|
||||
CREATE TABLE attendances (
|
||||
id INTEGER PRIMARY KEY,
|
||||
slot_id INTEGER NOT NULL REFERENCES slots(id),
|
||||
student_id INTEGER NOT NULL REFERENCES students(id),
|
||||
seat_id TEXT,
|
||||
checked_in_at TEXT NOT NULL CHECK (checked_in_at GLOB '????-??-??T??:??:??*'),
|
||||
UNIQUE(slot_id, student_id),
|
||||
UNIQUE(slot_id, seat_id)
|
||||
);
|
||||
|
||||
CREATE TABLE notes (
|
||||
id INTEGER PRIMARY KEY,
|
||||
slot_id INTEGER NOT NULL REFERENCES slots(id),
|
||||
student_id INTEGER NOT NULL REFERENCES students(id),
|
||||
tutor_id INTEGER NOT NULL REFERENCES tutors(id),
|
||||
content TEXT NOT NULL DEFAULT '',
|
||||
updated_at TEXT NOT NULL CHECK (updated_at GLOB '????-??-??T??:??:??*'),
|
||||
UNIQUE(slot_id, student_id, tutor_id)
|
||||
);
|
||||
+42
-2
@@ -1,5 +1,45 @@
|
||||
use sqlx::SqlitePool;
|
||||
use sqlx::{sqlite::SqlitePoolOptions, SqlitePool};
|
||||
|
||||
pub async fn init() -> Result<SqlitePool, sqlx::Error> {
|
||||
todo!()
|
||||
let url = std::env::var("DATABASE_URL")
|
||||
.unwrap_or_else(|_| "sqlite:attendance.db".into());
|
||||
let pool = SqlitePoolOptions::new()
|
||||
.after_connect(|conn, _| Box::pin(async move {
|
||||
sqlx::query("PRAGMA foreign_keys = ON")
|
||||
.execute(conn).await?;
|
||||
Ok(())
|
||||
}))
|
||||
.connect(&url).await?;
|
||||
sqlx::migrate!("./migrations").run(&pool).await?;
|
||||
Ok(pool)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
// NOTE: #[sqlx::test] injects its own pool, bypassing after_connect.
|
||||
// We test FK enforcement behaviorally: insert a row with a bad FK and assert it fails.
|
||||
// after_connect is manually invoked in the test setup below.
|
||||
#[sqlx::test(migrations = "./migrations")]
|
||||
async fn foreign_keys_enforced(pool: SqlitePool) {
|
||||
// Enable FK for this test connection (mirrors what after_connect does in production)
|
||||
sqlx::query("PRAGMA foreign_keys = ON").execute(&pool).await.unwrap();
|
||||
|
||||
let err = sqlx::query(
|
||||
"INSERT INTO students (course_id, name) VALUES (999, 'Ghost')"
|
||||
).execute(&pool).await;
|
||||
assert!(err.is_err(), "FK violation should be rejected when foreign_keys = ON");
|
||||
}
|
||||
|
||||
#[sqlx::test(migrations = "./migrations")]
|
||||
async fn all_tables_exist(pool: SqlitePool) {
|
||||
for table in &["courses","tutors","tutor_courses","students","rooms",
|
||||
"sessions","slots","attendances","notes"] {
|
||||
let count: (i64,) = sqlx::query_as(
|
||||
"SELECT count(*) FROM sqlite_master WHERE type='table' AND name=?"
|
||||
).bind(table).fetch_one(&pool).await.unwrap();
|
||||
assert_eq!(count.0, 1, "table {table} missing");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user