diff options
-rw-r--r-- | Cargo.toml | 4 | ||||
-rw-r--r-- | README.md | 4 | ||||
-rw-r--r-- | curlie_guide | 8 | ||||
-rw-r--r-- | src/api/album.rs | 51 | ||||
-rw-r--r-- | src/api/artist.rs | 53 | ||||
-rw-r--r-- | src/api/mod.rs | 6 | ||||
-rw-r--r-- | src/api/search_results.rs | 12 | ||||
-rw-r--r-- | src/api/song.rs | 51 | ||||
-rw-r--r-- | src/auth.rs | 53 | ||||
-rw-r--r-- | src/database/album.rs | 35 | ||||
-rw-r--r-- | src/database/artist.rs | 11 | ||||
-rw-r--r-- | src/database/mod.rs | 4 | ||||
-rw-r--r-- | src/database/song.rs | 18 | ||||
-rw-r--r-- | src/database/user.rs | 5 | ||||
-rw-r--r-- | src/doc.rs | 56 | ||||
-rw-r--r-- | src/extractors/auth_token.rs | 21 | ||||
-rw-r--r-- | src/main.rs | 59 |
17 files changed, 336 insertions, 115 deletions
@@ -1,6 +1,6 @@ [package] -name = "ver1" -version = "0.1.0" +name = "Balalaika" +version = "1.0.0" edition = "2021" [dependencies] @@ -49,7 +49,3 @@ Routes such as /song are now /api/song. ### Authentication routes have been added In order to create/remove users and retrieve the tokens, a new scope has been created (/auth). - -## TODO -[X] - User / Password system, use user id to generate tokens -[ ] - Add documentation (Utoipa) diff --git a/curlie_guide b/curlie_guide index 7db110d..df77402 100644 --- a/curlie_guide +++ b/curlie_guide @@ -4,11 +4,3 @@ curlie (:8000/api/[song/album/artist] || :8000/auth/[encode_token/decode_token/protected]) argument=value (ex: id=2) header:value (Authorization:token_here) - -== decode-auth == -curlie [-f POST] :8000/auth/decode-token token="token_here" - - - - -(saving just in case: Accept:application/json) diff --git a/src/api/album.rs b/src/api/album.rs index 9094495..f8731fb 100644 --- a/src/api/album.rs +++ b/src/api/album.rs @@ -5,15 +5,25 @@ use crate::extractors::auth_token::AuthenticationToken; use crate::AppState; use actix_web::{delete, get, post, put, web, HttpResponse}; use serde::Deserialize; +use utoipa::IntoParams; /* Possible arguments ( /album?arg=value ) */ -#[derive(Deserialize)] +#[derive(Deserialize, IntoParams)] struct AlbumQueryOptions { id: Option<String>, name: Option<String>, artist: Option<String>, } +#[utoipa::path( + params(AlbumQueryOptions), + context_path = "/api", + description = "Gets a list of the current albums and applies filters based on the url parameters recieved. It only accepts one parameter at a time.", + responses( + (status = 200, description = "Return a list of albums", body = Vec<Album>), + (status = 400, description = "Errors found, unfulfilled request"), + ), +)] #[get("/album")] pub async fn get_album( app_state: web::Data<AppState>, @@ -53,6 +63,19 @@ pub async fn get_album( } } +#[utoipa::path( + request_body = AlbumPost, + context_path = "/api", + description = "Creates a new album with the specified values.", + responses( + (status = 200, description = "Create new album", body = Response), + (status = 400, description = "Errors found, unfulfilled request"), + (status = 401, description = "Authentication failed"), + ), + security( + {"bearer_auth" = []} + ), +)] #[post("/album")] pub async fn post_album( app_state: web::Data<AppState>, @@ -68,6 +91,19 @@ pub async fn post_album( ) } +#[utoipa::path( + request_body = AlbumPut, + context_path = "/api", + description = "Edits the values of the specified album.", + responses( + (status = 200, description = "Edit album values", body = Response), + (status = 400, description = "Errors found or album not found"), + (status = 401, description = "Authentication failed"), + ), + security( + {"bearer_auth" = []} + ), +)] #[put("/album")] pub async fn put_album( app_state: web::Data<AppState>, @@ -83,6 +119,19 @@ pub async fn put_album( ) } +#[utoipa::path( + request_body = Delete, + context_path = "/api", + description = "Deletes the specified album.", + responses( + (status = 200, description = "Delete existing album", body = Response), + (status = 400, description = "Errors found or album not found"), + (status = 401, description = "Authentication failed"), + ), + security( + {"bearer_auth" = []} + ), +)] #[delete("/album")] pub async fn delete_album( app_state: web::Data<AppState>, diff --git a/src/api/artist.rs b/src/api/artist.rs index d36ccf4..1fdae37 100644 --- a/src/api/artist.rs +++ b/src/api/artist.rs @@ -5,14 +5,24 @@ use crate::extractors::auth_token::AuthenticationToken; use crate::AppState; use actix_web::{delete, get, post, put, web, HttpResponse}; use serde::Deserialize; +use utoipa::IntoParams; /* Possible arguments ( /artist?arg=value ) */ -#[derive(Deserialize)] -struct ArtistQueryOptions { +#[derive(Deserialize, IntoParams)] +pub struct ArtistQueryOptions { id: Option<String>, name: Option<String>, } +#[utoipa::path( + params(ArtistQueryOptions), + context_path = "/api", + description = "Gets a list of the current artists and applies filters based on the url parameters recieved. It only accepts one parameter at a time.", + responses( + (status = 200, description = "Return a list of artists", body = Vec<Artist>), + (status = 400, description = "Errors found, unfulfilled request"), + ), +)] #[get("/artist")] pub async fn get_artist( app_state: web::Data<AppState>, @@ -48,6 +58,19 @@ pub async fn get_artist( } } +#[utoipa::path( + request_body = ArtistPost, + context_path = "/api", + description = "Creates a new artist with the specified name.", + responses( + (status = 200, description = "Create new artist", body = Response), + (status = 400, description = "Errors found, unfulfilled request"), + (status = 401, description = "Authentication failed"), + ), + security( + {"bearer_auth" = []} + ), +)] #[post("/artist")] pub async fn post_artist( app_state: web::Data<AppState>, @@ -63,6 +86,19 @@ pub async fn post_artist( ) } +#[utoipa::path( + request_body = ArtistPut, + context_path = "/api", + description = "Edits the name of the specified artist.", + responses( + (status = 200, description = "Edit artist name", body = Response), + (status = 400, description = "Errors found or artist not found"), + (status = 401, description = "Authentication failed"), + ), + security( + {"bearer_auth" = []} + ), +)] #[put("/artist")] pub async fn put_artist( app_state: web::Data<AppState>, @@ -78,6 +114,19 @@ pub async fn put_artist( ) } +#[utoipa::path( + request_body = Delete, + context_path = "/api", + description = "Deletes the specified artist.", + responses( + (status = 200, description = "Delete existing artist", body = Response), + (status = 400, description = "Errors found or artist not found"), + (status = 401, description = "Authentication failed"), + ), + security( + {"bearer_auth" = []} + ), +)] #[delete("/artist")] pub async fn delete_artist( app_state: web::Data<AppState>, diff --git a/src/api/mod.rs b/src/api/mod.rs index 2a7c3a8..fc97f47 100644 --- a/src/api/mod.rs +++ b/src/api/mod.rs @@ -1,5 +1,6 @@ use actix_web::{web, HttpResponse, Scope}; use serde::{Deserialize, Serialize}; +use utoipa::ToSchema; pub mod album; pub mod artist; @@ -24,9 +25,10 @@ pub fn api_scope() -> Scope { .service(search_results::search_results) } -#[derive(Serialize, Deserialize)] +#[derive(Serialize, Deserialize, ToSchema)] pub struct Response { - message: String, + #[schema(default = "response")] + pub message: String, } pub fn get_response_from_query( diff --git a/src/api/search_results.rs b/src/api/search_results.rs index f53bce8..cd47054 100644 --- a/src/api/search_results.rs +++ b/src/api/search_results.rs @@ -4,12 +4,22 @@ use crate::database::song::Song; use crate::AppState; use actix_web::{get, web, HttpResponse}; use serde::Deserialize; +use utoipa::IntoParams; -#[derive(Deserialize)] +#[derive(Deserialize, IntoParams)] struct SearchQueryOptions { name: Option<String>, } +#[utoipa::path( + params(SearchQueryOptions), + context_path = "/api", + description = "Performs a search based on the 'name' parameter and returns a list.", + responses( + (status = 200, description = "Return a list of artists, albums and songs"), + (status = 400, description = "Errors found, unfulfilled request"), + ), +)] #[get("/search-results")] pub async fn search_results( app_state: web::Data<AppState>, diff --git a/src/api/song.rs b/src/api/song.rs index 1fec41c..06bc4b6 100644 --- a/src/api/song.rs +++ b/src/api/song.rs @@ -5,9 +5,10 @@ use crate::extractors::auth_token::AuthenticationToken; use crate::AppState; use actix_web::{delete, get, post, put, web, HttpResponse}; use serde::Deserialize; +use utoipa::IntoParams; /* Possible arguments ( /song?arg=value ) */ -#[derive(Deserialize)] +#[derive(Deserialize, IntoParams)] struct SongQueryOptions { id: Option<String>, name: Option<String>, @@ -15,6 +16,15 @@ struct SongQueryOptions { album: Option<String>, } +#[utoipa::path( + params(SongQueryOptions), + context_path = "/api", + description = "Gets a list of the current songs and applies filters based on the url parameters recieved. It only accepts one parameter at a time.", + responses( + (status = 200, description = "Return a list of songs", body = Vec<Song>), + (status = 400, description = "Errors found, unfulfilled request"), + ), +)] #[get("/song")] pub async fn get_song( app_state: web::Data<AppState>, @@ -58,6 +68,19 @@ pub async fn get_song( } } +#[utoipa::path( + request_body = SongPost, + context_path = "/api", + description = "Creates a new song with the specified values.", + responses( + (status = 200, description = "Create new song", body = Response), + (status = 400, description = "Errors found, unfulfilled request"), + (status = 401, description = "Authentication failed"), + ), + security( + {"bearer_auth" = []} + ), +)] #[post("/song")] pub async fn post_song( app_state: web::Data<AppState>, @@ -73,6 +96,19 @@ pub async fn post_song( ) } +#[utoipa::path( + request_body = SongPut, + context_path = "/api", + description = "Edits the values of the specified song.", + responses( + (status = 200, description = "Edit song values", body = Response), + (status = 400, description = "Errors found or song not found"), + (status = 401, description = "Authentication failed"), + ), + security( + {"bearer_auth" = []} + ), +)] #[put("/song")] pub async fn put_song( app_state: web::Data<AppState>, @@ -88,6 +124,19 @@ pub async fn put_song( ) } +#[utoipa::path( + request_body = Delete, + context_path = "/api", + description = "Deletes the specified song.", + responses( + (status = 200, description = "Delete existing song", body = Response), + (status = 400, description = "Errors found or song not found"), + (status = 401, description = "Authentication failed"), + ), + security( + {"bearer_auth" = []} + ), +)] #[delete("/song")] pub async fn delete_song( app_state: web::Data<AppState>, diff --git a/src/auth.rs b/src/auth.rs index 9c6f978..857ef6b 100644 --- a/src/auth.rs +++ b/src/auth.rs @@ -4,6 +4,8 @@ use actix_web::{delete, post, web, HttpResponse, Scope}; use chrono::{Duration, Utc}; use jsonwebtoken::{encode, EncodingKey, Header}; use serde::{Deserialize, Serialize}; +pub use crate::api::Response; +use utoipa::ToSchema; /* Set up scope */ pub fn auth_scope() -> Scope { @@ -19,28 +21,23 @@ pub struct Claims { pub exp: usize, } -#[derive(Serialize, Deserialize)] -struct Response { - message: String, -} - -#[derive(Serialize, Deserialize)] +#[derive(Serialize, Deserialize, ToSchema)] struct EncodeResponse { + #[schema(example = "response")] message: String, + #[schema(example = "4f4bf0b9ef653818a56df74cffb024bd")] token: String, } -#[derive(Serialize, Deserialize)] -struct DecodeResponse { - message: String, - id: usize, -} - -#[derive(Serialize, Deserialize)] -struct DecodeBody { - token: String, -} - +#[utoipa::path( + request_body = UserForm, + context_path = "/auth", + description = "Creates a new user with the specified values.", + responses( + (status = 200, description = "Create new user", body = Response), + (status = 400, description = "Errors found, unfulfilled request"), + ), +)] #[post("/register")] pub async fn register( app_state: web::Data<AppState>, @@ -57,6 +54,16 @@ pub async fn register( } } +#[utoipa::path( + request_body = UserForm, + context_path = "/auth", + description = "Attempts to log in user. If successful, it returns an encoded token that grants access to protected routes in the api.", + responses( + (status = 200, description = "Returns encoded token", body = EncodeResponse), + (status = 400, description = "Errors found, unfulfilled request"), + (status = 401, description = "Unauthorized"), + ), +)] #[post("/login")] pub async fn login( app_state: web::Data<AppState>, @@ -67,7 +74,7 @@ pub async fn login( let result = match query { Ok(res) => res, Err(e) => { - return HttpResponse::BadRequest().json(Response { + return HttpResponse::Unauthorized().json(Response { message: format!("There was an issue in the request: {}", e).to_owned(), }) } @@ -113,6 +120,16 @@ async fn encode_token(id: usize, secret: &String) -> Result<String, HttpResponse }; } +#[utoipa::path( + request_body = UserForm, + context_path = "/auth", + description = "Attempts to delete user. Both username and password are required.", + responses( + (status = 200, description = "Delete user", body = Response), + (status = 400, description = "Errors found, unfulfilled request"), + (status = 401, description = "Unauthorized"), + ), +)] #[delete("/user")] pub async fn delete_user( app_state: web::Data<AppState>, diff --git a/src/database/album.rs b/src/database/album.rs index d7ffae0..6b2b46d 100644 --- a/src/database/album.rs +++ b/src/database/album.rs @@ -1,28 +1,34 @@ use crate::database::DatabaseWrapper; use serde::{Deserialize, Serialize}; use sqlx::mysql::MySqlQueryResult; +use utoipa::ToSchema; -#[derive(Serialize)] +#[derive(Serialize, ToSchema)] pub struct Album { + #[schema(example = "album name")] name: Option<String>, + #[schema(example = "1")] id: Option<i32>, - cover: Option<String>, + #[schema(example = "just ignore this one")] artist_name: Option<String>, + #[schema(example = "1")] artist_id: Option<i32>, } -#[derive(Serialize, Deserialize)] +#[derive(Serialize, Deserialize, ToSchema)] pub struct AlbumPost { + #[schema(example = "album name")] name: Option<String>, - cover: Option<String>, + #[schema(example = "just ignore this one")] artist_id: Option<String>, } -#[derive(Serialize, Deserialize)] +#[derive(Serialize, Deserialize, ToSchema)] pub struct AlbumPut { id: Option<String>, + #[schema(example = "album name")] name: Option<String>, - cover: Option<String>, + #[schema(example = "just ignore this one")] artist_id: Option<String>, } @@ -30,7 +36,7 @@ impl DatabaseWrapper { pub async fn select_albums(&self) -> Result<Vec<Album>, sqlx::Error> { sqlx::query_as!( Album, - "SELECT album.name, album.id, album.cover, + "SELECT album.name, album.id, artist.name as artist_name, artist.id as artist_id FROM album INNER JOIN artist ON album.artist_id = artist.id @@ -43,7 +49,7 @@ impl DatabaseWrapper { pub async fn select_album_by_id(&self, id: &str) -> Result<Option<Album>, sqlx::Error> { sqlx::query_as!( Album, - "SELECT album.name, album.id, album.cover, + "SELECT album.name, album.id, artist.name as artist_name, artist.id as artist_id FROM album INNER JOIN artist ON album.artist_id = artist.id @@ -58,7 +64,7 @@ impl DatabaseWrapper { let name: String = format!("{}{}{}", "%", name_raw, "%"); sqlx::query_as!( Album, - "SELECT album.name, album.id, album.cover, + "SELECT album.name, album.id, artist.name as artist_name, artist.id as artist_id FROM album INNER JOIN artist ON album.artist_id = artist.id @@ -76,7 +82,7 @@ impl DatabaseWrapper { ) -> Result<Vec<Album>, sqlx::Error> { sqlx::query_as!( Album, - "SELECT album.name, album.id, album.cover, + "SELECT album.name, album.id, artist.name as artist_name, artist.id as artist_id FROM album INNER JOIN artist ON album.artist_id = artist.id @@ -103,10 +109,9 @@ impl DatabaseWrapper { } sqlx::query!( - "INSERT INTO album (name, cover, artist_id) - VALUE (?, ?, ?)", + "INSERT INTO album (name, artist_id) + VALUE (?, ?)", data.name, - data.cover.unwrap_or(String::default()), data.artist_id, ) .execute(&self.db_pool) @@ -125,11 +130,9 @@ impl DatabaseWrapper { Err(_) => return Err(sqlx::Error::RowNotFound), }; sqlx::query!( - "UPDATE album SET name=?, cover=? WHERE id=?", + "UPDATE album SET name=? WHERE id=?", data.name .unwrap_or(og_album.name.unwrap_or(String::default())), - data.cover - .unwrap_or(og_album.cover.unwrap_or(String::default())), data.id, ) .execute(&self.db_pool) diff --git a/src/database/artist.rs b/src/database/artist.rs index 1d4e1cd..0bdc7c1 100644 --- a/src/database/artist.rs +++ b/src/database/artist.rs @@ -5,20 +5,23 @@ use utoipa::ToSchema; #[derive(Serialize, ToSchema)] pub struct Artist { - #[schema(example = "Attempt", required = true)] + #[schema(example = "artist name")] name: Option<String>, - #[schema(example = 3, required = true)] + #[schema(example = "1")] id: Option<i32>, } -#[derive(Serialize, Deserialize)] +#[derive(Serialize, Deserialize, ToSchema)] pub struct ArtistPost { + #[schema(example = "artist name", required = true)] name: Option<String>, } -#[derive(Serialize, Deserialize)] +#[derive(Serialize, Deserialize, ToSchema)] pub struct ArtistPut { + #[schema(example = "1", required = true)] id: Option<String>, + #[schema(example = "new name", required = true)] name: Option<String>, } diff --git a/src/database/mod.rs b/src/database/mod.rs index 884a597..045cf23 100644 --- a/src/database/mod.rs +++ b/src/database/mod.rs @@ -6,10 +6,12 @@ pub mod user; use serde::{Deserialize, Serialize}; use sqlx::mysql::{MySqlPool, MySqlPoolOptions}; +use utoipa::ToSchema; use std::env; -#[derive(Serialize, Deserialize)] +#[derive(Serialize, Deserialize, ToSchema)] pub struct Delete { + #[schema(example = "1", required = true)] pub id: Option<String>, } diff --git a/src/database/song.rs b/src/database/song.rs index 95b05ba..a4c860c 100644 --- a/src/database/song.rs +++ b/src/database/song.rs @@ -5,27 +5,41 @@ use utoipa::ToSchema; #[derive(Serialize, ToSchema)] pub struct Song { + #[schema(example = "song name")] name: Option<String>, + #[schema(example = "1")] id: Option<i32>, + #[schema(example = "song lyrics...")] lyrics: Option<String>, + #[schema(example = "album name")] album_name: Option<String>, + #[schema(example = "1")] album_id: Option<i32>, + #[schema(example = "artist name")] artist_name: Option<String>, + #[schema(example = "1")] artist_id: Option<i32>, } -#[derive(Serialize, Deserialize)] +#[derive(Serialize, Deserialize, ToSchema)] pub struct SongPost { + #[schema(example = "song name")] name: Option<String>, + #[schema(example = "song lyrics...")] lyrics: Option<String>, + #[schema(example = "1")] album_id: Option<String>, } -#[derive(Serialize, Deserialize)] +#[derive(Serialize, Deserialize, ToSchema)] pub struct SongPut { + #[schema(example = "1")] id: Option<String>, + #[schema(example = "song name")] name: Option<String>, + #[schema(example = "song lyrics...")] lyrics: Option<String>, + #[schema(example = "1")] album_id: Option<String>, } diff --git a/src/database/user.rs b/src/database/user.rs index ff7b1ab..d3202ce 100644 --- a/src/database/user.rs +++ b/src/database/user.rs @@ -1,6 +1,7 @@ use crate::database::DatabaseWrapper; use serde::{Deserialize, Serialize}; use sqlx::mysql::MySqlQueryResult; +use utoipa::ToSchema; #[derive(Deserialize, Serialize)] pub struct User { @@ -9,9 +10,11 @@ pub struct User { password: Option<String>, } -#[derive(Deserialize, Serialize)] +#[derive(Deserialize, Serialize, ToSchema)] pub struct UserForm { + #[schema(example = "username")] name: Option<String>, + #[schema(example = "password")] password: Option<String>, } diff --git a/src/doc.rs b/src/doc.rs new file mode 100644 index 0000000..2d5e168 --- /dev/null +++ b/src/doc.rs @@ -0,0 +1,56 @@ +use utoipa::{ + openapi::security::{HttpAuthScheme, HttpBuilder, SecurityScheme}, + Modify, OpenApi, +}; +use crate::auth; + +use crate::api; + +pub fn get_openapi() -> utoipa::openapi::OpenApi { + /* utoipa setup */ + #[derive(OpenApi)] + #[openapi( + paths( + api::album::post_album, + api::album::put_album, + api::album::get_album, + api::album::delete_album, + api::artist::post_artist, + api::artist::put_artist, + api::artist::get_artist, + api::artist::delete_artist, + api::song::post_song, + api::song::put_song, + api::song::get_song, + api::song::delete_song, + api::search_results::search_results, + auth::register, + auth::login, + auth::delete_user, + ), + components( + schemas( + ) + ), + modifiers(&SecurityAddon) + )] + struct ApiDoc; + + struct SecurityAddon; + impl Modify for SecurityAddon { + fn modify(&self, openapi: &mut utoipa::openapi::OpenApi) { + let components = openapi.components.as_mut().unwrap(); + components.add_security_scheme( + "bearer_auth", + SecurityScheme::Http( + HttpBuilder::new() + .scheme(HttpAuthScheme::Bearer) + .bearer_format("JWT") + .build(), + ), + ); + } + } + + return ApiDoc::openapi(); +} diff --git a/src/extractors/auth_token.rs b/src/extractors/auth_token.rs index c3f781c..9728146 100644 --- a/src/extractors/auth_token.rs +++ b/src/extractors/auth_token.rs @@ -26,26 +26,37 @@ impl FromRequest for AuthenticationToken { None => return ready(Err(ErrorUnauthorized("No authorization token given"))), }; - let auth_token: String = auth_header.to_str().unwrap_or("").to_string(); // check errors later - // stop empty and weird (ascii, chinese...) auth_token strings: - if auth_token.is_empty() { + /* Get value as &str */ + let processed_header: &str = auth_header.to_str().unwrap_or(""); + if processed_header.is_empty() { return ready(Err(ErrorUnauthorized("Invalid auth token!"))); } + + /* Accept both bearer tokens and raw tokens */ + let splitted_header: Vec<&str> = processed_header.split_whitespace().collect(); + let auth_token: String = match splitted_header[0] { + "Bearer" => splitted_header[1].to_string(), + _ => processed_header.to_string(), + }; + + /* Get application secret */ let secret: String = req .app_data::<web::Data<AppState>>() .unwrap() .secret .to_string(); - // decode token with secret + /* Decode token with secret */ let decode: Result<TokenData<Claims>, JwtError> = decode::<Claims>( &auth_token, &DecodingKey::from_secret(secret.as_str().as_ref()), &Validation::new(Algorithm::HS256), ); + // for testing purposes println!("{}", auth_token); - // return authenticationtoken + + /* Return authenticationtoken */ match decode { Ok(token) => ready(Ok(AuthenticationToken { id: token.claims.id, diff --git a/src/main.rs b/src/main.rs index 69eed5d..fab6dd5 100644 --- a/src/main.rs +++ b/src/main.rs @@ -2,19 +2,15 @@ mod api; mod auth; mod database; mod extractors; +mod doc; -use actix_web::{web, App, HttpServer}; +use actix_web::web::Redirect; +use actix_web::{web, App, HttpServer, Responder}; use dotenv::dotenv; use std::env; use std::sync::Arc; -// use utoipa::{ -// openapi::security::{HttpAuthScheme, HttpBuilder, SecurityScheme}, -// Modify, OpenApi, -// }; -// -// use utoipa_swagger_ui::SwaggerUi; -// use database::artist::Artist; +use utoipa_swagger_ui::SwaggerUi; #[derive(Clone)] struct AppState { @@ -47,56 +43,25 @@ async fn main() -> std::io::Result<()> { secret: jwt_secret, }; - // /* utoipa setup */ - // #[derive(OpenApi)] - // #[openapi( - // paths( - // ), - // components( - // schemas( - // Artist - // ) - // ), - // modifiers(&SecurityAddon) - // )] - // struct ApiDoc; - - // struct SecurityAddon; - // impl Modify for SecurityAddon { - // fn modify(&self, openapi : &mut utoipa::openapi::OpenApi) { - // let components = openapi.components.as_mut().unwrap(); - // components.add_security_scheme( - // "bearer_auth", - // SecurityScheme::Http( - // HttpBuilder::new() - // .scheme(HttpAuthScheme::Bearer) - // .bearer_format("JWT") - // .build() - // ), - // ); - // } - // } - - // let openapi = ApiDoc::openapi(); + /* OpenApi */ + let openapi = doc::get_openapi(); /* Server setup */ HttpServer::new(move || { App::new() .app_data(web::Data::new(app_state.clone())) - .route("/", web::get().to(root)) - // .service(SwaggerUi::new("/docs/{_:.*}").url( - // "/docs/openapi.json", - // openapi.clone(), - // )) .service(api::api_scope()) .service(auth::auth_scope()) + .service(SwaggerUi::new("/doc/{_:.*}").url("/docs/openapi.json", openapi.clone())) + .route("/", web::get().to(redirect_to_docs)) + .route("/doc", web::get().to(redirect_to_docs)) }) .bind(("127.0.0.1", 8000))? .run() .await } -/* Main page*/ -async fn root() -> String { - String::from("Server is up and running") +/* Redirection page */ +async fn redirect_to_docs() -> impl Responder { + Redirect::to("/doc/") } |