From 1d7cdf343b08e9a50ca1b9e72dc6f96b17954396 Mon Sep 17 00:00:00 2001 From: Radical Date: Fri, 2 May 2025 19:19:59 +0200 Subject: [PATCH] feat: add users endpoint and add me and uuid under it Adds a users endpoint that returns all users on the server, will require instance permissions in future. Place previous user requests under users to avoid having multiple endpoints. --- Cargo.toml | 2 +- src/api/v1/mod.rs | 4 +- src/api/v1/users/me.rs | 66 +++++++++++++++++++++++ src/api/v1/users/mod.rs | 77 +++++++++++++++++++++++++++ src/api/v1/{user.rs => users/uuid.rs} | 18 ++----- 5 files changed, 149 insertions(+), 18 deletions(-) create mode 100644 src/api/v1/users/me.rs create mode 100644 src/api/v1/users/mod.rs rename src/api/v1/{user.rs => users/uuid.rs} (80%) diff --git a/Cargo.toml b/Cargo.toml index 624a983..e34d9b6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,7 +23,7 @@ simple_logger = "5.0.0" sqlx = { version = "0.8", features = ["runtime-tokio", "tls-native-tls", "postgres"] } toml = "0.8" url = { version = "2.5", features = ["serde"] } -uuid = { version = "1.16", features = ["v7"] } +uuid = { version = "1.16", features = ["serde", "v7"] } [dependencies.tokio] version = "1.44" diff --git a/src/api/v1/mod.rs b/src/api/v1/mod.rs index 2405b24..a5fd58a 100644 --- a/src/api/v1/mod.rs +++ b/src/api/v1/mod.rs @@ -2,11 +2,11 @@ use actix_web::{Scope, web}; mod auth; mod stats; -mod user; +mod users; pub fn web() -> Scope { web::scope("/v1") .service(stats::res) .service(auth::web()) - .service(user::res) + .service(users::web()) } diff --git a/src/api/v1/users/me.rs b/src/api/v1/users/me.rs new file mode 100644 index 0000000..18e6ba8 --- /dev/null +++ b/src/api/v1/users/me.rs @@ -0,0 +1,66 @@ +use actix_web::{Error, HttpResponse, error, post, web}; +use futures::StreamExt; +use log::error; +use serde::{Deserialize, Serialize}; + +use crate::{Data, api::v1::auth::check_access_token}; + +#[derive(Deserialize)] +struct AuthenticationRequest { + access_token: String, +} + +#[derive(Serialize)] +struct Response { + uuid: String, + username: String, + display_name: String, +} + +const MAX_SIZE: usize = 262_144; + +#[post("/me")] +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 authentication_request = serde_json::from_slice::(&body)?; + + let authorized = check_access_token(authentication_request.access_token, &data.pool).await; + + if let Err(error) = authorized { + return Ok(error); + } + + let uuid = authorized.unwrap(); + + let row = sqlx::query_as(&format!( + "SELECT username, display_name FROM users WHERE uuid = '{}'", + uuid + )) + .fetch_one(&data.pool) + .await; + + if let Err(error) = row { + error!("{}", error); + return Ok(HttpResponse::InternalServerError().finish()); + } + + let (username, display_name): (String, Option) = row.unwrap(); + + Ok(HttpResponse::Ok().json(Response { + uuid: uuid.to_string(), + username, + display_name: display_name.unwrap_or_default(), + })) +} diff --git a/src/api/v1/users/mod.rs b/src/api/v1/users/mod.rs new file mode 100644 index 0000000..937d857 --- /dev/null +++ b/src/api/v1/users/mod.rs @@ -0,0 +1,77 @@ +use actix_web::{error, post, web, Error, HttpResponse, Scope}; +use futures::StreamExt; +use log::error; +use serde::{Deserialize, Serialize}; +use sqlx::prelude::FromRow; +use crate::{Data, api::v1::auth::check_access_token}; + +mod me; +mod uuid; + +#[derive(Deserialize)] +struct Request { + access_token: String, + start: i32, + amount: i32, +} + +#[derive(Serialize, FromRow)] +struct Response { + uuid: String, + username: String, + display_name: Option, + email: String, +} + +const MAX_SIZE: usize = 262_144; + +pub fn web() -> Scope { + web::scope("/users") + .service(res) + .service(me::res) + .service(uuid::res) +} + +#[post("")] +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)?; + + if request.amount > 100 { + return Ok(HttpResponse::BadRequest().finish()) + } + + 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 CAST(uuid AS VARCHAR), username, display_name, email FROM users ORDER BY username 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 accounts: Vec = row.unwrap(); + + Ok(HttpResponse::Ok().json(accounts)) +} + diff --git a/src/api/v1/user.rs b/src/api/v1/users/uuid.rs similarity index 80% rename from src/api/v1/user.rs rename to src/api/v1/users/uuid.rs index b68ae90..41d87cc 100644 --- a/src/api/v1/user.rs +++ b/src/api/v1/users/uuid.rs @@ -20,10 +20,10 @@ struct Response { const MAX_SIZE: usize = 262_144; -#[post("/user/{uuid}")] +#[post("/{uuid}")] pub async fn res( mut payload: web::Payload, - path: web::Path<(String,)>, + path: web::Path<(Uuid,)>, data: web::Data, ) -> Result { let mut body = web::BytesMut::new(); @@ -36,7 +36,7 @@ pub async fn res( body.extend_from_slice(&chunk); } - let request = path.into_inner().0; + let uuid = path.into_inner().0; let authentication_request = serde_json::from_slice::(&body)?; @@ -46,18 +46,6 @@ pub async fn res( return Ok(error); } - let mut uuid = authorized.unwrap(); - - if request != "me" { - let requested_uuid = Uuid::parse_str(&request); - - if requested_uuid.is_err() { - return Ok(HttpResponse::BadRequest().json(r#"{ "error": "UUID is invalid!" }"#)); - } - - uuid = requested_uuid.unwrap() - } - let row = sqlx::query_as(&format!( "SELECT username, display_name FROM users WHERE uuid = '{}'", uuid