From fcaf70b7173d7057bcc4289b6754990b31e93a86 Mon Sep 17 00:00:00 2001 From: vaibhav Date: Sat, 14 Feb 2026 03:06:31 +0530 Subject: [PATCH] Add user management routes Integrate the new user management routes into the main application router. This commit also includes refactoring of the OpenSubsonic response types to better accommodate error responses and introduces a new struct for handling multiple extensions. --- src/main.rs | 3 +- src/routes/system_router.rs | 6 +- src/routes/user_management_router.rs | 74 +++++++++++++------ src/types/system.rs | 7 +- src/types/types.rs | 106 +++++++++++++++++++++++---- 5 files changed, 153 insertions(+), 43 deletions(-) diff --git a/src/main.rs b/src/main.rs index 5aa608c..9afce55 100644 --- a/src/main.rs +++ b/src/main.rs @@ -9,6 +9,7 @@ use axum::Router; use dotenvy::dotenv; use crate::routes::system_router::system_routers; +use crate::routes::user_management_router::user_management_routs; use crate::state::AppState; #[tokio::main] @@ -19,7 +20,7 @@ async fn main() { .unwrap_or_else(|_| panic!("Error creating pool for {}", database_url)); tracing_subscriber::fmt::init(); let app = Router::new() - .nest("/rest", system_routers()) + .nest("/rest", system_routers().merge(user_management_routs())) .with_state(state); let listener = tokio::net::TcpListener::bind("0.0.0.0:3311").await.unwrap(); diff --git a/src/routes/system_router.rs b/src/routes/system_router.rs index c6c7c91..461d765 100644 --- a/src/routes/system_router.rs +++ b/src/routes/system_router.rs @@ -1,5 +1,5 @@ use crate::state::AppState; -use crate::types::types::SubSonicResponses; +use crate::types::types::OpenSubSonicResponses; use axum::{Json, Router, http::StatusCode, routing::get}; pub fn system_routers() -> Router { @@ -9,9 +9,9 @@ pub fn system_routers() -> Router { ) } -async fn get_opensubsonic_extentions() -> (StatusCode, Json) { +async fn get_opensubsonic_extentions() -> (StatusCode, Json) { ( StatusCode::OK, - Json(SubSonicResponses::open_subsonic_extentions()), + Json(OpenSubSonicResponses::open_subsonic_extentions()), ) } diff --git a/src/routes/user_management_router.rs b/src/routes/user_management_router.rs index aa1dfc5..8018576 100644 --- a/src/routes/user_management_router.rs +++ b/src/routes/user_management_router.rs @@ -1,28 +1,56 @@ -use crate::{db::models::NewUser, schema::users, state::AppState}; -use axum::{Json, Router, extract::{Form, Query, State}, http::StatusCode, response::IntoResponse, routing::{Route, get}}; -use diesel::{RunQueryDsl, insert_into}; -use serde_json::json; +use crate::{ + db::models::NewUser, + schema::users, + state::AppState, + types::types::{OpenSubSonicResponses, OpenSubsonicErrorCode}, +}; +use axum::{ + Json, Router, + extract::{Form, State, rejection::FormRejection}, + http::StatusCode, + routing::get, +}; +use diesel::{ExpressionMethods, QueryDsl, RunQueryDsl, insert_into}; -fn user_management_routs() -> Router { - let userm_routs = Router::new() - .route("/createUser.view", get())create_user)) +pub fn user_management_routs() -> Router { + let userm_routs = Router::new().route("/createUser.view", get(create_user).post(create_user)); + return userm_routs; +} + +fn check_if_user_exist(user_name: &String, db_con: &AppState) -> bool { + let conn = &mut db_con.pool.get().unwrap(); + let result = users::table + .select(users::id) + .filter(users::username.eq(user_name)) + .first::>(conn); + match result { + Ok(_) => true, + Err(_) => false, + } } async fn create_user( - query: Option>, - form: Option>, - State(db_con): State -) -> { - let params = match (query, form) { - (Some(q), _) => q.0, - (_, Some(f)) => f.0, - _ => { - return ( StatusCode::BAD_REQUEST, Json(json!({ "error": "missing parameters" }))) - } + State(db_con): State, + form: Result, FormRejection>, +) -> (StatusCode, Json) { + let params = if let Ok(Form(parsed)) = form { + parsed + } else { + return ( + StatusCode::BAD_REQUEST, + Json(OpenSubSonicResponses::open_subsonic_response()), + ); }; - insert_into(users::table).values(params).execute(db_con); - - - - )), - )} + if check_if_user_exist(¶ms.username, &db_con) { + let response = OpenSubSonicResponses::open_subsonic_response_error( + OpenSubsonicErrorCode::WrongUsernameOrPassword, + ); + return (StatusCode::CONFLICT, Json(response)); + }; + let conn = &mut db_con.pool.get().unwrap(); + let _ = insert_into(users::table).values(params).execute(conn); + ( + StatusCode::OK, + Json(OpenSubSonicResponses::open_subsonic_response()), + ) +} diff --git a/src/types/system.rs b/src/types/system.rs index cda2c7d..e949ac0 100644 --- a/src/types/system.rs +++ b/src/types/system.rs @@ -3,5 +3,10 @@ use serde::{Deserialize, Serialize}; #[derive(Serialize, Deserialize)] pub struct OpenSubSonicExtension { pub name: String, - pub versions: Vec, + pub versions: Vec, +} + +#[derive(Serialize, Deserialize)] +pub struct OpenSubSonicExtensions { + pub openSubsonicExtensions: Vec, } diff --git a/src/types/types.rs b/src/types/types.rs index 1377da5..3acfe78 100644 --- a/src/types/types.rs +++ b/src/types/types.rs @@ -1,46 +1,121 @@ use serde::{Deserialize, Serialize}; -use crate::types::system::OpenSubSonicExtension; +use crate::types::system::{OpenSubSonicExtension, OpenSubSonicExtensions}; + +#[derive(Debug, Clone, Copy)] +pub enum OpenSubsonicErrorCode { + Generic = 0, + MissingParameter = 10, + ClientMustUpgrade = 20, + ServerMustUpgrade = 30, + WrongUsernameOrPassword = 40, + TokenAuthNotSupportedForLdap = 41, + AuthMechanismNotSupported = 42, + ConflictingAuthMechanisms = 43, + InvalidApiKey = 44, + NotAuthorized = 50, + TrialExpired = 60, + NotFound = 70, +} + +impl OpenSubsonicErrorCode { + pub const fn description(self) -> &'static str { + match self { + Self::Generic => "A generic error.", + Self::MissingParameter => "Required parameter is missing.", + Self::ClientMustUpgrade => { + "Incompatible Subsonic REST protocol version. Client must upgrade." + } + Self::ServerMustUpgrade => { + "Incompatible Subsonic REST protocol version. Server must upgrade." + } + Self::WrongUsernameOrPassword => "Wrong username or password.", + Self::TokenAuthNotSupportedForLdap => { + "Token authentication not supported for LDAP users." + } + Self::AuthMechanismNotSupported => "Provided authentication mechanism not supported.", + Self::ConflictingAuthMechanisms => { + "Multiple conflicting authentication mechanisms provided." + } + Self::InvalidApiKey => "Invalid API key.", + Self::NotAuthorized => "User is not authorized for the given operation.", + Self::TrialExpired => { + "The trial period for the Subsonic server is over. Please upgrade to Subsonic Premium." + } + Self::NotFound => "The requested data was not found.", + } + } +} #[derive(Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct BaseResponse { +pub struct BaseResponse { status: String, version: String, #[serde(rename = "type")] type_name: String, server_version: String, open_subsonic: bool, + #[serde(flatten)] + data: Option, +} + +#[derive(Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct ErrorResponse { + code: u8, + message: String, } #[derive(Serialize, Deserialize)] #[serde(untagged)] -pub enum SubSonicResponses { - SubSonicResponse { +pub enum OpenSubSonicResponses { + OpenSubSonicResponseError { #[serde(rename = "subsonic-response")] - subsonic_reponse: BaseResponse, + subsonic_response: BaseResponse, + }, + OpenSubSonicResponse { + #[serde(rename = "subsonic-response")] + subsonic_response: BaseResponse<()>, }, OpenSubSonicExtensions { #[serde(rename = "subsonic-response")] - subsonic_reponse: BaseResponse, - openSubSonicExtensions: Vec, + subsonic_response: BaseResponse, + // openSubSonicExtensions: Vec, }, } -impl SubSonicResponses { - fn base_response() -> BaseResponse { +impl OpenSubSonicResponses { + fn base_response(inner_response: Option) -> BaseResponse { BaseResponse { status: "ok".to_string(), version: "1.16.1".to_string(), type_name: "SoundSonic".to_string(), - server_version: "1.".to_string(), + server_version: "1.16.1".to_string(), open_subsonic: true, + data: inner_response, } } - pub fn subsonic_response() -> Self { - Self::SubSonicResponse { - subsonic_reponse: Self::base_response(), + pub fn open_subsonic_response_error(error_code: OpenSubsonicErrorCode) -> Self { + Self::OpenSubSonicResponseError { + subsonic_response: BaseResponse { + status: "failed".to_string(), + version: "1.16.1".to_string(), + type_name: "SoundSonic".to_string(), + server_version: "1.16.1".to_string(), + open_subsonic: true, + data: Some(ErrorResponse { + code: error_code as u8, + message: error_code.description().to_string(), + }), + }, + } + } + + pub fn open_subsonic_response() -> Self { + Self::OpenSubSonicResponse { + subsonic_response: Self::base_response(None::<()>), } } @@ -72,8 +147,9 @@ impl SubSonicResponses { }); Self::OpenSubSonicExtensions { - subsonic_reponse: Self::base_response(), - openSubSonicExtensions: extension, + subsonic_response: Self::base_response(Some(OpenSubSonicExtensions { + openSubsonicExtensions: extension, + })), } } }