From 2fd6d787a00a471967bf9868bf3235ca5b39fb92 Mon Sep 17 00:00:00 2001 From: N08I40K Date: Tue, 15 Apr 2025 19:39:46 +0400 Subject: [PATCH] =?UTF-8?q?=D0=AD=D0=BD=D0=B4=D0=BF=D0=BE=D0=B8=D0=BD?= =?UTF-8?q?=D1=82=20users/change-group.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/database/driver.rs | 65 +++++++++++++++++++--- src/main.rs | 1 + src/routes/users/change_group.rs | 85 +++++++++++++++++++++++++++++ src/routes/users/change_username.rs | 9 +-- src/routes/users/mod.rs | 4 +- 5 files changed, 148 insertions(+), 16 deletions(-) create mode 100644 src/routes/users/change_group.rs diff --git a/src/database/driver.rs b/src/database/driver.rs index bfc8c4a..285dd95 100644 --- a/src/database/driver.rs +++ b/src/database/driver.rs @@ -3,11 +3,11 @@ pub mod users { use crate::database::models::User; use crate::database::schema::users::dsl::users; use crate::database::schema::users::dsl::*; - use actix_web::web; - use diesel::SelectableHelper; - use diesel::{insert_into, ExpressionMethods, QueryResult}; - use diesel::{QueryDsl, RunQueryDsl}; use crate::utility::mutex::MutexScope; + use actix_web::web; + use diesel::{ExpressionMethods, QueryResult, insert_into}; + use diesel::{QueryDsl, RunQueryDsl}; + use diesel::{SaveChangesDsl, SelectableHelper}; pub fn get(state: &web::Data, _id: &String) -> QueryResult { state.database.scope(|conn| { @@ -69,7 +69,58 @@ pub mod users { } pub fn insert(state: &web::Data, user: &User) -> QueryResult { - state.database.scope(|conn| insert_into(users).values(user).execute(conn)) + state + .database + .scope(|conn| insert_into(users).values(user).execute(conn)) + } + + /// Function declaration [User::save][UserSave::save]. + pub trait UserSave { + /// Saves the user's changes to the database. + /// + /// # Arguments + /// + /// * `state`: The state of the actix-web application that stores the mutex of the [connection][diesel::PgConnection]. + /// + /// returns: `QueryResult` + /// + /// # Examples + /// + /// ``` + /// use crate::database::driver::users; + /// + /// #[derive(Deserialize)] + /// struct Params { + /// pub username: String, + /// } + /// + /// #[patch("/")] + /// async fn patch_user( + /// app_state: web::Data, + /// user: SyncExtractor, + /// web::Query(params): web::Query, + /// ) -> web::Json { + /// let mut user = user.into_inner(); + /// + /// user.username = params.username; + /// + /// match user.save(&app_state) { + /// Ok(user) => web::Json(user), + /// Err(e) => { + /// eprintln!("Failed to save user: {e}"); + /// panic!(); + /// } + /// } + /// } + /// ``` + fn save(&self, state: &web::Data) -> QueryResult; + } + + /// Implementation of [UserSave][UserSave] trait. + impl UserSave for User { + fn save(&self, state: &web::Data) -> QueryResult { + state.database.scope(|conn| self.save_changes::(conn)) + } } #[cfg(test)] @@ -95,12 +146,12 @@ pub mod users { pub mod fcm { use crate::app_state::AppState; - use crate::database::models::{User, FCM}; + use crate::database::models::{FCM, User}; + use crate::utility::mutex::MutexScope; use actix_web::web; use diesel::QueryDsl; use diesel::RunQueryDsl; use diesel::{BelongingToDsl, QueryResult, SelectableHelper}; - use crate::utility::mutex::MutexScope; pub fn from_user(state: &web::Data, user: &User) -> QueryResult { state.database.scope(|conn| { diff --git a/src/main.rs b/src/main.rs index fb625b4..c637d27 100644 --- a/src/main.rs +++ b/src/main.rs @@ -36,6 +36,7 @@ pub fn get_api_scope< let users_scope = utoipa_actix_web::scope("/users") .wrap(JWTAuthorization) + .service(routes::users::change_group) .service(routes::users::change_username) .service(routes::users::me); diff --git a/src/routes/users/change_group.rs b/src/routes/users/change_group.rs new file mode 100644 index 0000000..6ab4f25 --- /dev/null +++ b/src/routes/users/change_group.rs @@ -0,0 +1,85 @@ +use self::schema::*; +use crate::app_state::AppState; +use crate::database::driver::users::UserSave; +use crate::database::models::User; +use crate::extractors::base::SyncExtractor; +use crate::routes::schema::IntoResponseAsError; +use crate::utility::mutex::MutexScope; +use actix_web::{post, web}; + +#[utoipa::path(responses((status = OK)))] +#[post("/change-group")] +pub async fn change_group( + app_state: web::Data, + user: SyncExtractor, + data: web::Json, +) -> ServiceResponse { + let mut user = user.into_inner(); + + if user.group == data.group { + return ErrorCode::SameGroup.into_response(); + } + + if let Some(e) = app_state.schedule.scope(|schedule| match schedule { + Some(schedule) => { + if schedule.data.groups.contains_key(&data.group) { + None + } else { + Some(ErrorCode::NotFound) + } + } + None => Some(ErrorCode::NoSchedule), + }) { + return e.into_response(); + } + + user.group = data.into_inner().group; + + if let Some(e) = user.save(&app_state).err() { + eprintln!("Failed to update user: {e}"); + return ErrorCode::InternalServerError.into_response(); + } + + Ok(()).into() +} + +mod schema { + use actix_macros::{IntoResponseErrorNamed, StatusCode}; + use derive_more::Display; + use serde::{Deserialize, Serialize}; + use utoipa::ToSchema; + + pub type ServiceResponse = crate::routes::schema::Response<(), ErrorCode>; + + #[derive(Serialize, Deserialize, ToSchema)] + #[schema(as = ChangeGroup::Request)] + pub struct Request { + /// Название группы. + pub group: String, + } + + #[derive(Clone, Serialize, ToSchema, StatusCode, Display, IntoResponseErrorNamed)] + #[serde(rename_all = "SCREAMING_SNAKE_CASE")] + #[schema(as = ChangeGroup::ErrorCode)] + #[status_code = "actix_web::http::StatusCode::CONFLICT"] + pub enum ErrorCode { + /// Расписания ещё не получены. + #[display("Schedule not parsed yet.")] + #[status_code = "actix_web::http::StatusCode::SERVICE_UNAVAILABLE"] + NoSchedule, + + /// Передано то же название группы, что есть на данный момент. + #[display("Passed the same group name as it is at the moment.")] + SameGroup, + + /// Требуемая группа не существует. + #[display("The required group does not exist.")] + #[status_code = "actix_web::http::StatusCode::NOT_FOUND"] + NotFound, + + /// Ошибка на стороне сервера. + #[display("Internal server error.")] + #[status_code = "actix_web::http::StatusCode::INTERNAL_SERVER_ERROR"] + InternalServerError, + } +} diff --git a/src/routes/users/change_username.rs b/src/routes/users/change_username.rs index 1523c6f..e817f2e 100644 --- a/src/routes/users/change_username.rs +++ b/src/routes/users/change_username.rs @@ -1,12 +1,11 @@ use self::schema::*; use crate::app_state::AppState; use crate::database::driver; +use crate::database::driver::users::UserSave; use crate::database::models::User; use crate::extractors::base::SyncExtractor; use crate::routes::schema::IntoResponseAsError; -use crate::utility::mutex::MutexScope; use actix_web::{post, web}; -use diesel::SaveChangesDsl; #[utoipa::path(responses((status = OK)))] #[post("/change-username")] @@ -27,11 +26,7 @@ pub async fn change_username( user.username = data.into_inner().username; - if let Some(e) = app_state - .database - .scope(|conn| user.save_changes::(conn)) - .err() - { + if let Some(e) = user.save(&app_state).err() { eprintln!("Failed to update user: {e}"); return ErrorCode::InternalServerError.into_response(); } diff --git a/src/routes/users/mod.rs b/src/routes/users/mod.rs index 1b5d194..289e7cf 100644 --- a/src/routes/users/mod.rs +++ b/src/routes/users/mod.rs @@ -1,7 +1,7 @@ +mod change_group; mod change_username; mod me; +pub use change_group::*; pub use change_username::*; pub use me::*; - -// TODO: change-group