diff --git a/crates/wastebin_core/src/db.rs b/crates/wastebin_core/src/db.rs index a56b6323..14642a8d 100644 --- a/crates/wastebin_core/src/db.rs +++ b/crates/wastebin_core/src/db.rs @@ -95,9 +95,8 @@ struct Handler { /// Commands issued to the database handler and corresponding to [`Database`] calls. enum Command { Insert { - id: Id, entry: write::DatabaseEntry, - result: oneshot::Sender>, + result: oneshot::Sender>, }, Get { id: Id, @@ -382,9 +381,9 @@ impl Handler { fn run(mut self) -> Result<(), Error> { while let Ok(command) = self.receiver.recv() { match command { - Command::Insert { id, entry, result } => { + Command::Insert { entry, result } => { result - .send(self.insert(id, entry)) + .send(self.insert(entry)) .map_err(|_| Error::ResultSendError)?; } Command::Get { id, result } => { @@ -430,29 +429,54 @@ impl Handler { fn insert( &self, - id: Id, write::DatabaseEntry { entry, data, nonce }: write::DatabaseEntry, - ) -> Result<(), Error> { - match entry.expires { - None => self.conn.execute( - "INSERT INTO entries (id, uid, data, burn_after_reading, nonce, title) VALUES (?1, ?2, ?3, ?4, ?5, ?6)", - params![id.to_i64(), entry.uid, data, entry.burn_after_reading, nonce, entry.title], - )?, - Some(expires) => self.conn.execute( - "INSERT INTO entries (id, uid, data, burn_after_reading, nonce, expires, title) VALUES (?1, ?2, ?3, ?4, ?5, datetime('now', ?6), ?7)", - params![ - id.to_i64(), - entry.uid, - data, - entry.burn_after_reading, - nonce, - format!("{expires} seconds"), - entry.title, - ], - )?, - }; - - Ok(()) + ) -> Result<(Id, write::Entry), Error> { + let mut counter = 0; + + loop { + let id = Id::rand(); + + let result = match entry.expires { + None => self.conn.execute( + "INSERT INTO entries (id, uid, data, burn_after_reading, nonce, title) VALUES (?1, ?2, ?3, ?4, ?5, ?6)", + params![id.to_i64(), entry.uid, data, entry.burn_after_reading, nonce, entry.title], + ), + Some(expires) => self.conn.execute( + "INSERT INTO entries (id, uid, data, burn_after_reading, nonce, expires, title) VALUES (?1, ?2, ?3, ?4, ?5, datetime('now', ?6), ?7)", + params![ + id.to_i64(), + entry.uid, + data, + entry.burn_after_reading, + nonce, + format!("{expires} seconds"), + entry.title, + ], + ), + }; + + match result { + Err(rusqlite::Error::SqliteFailure( + rusqlite::ffi::Error { + code, + extended_code, + }, + Some(ref _message), + )) if code == rusqlite::ErrorCode::ConstraintViolation + && extended_code == 1555 + && counter < 10 => + { + /* Retry if ID is already existent */ + counter += 1; + continue; + } + Err(err) => break Err(err)?, + Ok(rows) => { + debug_assert!(rows == 1); + return Ok((id, entry)); + } + } + } } fn get_metadata(&self, id: Id) -> Result { @@ -576,12 +600,12 @@ impl Database { } /// Insert `entry` under `id` into the database and optionally set owner to `uid`. - pub async fn insert(&self, id: Id, entry: write::Entry) -> Result<(), Error> { + pub async fn insert(&self, entry: write::Entry) -> Result<(Id, write::Entry), Error> { let entry = entry.compress().await?.encrypt().await?; let (result, command_result) = oneshot::channel(); self.sender - .send(Command::Insert { id, entry, result }) + .send(Command::Insert { entry, result }) .await .map_err(|_| Error::SendError)?; @@ -712,8 +736,7 @@ mod tests { ..Default::default() }; - let id = Id::from(1234u32); - db.insert(id, entry).await?; + let (id, _entry) = db.insert(entry).await?; let entry = db.get(id, None).await?.unwrap_inner(); assert_eq!(entry.text, "hello world"); @@ -735,8 +758,7 @@ mod tests { ..Default::default() }; - let id = Id::from(1234u32); - db.insert(id, entry).await?; + let (id, _entry) = db.insert(entry).await?; tokio::time::sleep(tokio::time::Duration::from_secs(2)).await; @@ -751,8 +773,7 @@ mod tests { async fn delete() -> Result<(), Box> { let db = new_db()?; - let id = Id::from(1234u32); - db.insert(id, write::Entry::default()).await?; + let (id, _entry) = db.insert(write::Entry::default()).await?; assert!(db.get(id, None).await.is_ok()); assert!(db.delete(id).await.is_ok()); @@ -770,14 +791,13 @@ mod tests { ..Default::default() }; - let id = Id::from(1234u32); - db.insert(id, entry).await?; + let (id, _entry) = db.insert(entry).await?; tokio::time::sleep(tokio::time::Duration::from_secs(2)).await; let ids = db.purge().await?; assert_eq!(ids.len(), 1); - assert_eq!(ids[0].to_i64(), 1234); + assert_eq!(ids[0], id); Ok(()) } diff --git a/crates/wastebin_server/src/handlers/insert/api.rs b/crates/wastebin_server/src/handlers/insert/api.rs index 3de5afbb..f6482203 100644 --- a/crates/wastebin_server/src/handlers/insert/api.rs +++ b/crates/wastebin_server/src/handlers/insert/api.rs @@ -4,7 +4,6 @@ use axum::extract::State; use serde::{Deserialize, Serialize}; use std::num::NonZeroU32; use wastebin_core::db::{Database, write}; -use wastebin_core::id::Id; #[derive(Debug, Serialize, Deserialize)] pub(crate) struct Entry { @@ -39,10 +38,9 @@ pub async fn post( State(db): State, Json(entry): Json, ) -> Result, JsonErrorResponse> { - let id = Id::rand(); let entry: write::Entry = entry.into(); + let (id, entry) = db.insert(entry).await.map_err(Error::Database)?; let path = format!("/{}", id.to_url_path(&entry)); - db.insert(id, entry).await.map_err(Error::Database)?; Ok(Json::from(RedirectResponse { path })) } diff --git a/crates/wastebin_server/src/handlers/insert/form.rs b/crates/wastebin_server/src/handlers/insert/form.rs index 3ed6935c..b4965801 100644 --- a/crates/wastebin_server/src/handlers/insert/form.rs +++ b/crates/wastebin_server/src/handlers/insert/form.rs @@ -8,7 +8,6 @@ use axum_extra::extract::cookie::{Cookie, SameSite, SignedCookieJar}; use serde::{Deserialize, Serialize}; use std::num::NonZeroU32; use wastebin_core::db::{Database, write}; -use wastebin_core::id::Id; #[derive(Debug, Default, Serialize, Deserialize)] pub(crate) struct Entry { @@ -79,14 +78,14 @@ pub async fn post( let mut entry: write::Entry = entry.into(); entry.uid = Some(uid); - let id = Id::rand(); + let (id, entry) = db.insert(entry).await?; + let mut url = id.to_url_path(&entry); if entry.burn_after_reading.unwrap_or(false) { url = format!("burn/{url}"); } - db.insert(id, entry).await?; let url = format!("/{url}"); let cookie = Cookie::build(("uid", uid.to_string()))