diff --git a/Cargo.lock b/Cargo.lock index 13414d2..0833cd2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -243,6 +243,21 @@ version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" +[[package]] +name = "android-tzdata" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + [[package]] name = "anstream" version = "0.6.18" @@ -382,6 +397,12 @@ dependencies = [ "alloc-stdlib", ] +[[package]] +name = "bumpalo" +version = "3.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" + [[package]] name = "bytemuck" version = "1.20.0" @@ -426,6 +447,20 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "chrono" +version = "0.4.38" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a21f936df1771bf62b77f047b726c4625ff2e8aa607c01ec06e5a05bd8463401" +dependencies = [ + "android-tzdata", + "iana-time-zone", + "js-sys", + "num-traits", + "wasm-bindgen", + "windows-targets 0.52.6", +] + [[package]] name = "colorchoice" version = "1.0.3" @@ -464,6 +499,12 @@ dependencies = [ "version_check", ] +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + [[package]] name = "cpufeatures" version = "0.2.16" @@ -822,6 +863,7 @@ name = "gunnhildr" version = "0.1.0" dependencies = [ "actix-web", + "chrono", "env_logger", "figment", "log", @@ -942,6 +984,29 @@ version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" +[[package]] +name = "iana-time-zone" +version = "0.1.61" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "235e081f3925a06703c2d0117ea8b91f042756fd6e7a6e5d901e8ca1a996b220" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "wasm-bindgen", + "windows-core", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + [[package]] name = "icu_collections" version = "1.5.0" @@ -1124,6 +1189,16 @@ dependencies = [ "libc", ] +[[package]] +name = "js-sys" +version = "0.3.76" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6717b6b5b077764fb5966237269cb3c64edddde4b14ce42647430a78ced9e7b7" +dependencies = [ + "once_cell", + "wasm-bindgen", +] + [[package]] name = "language-tags" version = "0.3.2" @@ -2282,6 +2357,60 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b8dad83b4f25e74f184f64c43b150b91efe7647395b42289f38e50566d82855b" +[[package]] +name = "wasm-bindgen" +version = "0.2.99" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a474f6281d1d70c17ae7aa6a613c87fce69a127e2624002df63dcb39d6cf6396" +dependencies = [ + "cfg-if", + "once_cell", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.99" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f89bb38646b4f81674e8f5c3fb81b562be1fd936d84320f3264486418519c79" +dependencies = [ + "bumpalo", + "log", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.99" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2cc6181fd9a7492eef6fef1f33961e3695e4579b9872a6f7c83aee556666d4fe" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.99" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30d7a95b763d3c45903ed6c81f156801839e5ee968bb07e534c44df0fcd330c2" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.99" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "943aab3fdaaa029a6e0271b35ea10b72b943135afe9bffca82384098ad0e06a6" + [[package]] name = "whoami" version = "1.5.2" @@ -2292,6 +2421,15 @@ dependencies = [ "wasite", ] +[[package]] +name = "windows-core" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" +dependencies = [ + "windows-targets 0.52.6", +] + [[package]] name = "windows-sys" version = "0.48.0" diff --git a/Cargo.toml b/Cargo.toml index 2fbb05f..bfc9501 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,6 +5,7 @@ edition = "2021" [dependencies] actix-web = {version="4.9.0"} +chrono = "0.4.38" env_logger = "0.11.5" figment = {version="0.10.19", features=["env"]} log = "0.4.22" diff --git a/migrations/sqlite/20241207082654_initial_table_setup.sql b/migrations/sqlite/20241207082654_initial_table_setup.sql index 3fb9ee3..4f90b11 100644 --- a/migrations/sqlite/20241207082654_initial_table_setup.sql +++ b/migrations/sqlite/20241207082654_initial_table_setup.sql @@ -7,7 +7,7 @@ CREATE TABLE IF NOT EXISTS books( book_id INTEGER PRIMARY KEY, book_title TEXT NOT NULL, book_description TEXT, - book_creation_date TEXT NOT NULL, + book_creation_date INT NOT NULL, author_id INTEGER NOT NULL, FOREIGN KEY (author_id) REFERENCES users(user_id) ON DELETE CASCADE @@ -17,7 +17,7 @@ CREATE TABLE IF NOT EXISTS chapters( chapter_id INTEGER PRIMARY KEY, chapter_title TEXT NOT NULL, chapter_text TEXT NOT NULL, - chapter_creation_date TEXT NOT NULL, + chapter_creation_date INT NOT NULL, book_id INTEGER NOT NULL, author_id INTEGER NOT NULL, FOREIGN KEY (book_id) REFERENCES books(book_id) ON DELETE CASCADE, diff --git a/src/db/mod.rs b/src/db/mod.rs index a894e29..4cd2f49 100644 --- a/src/db/mod.rs +++ b/src/db/mod.rs @@ -15,6 +15,24 @@ pub enum DataBase { Postgres(sqlx::Pool), } +/// Error type for handling DB related errors +pub enum DbError { + /// No such entry found + NotFound, + /// Database related error + SqlxError(sqlx::Error), +} + +impl From for DbError { + fn from(value: sqlx::Error) -> Self { + match value { + sqlx::Error::RowNotFound => DbError::NotFound, + _ => DbError::SqlxError(value), + } + } +} +type DbResult = Result; + impl DataBase { /// Database backed by SQLite pub async fn sqlite() -> Self { @@ -67,9 +85,65 @@ impl DataBase { Self::Postgres(pool) } - pub fn get_chapter(&self, id: u32) -> models::Chapter { + /// Tries to fetch a book from the database + pub async fn get_book(&self, id: u32) -> DbResult { match self { - DataBase::Sqlite(pool) => sqlite::sqlite_chapter(pool, id), + DataBase::Sqlite(pool) => sqlite::sqlite_book(pool, id).await, + DataBase::Postgres(pool) => todo!(), + } + } + + /// Tries to create a book and returns the book's id if successful + pub async fn create_book( + &self, + title: String, + description: String, + author_id: u32, + ) -> DbResult { + match self { + DataBase::Sqlite(pool) => { + sqlite::sqlite_book_create(pool, title, description, author_id).await + } + DataBase::Postgres(pool) => todo!(), + } + } + + /// Tries to fetch a chapter from the database + pub async fn get_chapter(&self, id: u32) -> DbResult { + match self { + DataBase::Sqlite(pool) => sqlite::sqlite_chapter(pool, id).await, + DataBase::Postgres(pool) => todo!(), + } + } + + /// Tries to create a chapter and returns the chapter's id if successfu + pub async fn create_chapter( + &self, + title: String, + text: String, + book_id: u32, + author_id: u32, + ) -> DbResult { + match self { + DataBase::Sqlite(pool) => { + sqlite::sqlite_chapter_create(pool, title, text, book_id, author_id).await + } + DataBase::Postgres(pool) => todo!(), + } + } + + /// Tries to fetch a user from the database + pub async fn get_user(&self, id: u32) -> DbResult { + match self { + DataBase::Sqlite(pool) => sqlite::sqlite_user(pool, id).await, + DataBase::Postgres(pool) => todo!(), + } + } + + /// Tries to create a user and returns the user's id if successful + pub async fn create_user(&self, name: String) -> DbResult { + match self { + DataBase::Sqlite(pool) => sqlite::sqlite_user_create(pool, name).await, DataBase::Postgres(pool) => todo!(), } } diff --git a/src/db/models.rs b/src/db/models.rs index ac2fd84..470c4d1 100644 --- a/src/db/models.rs +++ b/src/db/models.rs @@ -2,7 +2,7 @@ pub struct Chapter { pub id: u32, pub title: String, pub text: String, - pub creation_data: String, + pub creation_date: String, pub book_id: u32, pub author_id: u32, } diff --git a/src/db/sqlite.rs b/src/db/sqlite.rs index f0a727d..201fabf 100644 --- a/src/db/sqlite.rs +++ b/src/db/sqlite.rs @@ -1,5 +1,99 @@ //! Module containing database code for SQLite +use super::models::{Book, Chapter, User}; +use super::{DbError, DbResult}; +use serde::de; +use sqlx::{Pool, Row, Sqlite}; -pub fn sqlite_chapter(pool: &sqlx::Pool, id: u32) -> super::models::Chapter { - todo!() +pub async fn sqlite_book(pool: &Pool, id: u32) -> DbResult { + let row = sqlx::query("SELECT * FROM users WHERE user_id = ?") + .bind(id) + .fetch_one(pool) + .await?; + + let book = Book { + id, + title: row.get("book_title"), + description: row.get("book_description"), + creation_date: row.get("book_creation_date"), + author_id: row.get("author_id"), + }; + + Ok(book) +} + +pub async fn sqlite_book_create( + pool: &Pool, + title: String, + description: String, + author_id: u32, +) -> DbResult { + let id = sqlx::query("INSERT INTO books (title, description, author_id, book_creation_date) VALUES ( ?1, ?2, ?3, ?4 )") + .bind(title) + .bind(description) + .bind(author_id) + .bind(chrono::Local::now().timestamp()) + .execute(pool) + .await? + .last_insert_rowid() as u32; + Ok(id) +} + +pub async fn sqlite_chapter(pool: &Pool, id: u32) -> DbResult { + let row = sqlx::query("SELECT * FROM chapters WHERE chapter_id = ?") + .bind(id) + .fetch_one(pool) + .await?; + + let chapter = Chapter { + id, + title: row.get("chapter_title"), + text: row.get("chapter_text"), + creation_date: row.get("chapter_creation_date"), + book_id: row.get("book_id"), + author_id: row.get("author_id"), + }; + Ok(chapter) +} + +pub async fn sqlite_chapter_create( + pool: &Pool, + title: String, + text: String, + book_id: u32, + author_id: u32, +) -> DbResult { + let id = sqlx::query("INSERT INTO chapters (chapter_title, chapter_text, book_id, author_id, chapter_creation_date) VALUES ( ?1, ?2, ?3, ?4, ?5 )") + .bind(title) + .bind(text) + .bind(book_id) + .bind(author_id) + .bind(chrono::Local::now().timestamp()) + .execute(pool) + .await? + .last_insert_rowid() as u32; + + Ok(id) +} + +pub async fn sqlite_user(pool: &Pool, id: u32) -> DbResult { + let row = sqlx::query("SELECT * FROM users WHERE user_id = ?") + .bind(id) + .fetch_one(pool) + .await?; + + let user = User { + id, + name: row.get("name"), + }; + + Ok(user) +} + +pub async fn sqlite_user_create(pool: &Pool, name: String) -> DbResult { + let id = sqlx::query("INSERT INTO users (name) VALUES ( ? )") + .bind(name) + .execute(pool) + .await? + .last_insert_rowid() as u32; + Ok(id) }