From b66c8f0613c75120fe6e7c07a079bc9e76afe0f4 Mon Sep 17 00:00:00 2001 From: Radical Date: Tue, 20 May 2025 18:04:44 +0200 Subject: [PATCH] feat: implement proper user and me structs --- src/api/v1/users/me.rs | 87 +++++++++++++++++++--------- src/api/v1/users/mod.rs | 29 ++-------- src/api/v1/users/uuid.rs | 30 ++-------- src/main.rs | 1 + src/structs.rs | 120 ++++++++++++++++++++++++++++++++++++++- 5 files changed, 191 insertions(+), 76 deletions(-) diff --git a/src/api/v1/users/me.rs b/src/api/v1/users/me.rs index f641678..09cb400 100644 --- a/src/api/v1/users/me.rs +++ b/src/api/v1/users/me.rs @@ -1,15 +1,7 @@ -use actix_web::{Error, HttpRequest, HttpResponse, get, web}; -use log::error; -use serde::Serialize; +use actix_web::{get, post, web, Error, HttpRequest, HttpResponse}; +use serde::Deserialize; -use crate::{Data, api::v1::auth::check_access_token, utils::get_auth_header}; - -#[derive(Serialize)] -struct Response { - uuid: String, - username: String, - display_name: String, -} +use crate::{api::v1::auth::check_access_token, structs::Me, utils::get_auth_header, Data}; #[get("/me")] pub async fn res(req: HttpRequest, data: web::Data) -> Result { @@ -29,23 +21,64 @@ pub async fn res(req: HttpRequest, data: web::Data) -> Result) = row.unwrap(); - - Ok(HttpResponse::Ok().json(Response { - uuid: uuid.to_string(), - username, - display_name: display_name.unwrap_or_default(), - })) + Ok(HttpResponse::Ok().json(me.unwrap())) +} + +#[derive(Deserialize)] +struct NewInfo { + username: Option, + display_name: Option, + password: Option, + email: Option, +} + +#[post("/me")] +pub async fn update(req: HttpRequest, new_info: web::Json, data: web::Data) -> Result { + let headers = req.headers(); + + let auth_header = get_auth_header(headers); + + if let Err(error) = auth_header { + return Ok(error); + } + + let authorized = check_access_token(auth_header.unwrap(), &data.pool).await; + + if let Err(error) = authorized { + return Ok(error); + } + + let uuid = authorized.unwrap(); + + let me_result = Me::get(&data.pool, uuid).await; + + if let Err(error) = me_result { + return Ok(error); + } + + let me = me_result; + + if let Some(username) = &new_info.username { + todo!(); + } + + if let Some(display_name) = &new_info.display_name { + todo!(); + } + + if let Some(password) = &new_info.password { + todo!(); + } + + if let Some(email) = &new_info.email { + todo!(); + } + + Ok(HttpResponse::Ok().finish()) } diff --git a/src/api/v1/users/mod.rs b/src/api/v1/users/mod.rs index 079cb77..f79bb5a 100644 --- a/src/api/v1/users/mod.rs +++ b/src/api/v1/users/mod.rs @@ -1,8 +1,6 @@ -use crate::{Data, api::v1::auth::check_access_token, utils::get_auth_header}; +use crate::{api::v1::auth::check_access_token, structs::User, utils::get_auth_header, Data}; use actix_web::{Error, HttpRequest, HttpResponse, Scope, get, web}; -use log::error; -use serde::{Deserialize, Serialize}; -use sqlx::prelude::FromRow; +use serde::Deserialize; mod me; mod uuid; @@ -13,14 +11,6 @@ struct RequestQuery { amount: Option, } -#[derive(Serialize, FromRow)] -struct Response { - uuid: String, - username: String, - display_name: Option, - email: String, -} - pub fn web() -> Scope { web::scope("/users") .service(res) @@ -52,18 +42,11 @@ pub async fn res( 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(amount) - .bind(start) - .fetch_all(&data.pool) - .await; + let accounts = User::fetch_all(&data.pool, start, amount).await; - if let Err(error) = row { - error!("{}", error); - return Ok(HttpResponse::InternalServerError().finish()); + if let Err(error) = accounts { + return Ok(error); } - let accounts: Vec = row.unwrap(); - - Ok(HttpResponse::Ok().json(accounts)) + Ok(HttpResponse::Ok().json(accounts.unwrap())) } diff --git a/src/api/v1/users/uuid.rs b/src/api/v1/users/uuid.rs index 9edaffa..046734f 100644 --- a/src/api/v1/users/uuid.rs +++ b/src/api/v1/users/uuid.rs @@ -1,16 +1,8 @@ use actix_web::{Error, HttpRequest, HttpResponse, get, web}; use log::error; -use serde::Serialize; use uuid::Uuid; -use crate::{Data, api::v1::auth::check_access_token, utils::get_auth_header}; - -#[derive(Serialize, Clone)] -struct Response { - uuid: String, - username: String, - display_name: String, -} +use crate::{api::v1::auth::check_access_token, structs::User, utils::get_auth_header, Data}; #[get("/{uuid}")] pub async fn res( @@ -42,25 +34,13 @@ pub async fn res( .body(cache_hit)); } - let row = sqlx::query_as(&format!( - "SELECT username, display_name FROM users WHERE uuid = '{}'", - uuid - )) - .fetch_one(&data.pool) - .await; + let user_result = User::fetch_one(&data.pool, uuid).await; - if let Err(error) = row { - error!("{}", error); - return Ok(HttpResponse::InternalServerError().finish()); + if let Err(error) = user_result { + return Ok(error); } - let (username, display_name): (String, Option) = row.unwrap(); - - let user = Response { - uuid: uuid.to_string(), - username, - display_name: display_name.unwrap_or_default(), - }; + let user = user_result.unwrap(); let cache_result = data .set_cache_key(uuid.to_string(), user.clone(), 1800) diff --git a/src/main.rs b/src/main.rs index bf918dc..3ede686 100644 --- a/src/main.rs +++ b/src/main.rs @@ -68,6 +68,7 @@ async fn main() -> Result<(), Error> { password varchar(512) NOT NULL, email varchar(100) NOT NULL, email_verified boolean NOT NULL DEFAULT FALSE, + avatar varchar(100) DEFAULT NULL, is_deleted boolean NOT NULL DEFAULT FALSE, deleted_at int8 DEFAULT NULL, CONSTRAINT unique_username_active UNIQUE NULLS NOT DISTINCT (username, is_deleted), diff --git a/src/structs.rs b/src/structs.rs index 92252cc..26bd89d 100644 --- a/src/structs.rs +++ b/src/structs.rs @@ -740,7 +740,7 @@ pub struct Message { } #[derive(FromRow)] -pub struct InviteBuilder { +struct InviteBuilder { id: String, user_uuid: String, guild_uuid: String, @@ -784,3 +784,121 @@ impl Invite { Ok(invite.unwrap().build()) } } + +#[derive(FromRow, Clone)] +struct UserBuilder { + uuid: String, + username: String, + display_name: Option, + avatar: Option, + email: Option, +} + +impl UserBuilder { + fn build(self) -> User { + User { + uuid: Uuid::from_str(&self.uuid).unwrap(), + username: self.username, + display_name: self.display_name, + avatar: self.avatar, + email: self.email, + } + } +} + +#[derive(Serialize, Clone)] +pub struct User { + uuid: Uuid, + username: String, + display_name: Option, + avatar: Option, + #[serde(skip_serializing_if = "Option::is_none")] + email: Option, +} + +impl User { + pub async fn fetch_one(pool: &Pool, user_uuid: Uuid) -> Result { + let user_result: Result = + sqlx::query_as(&format!("SELECT CAST(uuid AS VARCHAR), username, display_name, avatar, email FROM users WHERE uuid = '{}'", user_uuid)) + .fetch_one(pool) + .await; + + if let Err(error) = user_result { + error!("{}", error); + + return Err(HttpResponse::InternalServerError().finish()); + } + + let mut user = user_result.unwrap(); + + // Override email since it shouldn't be returned by this (FromRow impl kinda sucks since it doesnt automatically count missing columns as Nulls) + user.email = None; + + Ok(user.build()) + } + + pub async fn fetch_all(pool: &Pool, start: i32, amount: i32) -> Result, HttpResponse> { + let row = sqlx::query_as("SELECT CAST(uuid AS VARCHAR), username, display_name, avatar, email FROM users ORDER BY username LIMIT $1 OFFSET $2") + .bind(amount) + .bind(start) + .fetch_all(pool) + .await; + + if let Err(error) = row { + error!("{}", error); + return Err(HttpResponse::InternalServerError().finish()); + } + + let accounts: Vec = row.unwrap(); + + Ok(accounts.iter().map(|u| u.clone().build()).collect()) + } +} + +#[derive(FromRow)] +struct MeBuilder { + uuid: String, + username: String, + display_name: Option, + avatar: Option, + email: String, + email_verified: bool, +} + +impl MeBuilder { + fn build(self) -> Me { + Me { + uuid: Uuid::from_str(&self.uuid).unwrap(), + username: self.username, + display_name: self.display_name, + avatar: self.avatar, + email: self.email, + email_verified: self.email_verified, + } + } +} + +#[derive(Serialize)] +pub struct Me { + uuid: Uuid, + username: String, + display_name: Option, + avatar: Option, + email: String, + email_verified: bool, +} + +impl Me { + pub async fn get(pool: &Pool, user_uuid: Uuid) -> Result { + let me: Result = sqlx::query_as(&format!("SELECT CAST(uuid AS VARCHAR), username, display_name, avatar, email, email_verified FROM users WHERE uuid = '{}'", user_uuid)) + .fetch_one(pool) + .await; + + if let Err(error) = me { + error!("{}", error); + return Err(HttpResponse::InternalServerError().finish()) + } + + Ok(me.unwrap().build()) + } +}