CREATE TABLE IF NOT EXISTS users ( id SERIAL PRIMARY KEY, username TEXT NOT NULL, password_hash TEXT NOT NULL, created_at TIMESTAMPTZ NOT NULL DEFAULT now() ); CREATE UNIQUE INDEX IF NOT EXISTS users_username_lower_key ON users (lower(username)); CREATE TABLE IF NOT EXISTS sessions ( token_hash BYTEA PRIMARY KEY, user_id INT NOT NULL REFERENCES users ON DELETE CASCADE, created_at TIMESTAMPTZ NOT NULL DEFAULT now(), expires_at TIMESTAMPTZ NOT NULL ); CREATE INDEX IF NOT EXISTS sessions_user_id_idx ON sessions (user_id); CREATE TABLE IF NOT EXISTS genres ( id SMALLSERIAL PRIMARY KEY, name TEXT NOT NULL UNIQUE ); -- WHERE NOT EXISTS instead of ON CONFLICT: ON CONFLICT consumes a sequence value per row -- on every startup, which would eventually overflow SMALLSERIAL. INSERT INTO genres (name) SELECT seed.name FROM (VALUES ('Fiction'), ('Non-Fiction'), ('Mystery'), ('Thriller'), ('Science Fiction'), ('Fantasy'), ('Romance'), ('Horror'), ('Historical Fiction'), ('Biography'), ('Memoir'), ('Self-Help'), ('Science'), ('History'), ('Poetry'), ('Other')) AS seed(name) WHERE NOT EXISTS (SELECT 1 FROM genres WHERE genres.name = seed.name); CREATE TABLE IF NOT EXISTS books ( id SERIAL PRIMARY KEY, user_id INT NOT NULL REFERENCES users ON DELETE CASCADE, title TEXT NOT NULL CHECK (btrim(title) <> ''), author TEXT NOT NULL CHECK (btrim(author) <> ''), genre_id SMALLINT NOT NULL REFERENCES genres, total_pages INT NOT NULL CHECK (total_pages > 0), current_page INT NOT NULL DEFAULT 0, created_at TIMESTAMPTZ NOT NULL DEFAULT now(), updated_at TIMESTAMPTZ NOT NULL DEFAULT now(), CONSTRAINT books_current_page_range CHECK (current_page BETWEEN 0 AND total_pages) ); CREATE INDEX IF NOT EXISTS books_user_id_idx ON books (user_id); CREATE TABLE IF NOT EXISTS notes ( id SERIAL PRIMARY KEY, book_id INT NOT NULL REFERENCES books ON DELETE CASCADE, body TEXT NOT NULL CHECK (btrim(body) <> ''), created_at TIMESTAMPTZ NOT NULL DEFAULT now(), updated_at TIMESTAMPTZ NOT NULL DEFAULT now() ); CREATE INDEX IF NOT EXISTS notes_book_id_idx ON notes (book_id);