Allow creation of new document by POST request

A post request to `api/create/<language>` creates a new document
and returns the newly generated id. It uses all the input as text.
This commit is contained in:
Matthias Bilger 2023-03-10 16:37:33 +01:00 committed by Matthias Bilger
parent 14dde3c283
commit cd5bd8fcef
6 changed files with 134 additions and 2871 deletions

1
Cargo.lock generated
View file

@ -1060,6 +1060,7 @@ version = "0.1.0"
dependencies = [
"anyhow",
"bytecount",
"bytes",
"dashmap",
"dotenv",
"futures",

2923
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -6,6 +6,7 @@ edition = "2021"
[dependencies]
anyhow = "1.0.40"
bytes = "1.0"
bytecount = "0.6"
dashmap = "4.0.2"
dotenv = "0.15.0"

View file

@ -78,4 +78,14 @@ ON CONFLICT(id) DO UPDATE SET
.await?;
Ok(row.0 as usize)
}
/// Count the number of documents in the database.
pub async fn exists(&self, document_id: &str) -> Result<bool> {
let row: (i64,) = sqlx::query_as(r#"SELECT count(*) FROM document WHERE id = $1"#)
.bind(document_id)
.fetch_one(&self.pool)
.await?;
Ok(row.0 > 0)
}
}

View file

@ -5,10 +5,12 @@
use std::sync::Arc;
use std::time::{Duration, SystemTime};
use rand::distributions::Alphanumeric;
use bytes::Bytes;
use dashmap::DashMap;
use log::{error, info};
use rand::Rng;
use rand::{thread_rng, Rng};
use serde::Serialize;
use tokio::time::{self, Instant};
use warp::{filters::BoxedFilter, ws::Ws, Filter, Rejection, Reply};
@ -119,16 +121,24 @@ fn backend(config: ServerConfig) -> BoxedFilter<(impl Reply,)> {
.and(state_filter.clone())
.and_then(text_handler);
let text_post =
warp::post()
.and(warp::path!("create" / String))
.and(warp::body::bytes())
.and(state_filter.clone())
.and_then(text_post_handler);
let start_time = SystemTime::now()
.duration_since(SystemTime::UNIX_EPOCH)
.expect("SystemTime returned before UNIX_EPOCH")
.as_secs();
let stats = warp::path!("stats")
.and(warp::any().map(move || start_time))
.and(state_filter)
.and_then(stats_handler);
socket.or(text).or(stats).boxed()
socket.or(text_post).or(text).or(stats).boxed()
}
/// Handler for the `/api/socket/{id}` endpoint.
@ -172,6 +182,41 @@ async fn text_handler(id: String, state: ServerState) -> Result<impl Reply, Reje
})
}
/// Handler for the `/api/text/{id}` endpoint.
async fn text_post_handler(language: String, bytes: bytes::Bytes, state: ServerState) -> Result<impl Reply, Rejection> {
let mut retry = true;
let mut id:String = "".to_string();
let maybe_text = String::from_utf8(bytes.to_vec())
.map_err(|_| warp::reject());
let text = maybe_text.unwrap_or_default();
while retry{
id = thread_rng()
.sample_iter(&Alphanumeric)
.take(6)
.map(char::from)
.collect();
if !state.documents.contains_key(&id){
if let Some(db) = state.database.clone() {
if let Ok(entryexists) = db.exists(&id).await{
if !entryexists {
println!("{}", text);
let rustpad = Arc::new(Rustpad::from((&language, &text)));
tokio::spawn(persister(id.clone(), Arc::clone(&rustpad), db.clone()));
state.documents.insert(id.clone(), Document::new(rustpad));
retry = false;
}
}
}else{
println!("{}", text);
let rustpad = Arc::new(Rustpad::from((&language, &text)));
state.documents.insert(id.clone(), Document::new(rustpad));
retry = false;
}
}
}
Ok(id)
}
/// Handler for the `/api/stats` endpoint.
async fn stats_handler(start_time: u64, state: ServerState) -> Result<impl Reply, Rejection> {
let num_documents = state.documents.len();

View file

@ -129,6 +129,27 @@ impl From<PersistedDocument> for Rustpad {
}
}
impl From<(&String, &String)> for Rustpad {
fn from(create: (&String, &String)) -> Self {
let language = create.0;
let text = create.1;
let mut operation = OperationSeq::default();
operation.insert(text);
let rustpad = Self::default();
{
let mut state = rustpad.state.write();
state.text = text.to_string();
state.language = Some(language.clone());
state.operations.push(UserOperation {
id: u64::MAX,
operation,
})
}
rustpad
}
}
impl Rustpad {
/// Handle a connection from a WebSocket.
pub async fn on_connection(&self, socket: WebSocket) {