diff --git a/src/api/mod.rs b/src/api/mod.rs index 80dc442..14eb428 100644 --- a/src/api/mod.rs +++ b/src/api/mod.rs @@ -1,2 +1,5 @@ pub mod v1; pub mod versions; + +// This is purely for testing, will be deleted at a later date +pub mod v0; \ No newline at end of file diff --git a/src/api/v0/channel.rs b/src/api/v0/channel.rs new file mode 100644 index 0000000..b87f15b --- /dev/null +++ b/src/api/v0/channel.rs @@ -0,0 +1,62 @@ +use actix_web::{Error, HttpResponse, error, post, web}; +use futures::StreamExt; +use log::error; +use serde::{Deserialize, Serialize}; +use sqlx::prelude::FromRow; + +use crate::{Data, api::v1::auth::check_access_token}; + +#[derive(Deserialize)] +struct Request { + access_token: String, + start: i32, + amount: i32, +} + +#[derive(Serialize, FromRow)] +struct Response { + timestamp: i64, + uuid: String, + message: String, +} + +const MAX_SIZE: usize = 262_144; + +#[post("/channel")] +pub async fn res( + mut payload: web::Payload, + data: web::Data, +) -> Result { + let mut body = web::BytesMut::new(); + while let Some(chunk) = payload.next().await { + let chunk = chunk?; + // limit max size of in-memory payload + if (body.len() + chunk.len()) > MAX_SIZE { + return Err(error::ErrorBadRequest("overflow")); + } + body.extend_from_slice(&chunk); + } + + let request = serde_json::from_slice::(&body)?; + + let authorized = check_access_token(request.access_token, &data.pool).await; + + if let Err(error) = authorized { + return Ok(error); + } + + let row = sqlx::query_as("SELECT timestamp, CAST(uuid AS VARCHAR), message FROM channel ORDER BY timestamp DESC LIMIT $1 OFFSET $2") + .bind(request.amount) + .bind(request.start) + .fetch_all(&data.pool) + .await; + + if let Err(error) = row { + error!("{}", error); + return Ok(HttpResponse::InternalServerError().finish()); + } + + let messages: Vec = row.unwrap(); + + Ok(HttpResponse::Ok().json(messages)) +} diff --git a/src/api/v0/mod.rs b/src/api/v0/mod.rs new file mode 100644 index 0000000..99d2ab9 --- /dev/null +++ b/src/api/v0/mod.rs @@ -0,0 +1,10 @@ +use actix_web::{Scope, web}; + +mod channel; +mod send; + +pub fn web() -> Scope { + web::scope("/v0") + .service(channel::res) + .service(send::res) +} diff --git a/src/api/v0/send.rs b/src/api/v0/send.rs new file mode 100644 index 0000000..ea5e353 --- /dev/null +++ b/src/api/v0/send.rs @@ -0,0 +1,60 @@ +use std::time::{SystemTime, UNIX_EPOCH}; + +use actix_web::{Error, HttpResponse, error, post, web}; +use futures::StreamExt; +use log::error; +use serde::Deserialize; + +use crate::{Data, api::v1::auth::check_access_token}; + +#[derive(Deserialize)] +struct Request { + access_token: String, + message: String, +} + +const MAX_SIZE: usize = 262_144; + +#[post("/send")] +pub async fn res( + mut payload: web::Payload, + data: web::Data, +) -> Result { + let mut body = web::BytesMut::new(); + while let Some(chunk) = payload.next().await { + let chunk = chunk?; + // limit max size of in-memory payload + if (body.len() + chunk.len()) > MAX_SIZE { + return Err(error::ErrorBadRequest("overflow")); + } + body.extend_from_slice(&chunk); + } + + let request = serde_json::from_slice::(&body)?; + + let authorized = check_access_token(request.access_token, &data.pool).await; + + if let Err(error) = authorized { + return Ok(error); + } + + let uuid = authorized.unwrap(); + + let current_time = SystemTime::now() + .duration_since(UNIX_EPOCH) + .unwrap() + .as_secs() as i64; + + let row = sqlx::query(&format!("INSERT INTO channel (timestamp, uuid, message) VALUES ($1, '{}', $2)", uuid)) + .bind(current_time) + .bind(request.message) + .execute(&data.pool) + .await; + + if let Err(error) = row { + error!("{}", error); + return Ok(HttpResponse::InternalServerError().finish()); + } + + Ok(HttpResponse::Ok().finish()) +} diff --git a/src/api/v1/mod.rs b/src/api/v1/mod.rs index a5fd58a..d81db79 100644 --- a/src/api/v1/mod.rs +++ b/src/api/v1/mod.rs @@ -1,6 +1,6 @@ use actix_web::{Scope, web}; -mod auth; +pub mod auth; mod stats; mod users; diff --git a/src/main.rs b/src/main.rs index 4c909b1..ac7f116 100644 --- a/src/main.rs +++ b/src/main.rs @@ -71,6 +71,11 @@ async fn main() -> Result<(), Error> { refresh_token varchar(64) UNIQUE NOT NULL REFERENCES refresh_tokens(token), uuid uuid NOT NULL REFERENCES users(uuid), created int8 NOT NULL + ); + CREATE TABLE IF NOT EXISTS channel ( + timestamp int8 PRIMARY KEY NOT NULL, + uuid uuid NOT NULL REFERENCES users(uuid), + message varchar(2000) NOT NULL ) "#, ) @@ -90,6 +95,7 @@ async fn main() -> Result<(), Error> { .app_data(web::Data::new(data.clone())) .service(api::versions::res) .service(api::v1::web()) + .service(api::v0::web()) }) .bind((web.url, web.port))? .run()