From d72958b7494ea98ecc3a38c79bcab6581d9bd5d7 Mon Sep 17 00:00:00 2001 From: Brandon Thomas Date: Mon, 6 Sep 2021 01:43:28 -0400 Subject: [PATCH] list users endpoint --- .../list_users_query_builder.rs | 18 +-- .../endpoints/moderation/users/list_users.rs | 148 ++++++++++++++++++ src/main.rs | 9 ++ 3 files changed, 166 insertions(+), 9 deletions(-) diff --git a/src/database/query_builders/list_users_query_builder.rs b/src/database/query_builders/list_users_query_builder.rs index 5aa5ae6a09..2ab825a81a 100644 --- a/src/database/query_builders/list_users_query_builder.rs +++ b/src/database/query_builders/list_users_query_builder.rs @@ -1,3 +1,4 @@ +use chrono::{DateTime, Utc}; use crate::database::helpers::boolean_converters::i8_to_bool; use crate::util::anyhow_result::AnyhowResult; use sqlx::MySqlPool; @@ -29,16 +30,16 @@ pub struct UserForList { pub is_banned: bool, pub user_role_slug: String, - pub created_at: String, - pub updated_at: String, + pub created_at: DateTime, + pub updated_at: DateTime, } -pub struct BuildListUsers { +pub struct ListUsersQueryBuilder { sort_ascending: bool, per_page: i64, } -impl BuildListUsers { +impl ListUsersQueryBuilder { pub fn new() -> Self { Self { sort_ascending: false, @@ -109,7 +110,7 @@ impl BuildListUsers { let mut query_string = r#" SELECT id as user_id, - user_token, + token as user_token, username, display_name, display_name, @@ -117,9 +118,8 @@ SELECT is_banned, user_role_slug, created_at, - updated_at, + updated_at FROM users -LIMIT 50 "#; query_string.to_string() @@ -138,7 +138,7 @@ pub struct UserForListRaw { pub is_banned: i8, pub user_role_slug: String, - pub created_at: String, - pub updated_at: String, + pub created_at: DateTime, + pub updated_at: DateTime, } diff --git a/src/http_server/endpoints/moderation/users/list_users.rs b/src/http_server/endpoints/moderation/users/list_users.rs index e69de29bb2..31d586bf6a 100644 --- a/src/http_server/endpoints/moderation/users/list_users.rs +++ b/src/http_server/endpoints/moderation/users/list_users.rs @@ -0,0 +1,148 @@ +use actix_http::Error; +use actix_http::http::header; +use actix_web::cookie::Cookie; +use actix_web::dev::HttpResponseBuilder; +use actix_web::error::ResponseError; +use actix_web::http::StatusCode; +use actix_web::{Responder, web, HttpResponse, error, HttpRequest}; +use crate::database::queries::get_user_profile_by_username::get_user_profile_by_username; +use crate::database::query_builders::list_users_query_builder::{ListUsersQueryBuilder, UserForList}; +use crate::http_server::web_utils::response_error_helpers::to_simple_json_error; +use crate::http_server::web_utils::response_success_helpers::simple_json_success; +use crate::http_server::web_utils::serialize_as_json_error::serialize_as_json_error; +use crate::server_state::ServerState; +use derive_more::{Display, Error}; +use log::{info, warn, log}; +use regex::Regex; +use sqlx::error::DatabaseError; +use sqlx::error::Error::Database; +use sqlx::mysql::MySqlDatabaseError; +use std::fmt; +use std::sync::Arc; + +#[derive(Deserialize)] +pub struct ListUsersRequest { + // TODO: Implement the endpoint +} + +#[derive(Serialize)] +pub struct ListUsersResponse { + success: bool, + users: Vec +} + +#[derive(Serialize, Debug)] +pub struct ListUsersErrorResponse { + pub success: bool, + pub error_type: ListUsersErrorType, + pub error_message: String, +} + +#[derive(Copy, Clone, Debug, Serialize)] +pub enum ListUsersErrorType { + BadInput, + ServerError, + Unauthorized, + UserNotFound, +} + +impl ListUsersErrorResponse { + fn unauthorized() -> Self { + Self { + success: false, + error_type: ListUsersErrorType::Unauthorized, + error_message: "unauthorized".to_string() + } + } + fn server_error() -> Self { + Self { + success: false, + error_type: ListUsersErrorType::ServerError, + error_message: "server error".to_string() + } + } + fn bad_request(error_message: &str) -> Self { + Self { + success: false, + error_type: ListUsersErrorType::BadInput, + error_message: error_message.to_string() + } + } +} + +impl ResponseError for ListUsersErrorResponse { + fn status_code(&self) -> StatusCode { + match self.error_type { + ListUsersErrorType::BadInput => StatusCode::BAD_REQUEST, + ListUsersErrorType::ServerError => StatusCode::INTERNAL_SERVER_ERROR, + ListUsersErrorType::Unauthorized => StatusCode::UNAUTHORIZED, + ListUsersErrorType::UserNotFound => StatusCode::NOT_FOUND, + } + } + + fn error_response(&self) -> HttpResponse { + serialize_as_json_error(self) + } +} + +// NB: Not using DeriveMore since Clion doesn't understand it. +impl fmt::Display for ListUsersErrorResponse { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{:?}", self.error_type) + } +} + +pub async fn list_users_handler( + http_request: HttpRequest, + request: web::Query, + server_state: web::Data> +) -> Result { + + let maybe_user_session = server_state + .session_checker + .maybe_get_user_session(&http_request, &server_state.mysql_pool) + .await + .map_err(|e| { + warn!("Session checker error: {:?}", e); + ListUsersErrorResponse::server_error() + })?; + + let user_session = match maybe_user_session { + Some(session) => session, + None => { + warn!("not logged in"); + return Err(ListUsersErrorResponse::unauthorized()); + } + }; + + // TODO: Not the correct permission + if !user_session.can_ban_users { + warn!("user is not allowed to add bans: {}", user_session.user_token); + return Err(ListUsersErrorResponse::unauthorized()); + } + + let query_results = ListUsersQueryBuilder::new() + .sort_ascending(true) + .query_for_page(&server_state.mysql_pool) + .await; + + let results_page = match query_results { + Ok(results) => results, + Err(e) => { + warn!("Query error: {:?}", e); + return Err(ListUsersErrorResponse::server_error()); + } + }; + + let response = ListUsersResponse { + success: true, + users: results_page.users, + }; + + let body = serde_json::to_string(&response) + .map_err(|e| ListUsersErrorResponse::server_error())?; + + Ok(HttpResponse::Ok() + .content_type("application/json") + .body(body)) +} diff --git a/src/main.rs b/src/main.rs index fd065262de..9fca5cec6d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -105,6 +105,7 @@ use sqlx::MySqlPool; use sqlx::mysql::MySqlPoolOptions; use std::sync::Arc; use std::time::Duration; +use crate::http_server::endpoints::moderation::users::list_users::list_users_handler; // TODO TODO TODO TODO // TODO TODO TODO TODO @@ -383,6 +384,14 @@ pub async fn serve(server_state: ServerState) -> AnyhowResult<()> .route(web::head().to(|| HttpResponse::Ok())) ) ) + .service( + web::scope("/user") + .service( + web::resource("/list") + .route(web::get().to(list_users_handler)) + .route(web::head().to(|| HttpResponse::Ok())) + ) + ) .service( web::scope("/user_bans") .service(