Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
103 changes: 61 additions & 42 deletions crates/wastebin_core/src/db.rs
Original file line number Diff line number Diff line change
Expand Up @@ -59,9 +59,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<(), Error>>,
result: oneshot::Sender<Result<(Id, write::Entry), Error>>,
},
Get {
id: Id,
Expand Down Expand Up @@ -396,9 +395,9 @@ impl Handler {
};

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 } => {
Expand Down Expand Up @@ -447,31 +446,56 @@ impl Handler {

fn insert(
&self,
id: Id,
write::DatabaseEntry { entry, data, nonce }: write::DatabaseEntry,
) -> Result<(), Error> {
) -> Result<(Id, write::Entry), Error> {
let mut counter = 0;
let title = entry.title.clone();
let nonce = nonce.as_ref().map(|n| n.as_slice());

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,
],
)?,
};
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, 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"),
title,
],
),
};

Ok(())
match result {
Err(rusqlite::Error::SqliteFailure(
rusqlite::ffi::Error {
code,
extended_code,
},
Some(ref _message),
)) if code == rusqlite::ErrorCode::ConstraintViolation
&& extended_code == rusqlite::ffi::SQLITE_CONSTRAINT_PRIMARYKEY
&& 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<Metadata, Error> {
Expand Down Expand Up @@ -623,13 +647,14 @@ impl Database {
Ok((Self { sender }, fut))
}

/// 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> {
/// Insert `entry` under a new random id into the database and optionally set owner to `uid`.
/// Returns the id of the new entry on success.
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)?;

Expand Down Expand Up @@ -770,8 +795,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");
Expand Down Expand Up @@ -807,8 +831,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;

Expand All @@ -822,8 +845,7 @@ mod tests {
async fn delete() -> Result<(), Box<dyn std::error::Error>> {
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());
Expand All @@ -836,14 +858,13 @@ mod tests {
async fn delete_for() -> Result<(), Box<dyn std::error::Error>> {
let db = new_db()?;

let id = Id::from(1234u32);
let uid = 42;

let entry = write::Entry {
uid: Some(uid),
..Default::default()
};
db.insert(id, entry).await?;
let (id, _entry) = db.insert(entry).await?;

assert!(db.get(id, None).await.is_ok());
assert!(db.delete_for(id, uid).await.is_ok());
Expand All @@ -853,7 +874,7 @@ mod tests {
uid: Some(uid),
..Default::default()
};
db.insert(id, entry).await?;
let (id, _entry) = db.insert(entry).await?;

let incorrect_uid = 99;
assert!(matches!(
Expand All @@ -875,14 +896,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(())
}
Expand All @@ -899,8 +919,7 @@ mod tests {
..Default::default()
};

let id = Id::from(5678u32);
db.insert(id, entry).await?;
let (id, _entry) = db.insert(entry).await?;

let metadata = db.get_metadata(id).await?;
assert_eq!(metadata.uid, Some(42));
Expand Down
4 changes: 1 addition & 3 deletions crates/wastebin_server/src/handlers/insert/api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ use serde::{Deserialize, Serialize};

use crate::errors::{Error, JsonErrorResponse};
use wastebin_core::db::{Database, write};
use wastebin_core::id::Id;

#[derive(Debug, Serialize, Deserialize)]
pub(crate) struct Entry {
Expand Down Expand Up @@ -41,10 +40,9 @@ pub async fn post(
State(db): State<Database>,
Json(entry): Json<Entry>,
) -> Result<Json<RedirectResponse>, 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 }))
}
Expand Down
5 changes: 1 addition & 4 deletions crates/wastebin_server/src/handlers/insert/form.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ use crate::handlers::cookie;
use crate::handlers::extract::{Theme, Uid};
use crate::handlers::html::make_error;
use wastebin_core::db::{Database, write};
use wastebin_core::id::Id;

#[derive(Debug, Default, Serialize, Deserialize)]
pub(crate) struct Entry {
Expand Down Expand Up @@ -67,7 +66,7 @@ pub async fn post<E: std::fmt::Debug>(
let mut entry: write::Entry = entry.into();
entry.uid = Some(uid);

let id = Id::rand();
let (id, entry) = db.insert(entry).await?;

let url = {
let url_path = id.to_url_path(&entry);
Expand All @@ -78,8 +77,6 @@ pub async fn post<E: std::fmt::Debug>(
}
};

db.insert(id, entry).await?;

let mut cookie = cookie("uid", uid.to_string());
cookie.set_secure(true);

Expand Down
Loading