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.
This commit is contained in:
vaibhav
2026-02-14 03:06:31 +05:30
parent 9bf9a2296e
commit fcaf70b717
5 changed files with 153 additions and 43 deletions

View File

@@ -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();

View File

@@ -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<AppState> {
@@ -9,9 +9,9 @@ pub fn system_routers() -> Router<AppState> {
)
}
async fn get_opensubsonic_extentions() -> (StatusCode, Json<SubSonicResponses>) {
async fn get_opensubsonic_extentions() -> (StatusCode, Json<OpenSubSonicResponses>) {
(
StatusCode::OK,
Json(SubSonicResponses::open_subsonic_extentions()),
Json(OpenSubSonicResponses::open_subsonic_extentions()),
)
}

View File

@@ -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<AppState> {
let userm_routs = Router::new()
.route("/createUser.view", get())create_user))
pub fn user_management_routs() -> Router<AppState> {
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::<Option<i32>>(conn);
match result {
Ok(_) => true,
Err(_) => false,
}
}
async fn create_user(
query: Option<Query<NewUser>>,
form: Option<Form<NewUser>>,
State(db_con): State<AppState>
) -> {
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<AppState>,
form: Result<Form<NewUser>, FormRejection>,
) -> (StatusCode, Json<OpenSubSonicResponses>) {
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(&params.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()),
)
}

View File

@@ -3,5 +3,10 @@ use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize)]
pub struct OpenSubSonicExtension {
pub name: String,
pub versions: Vec<i8>,
pub versions: Vec<u8>,
}
#[derive(Serialize, Deserialize)]
pub struct OpenSubSonicExtensions {
pub openSubsonicExtensions: Vec<OpenSubSonicExtension>,
}

View File

@@ -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<T> {
status: String,
version: String,
#[serde(rename = "type")]
type_name: String,
server_version: String,
open_subsonic: bool,
#[serde(flatten)]
data: Option<T>,
}
#[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<ErrorResponse>,
},
OpenSubSonicResponse {
#[serde(rename = "subsonic-response")]
subsonic_response: BaseResponse<()>,
},
OpenSubSonicExtensions {
#[serde(rename = "subsonic-response")]
subsonic_reponse: BaseResponse,
openSubSonicExtensions: Vec<OpenSubSonicExtension>,
subsonic_response: BaseResponse<OpenSubSonicExtensions>,
// openSubSonicExtensions: Vec<OpenSubSonicExtension>,
},
}
impl SubSonicResponses {
fn base_response() -> BaseResponse {
impl OpenSubSonicResponses {
fn base_response<T>(inner_response: Option<T>) -> BaseResponse<T> {
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,
})),
}
}
}