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:
@@ -9,6 +9,7 @@ use axum::Router;
|
|||||||
use dotenvy::dotenv;
|
use dotenvy::dotenv;
|
||||||
|
|
||||||
use crate::routes::system_router::system_routers;
|
use crate::routes::system_router::system_routers;
|
||||||
|
use crate::routes::user_management_router::user_management_routs;
|
||||||
use crate::state::AppState;
|
use crate::state::AppState;
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
@@ -19,7 +20,7 @@ async fn main() {
|
|||||||
.unwrap_or_else(|_| panic!("Error creating pool for {}", database_url));
|
.unwrap_or_else(|_| panic!("Error creating pool for {}", database_url));
|
||||||
tracing_subscriber::fmt::init();
|
tracing_subscriber::fmt::init();
|
||||||
let app = Router::new()
|
let app = Router::new()
|
||||||
.nest("/rest", system_routers())
|
.nest("/rest", system_routers().merge(user_management_routs()))
|
||||||
.with_state(state);
|
.with_state(state);
|
||||||
|
|
||||||
let listener = tokio::net::TcpListener::bind("0.0.0.0:3311").await.unwrap();
|
let listener = tokio::net::TcpListener::bind("0.0.0.0:3311").await.unwrap();
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
use crate::state::AppState;
|
use crate::state::AppState;
|
||||||
use crate::types::types::SubSonicResponses;
|
use crate::types::types::OpenSubSonicResponses;
|
||||||
use axum::{Json, Router, http::StatusCode, routing::get};
|
use axum::{Json, Router, http::StatusCode, routing::get};
|
||||||
|
|
||||||
pub fn system_routers() -> Router<AppState> {
|
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,
|
StatusCode::OK,
|
||||||
Json(SubSonicResponses::open_subsonic_extentions()),
|
Json(OpenSubSonicResponses::open_subsonic_extentions()),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,28 +1,56 @@
|
|||||||
use crate::{db::models::NewUser, schema::users, state::AppState};
|
use crate::{
|
||||||
use axum::{Json, Router, extract::{Form, Query, State}, http::StatusCode, response::IntoResponse, routing::{Route, get}};
|
db::models::NewUser,
|
||||||
use diesel::{RunQueryDsl, insert_into};
|
schema::users,
|
||||||
use serde_json::json;
|
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> {
|
pub fn user_management_routs() -> Router<AppState> {
|
||||||
let userm_routs = Router::new()
|
let userm_routs = Router::new().route("/createUser.view", get(create_user).post(create_user));
|
||||||
.route("/createUser.view", get())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(
|
async fn create_user(
|
||||||
query: Option<Query<NewUser>>,
|
State(db_con): State<AppState>,
|
||||||
form: Option<Form<NewUser>>,
|
form: Result<Form<NewUser>, FormRejection>,
|
||||||
State(db_con): State<AppState>
|
) -> (StatusCode, Json<OpenSubSonicResponses>) {
|
||||||
) -> {
|
let params = if let Ok(Form(parsed)) = form {
|
||||||
let params = match (query, form) {
|
parsed
|
||||||
(Some(q), _) => q.0,
|
} else {
|
||||||
(_, Some(f)) => f.0,
|
return (
|
||||||
_ => {
|
StatusCode::BAD_REQUEST,
|
||||||
return ( StatusCode::BAD_REQUEST, Json(json!({ "error": "missing parameters" })))
|
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()),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|||||||
@@ -3,5 +3,10 @@ use serde::{Deserialize, Serialize};
|
|||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Serialize, Deserialize)]
|
||||||
pub struct OpenSubSonicExtension {
|
pub struct OpenSubSonicExtension {
|
||||||
pub name: String,
|
pub name: String,
|
||||||
pub versions: Vec<i8>,
|
pub versions: Vec<u8>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize)]
|
||||||
|
pub struct OpenSubSonicExtensions {
|
||||||
|
pub openSubsonicExtensions: Vec<OpenSubSonicExtension>,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,46 +1,121 @@
|
|||||||
use serde::{Deserialize, Serialize};
|
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)]
|
#[derive(Serialize, Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct BaseResponse {
|
pub struct BaseResponse<T> {
|
||||||
status: String,
|
status: String,
|
||||||
version: String,
|
version: String,
|
||||||
#[serde(rename = "type")]
|
#[serde(rename = "type")]
|
||||||
type_name: String,
|
type_name: String,
|
||||||
server_version: String,
|
server_version: String,
|
||||||
open_subsonic: bool,
|
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)]
|
#[derive(Serialize, Deserialize)]
|
||||||
#[serde(untagged)]
|
#[serde(untagged)]
|
||||||
pub enum SubSonicResponses {
|
pub enum OpenSubSonicResponses {
|
||||||
SubSonicResponse {
|
OpenSubSonicResponseError {
|
||||||
#[serde(rename = "subsonic-response")]
|
#[serde(rename = "subsonic-response")]
|
||||||
subsonic_reponse: BaseResponse,
|
subsonic_response: BaseResponse<ErrorResponse>,
|
||||||
|
},
|
||||||
|
OpenSubSonicResponse {
|
||||||
|
#[serde(rename = "subsonic-response")]
|
||||||
|
subsonic_response: BaseResponse<()>,
|
||||||
},
|
},
|
||||||
OpenSubSonicExtensions {
|
OpenSubSonicExtensions {
|
||||||
#[serde(rename = "subsonic-response")]
|
#[serde(rename = "subsonic-response")]
|
||||||
subsonic_reponse: BaseResponse,
|
subsonic_response: BaseResponse<OpenSubSonicExtensions>,
|
||||||
openSubSonicExtensions: Vec<OpenSubSonicExtension>,
|
// openSubSonicExtensions: Vec<OpenSubSonicExtension>,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SubSonicResponses {
|
impl OpenSubSonicResponses {
|
||||||
fn base_response() -> BaseResponse {
|
fn base_response<T>(inner_response: Option<T>) -> BaseResponse<T> {
|
||||||
BaseResponse {
|
BaseResponse {
|
||||||
status: "ok".to_string(),
|
status: "ok".to_string(),
|
||||||
version: "1.16.1".to_string(),
|
version: "1.16.1".to_string(),
|
||||||
type_name: "SoundSonic".to_string(),
|
type_name: "SoundSonic".to_string(),
|
||||||
server_version: "1.".to_string(),
|
server_version: "1.16.1".to_string(),
|
||||||
open_subsonic: true,
|
open_subsonic: true,
|
||||||
|
data: inner_response,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn subsonic_response() -> Self {
|
pub fn open_subsonic_response_error(error_code: OpenSubsonicErrorCode) -> Self {
|
||||||
Self::SubSonicResponse {
|
Self::OpenSubSonicResponseError {
|
||||||
subsonic_reponse: Self::base_response(),
|
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 {
|
Self::OpenSubSonicExtensions {
|
||||||
subsonic_reponse: Self::base_response(),
|
subsonic_response: Self::base_response(Some(OpenSubSonicExtensions {
|
||||||
openSubSonicExtensions: extension,
|
openSubsonicExtensions: extension,
|
||||||
|
})),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user