Skip to content
Open
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
94 changes: 57 additions & 37 deletions crates/wastebin_core/src/db.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<(), Error>>,
result: oneshot::Sender<Result<(Id, write::Entry), Error>>,
},
Get {
id: Id,
Expand Down Expand Up @@ -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 } => {
Expand Down Expand Up @@ -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<Metadata, Error> {
Expand Down Expand Up @@ -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)?;

Expand Down Expand Up @@ -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");
Expand All @@ -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;

Expand All @@ -751,8 +773,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 @@ -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(())
}
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 @@ -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 {
Expand Down Expand Up @@ -39,10 +38,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: 2 additions & 3 deletions crates/wastebin_server/src/handlers/insert/form.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -79,14 +78,14 @@ 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 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()))
Expand Down