summaryrefslogtreecommitdiff
path: root/src/api
diff options
context:
space:
mode:
Diffstat (limited to 'src/api')
-rw-r--r--src/api/album.rs121
-rw-r--r--src/api/artist.rs122
-rw-r--r--src/api/mod.rs46
-rw-r--r--src/api/search_results.rs42
-rw-r--r--src/api/song.rs116
5 files changed, 343 insertions, 104 deletions
diff --git a/src/api/album.rs b/src/api/album.rs
index d9d0d52..f8731fb 100644
--- a/src/api/album.rs
+++ b/src/api/album.rs
@@ -1,33 +1,51 @@
-use crate::database::{Album, AlbumPost, AlbumPut, Delete};
+use crate::api::{get_response_from_query, Response};
+use crate::database::album::{Album, AlbumPost, AlbumPut};
+use crate::database::Delete;
+use crate::extractors::auth_token::AuthenticationToken;
use crate::AppState;
use actix_web::{delete, get, post, put, web, HttpResponse};
use serde::Deserialize;
+use utoipa::IntoParams;
-#[derive(Deserialize)]
+/* Possible arguments ( /album?arg=value ) */
+#[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>,
get_args: web::Query<AlbumQueryOptions>,
) -> HttpResponse {
- let default = String::from("");
+ /* Avoid lifespan issues */
+ let default = String::default();
+ /* Handle individual result for search-by-id */
if get_args.id.is_some() {
let id: &str = get_args.id.as_ref().unwrap_or(&default);
- let search_attempt: sqlx::Result<Option<Album>> =
+ let query_result: sqlx::Result<Option<Album>> =
app_state.database.select_album_by_id(id).await;
- return match search_attempt {
+ return match query_result {
Ok(song_list) => HttpResponse::Ok().json(song_list),
Err(e) => HttpResponse::Ok().body(format!("{}", e)),
};
}
- let search_attempt: sqlx::Result<Vec<Album>, sqlx::Error> = match true {
+
+ /* Handle N results */
+ let query_result: sqlx::Result<Vec<Album>, sqlx::Error> = match true {
_ if get_args.name.is_some() => {
let name: &str = &get_args.name.as_ref().unwrap_or(&default);
app_state.database.select_albums_by_name(name).await
@@ -39,44 +57,89 @@ pub async fn get_album(
_ => app_state.database.select_albums().await,
};
- match search_attempt {
+ match query_result {
Ok(album_list) => HttpResponse::Ok().json(album_list),
Err(e) => HttpResponse::Ok().body(format!("{}", e)),
}
}
+#[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>,
- post_data: web::Json<AlbumPost>,
+ request_data: web::Json<AlbumPost>,
+ _auth_token: AuthenticationToken,
) -> HttpResponse {
- match app_state
- .database
- .create_album(post_data.into_inner())
- .await
- {
- Ok(_) => HttpResponse::Ok().body("Post succeeded\n"),
- Err(e) => HttpResponse::Ok().body(format!("{}", e)),
- }
+ get_response_from_query(
+ app_state
+ .database
+ .create_album(request_data.into_inner())
+ .await,
+ "POST".to_string(),
+ )
}
+#[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>,
- post_data: web::Json<AlbumPut>,
+ request_data: web::Json<AlbumPut>,
+ _auth_token: AuthenticationToken,
) -> HttpResponse {
- match app_state.database.edit_album(post_data.into_inner()).await {
- Ok(_) => HttpResponse::Ok().body("Put succeeded\n"),
- Err(e) => HttpResponse::Ok().body(format!("{}", e)),
- }
+ get_response_from_query(
+ app_state
+ .database
+ .edit_album(request_data.into_inner())
+ .await,
+ "PUT".to_string(),
+ )
}
+#[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>,
- post_data: web::Json<Delete>,
+ request_data: web::Json<Delete>,
+ _auth_token: AuthenticationToken,
) -> HttpResponse {
- let id: i32 = post_data
+ /* Check if ID is valid (return -1 if invalid) */
+ let id: i32 = request_data
.into_inner()
.id
.unwrap_or(String::default())
@@ -84,11 +147,13 @@ pub async fn delete_album(
.unwrap_or(-1);
if id == -1 {
- return HttpResponse::Ok().body("Invalid id value, code not executed\n");
+ return HttpResponse::BadRequest().json(Response {
+ message: "Invalid id value, code not executed\n".to_owned(),
+ });
}
- match app_state.database.delete_album(id).await {
- Ok(_) => HttpResponse::Ok().body("Deletion succeeded\n".to_owned()),
- Err(e) => HttpResponse::Ok().body(format!("There was an issue in the request:\n{}", e)),
- }
+ get_response_from_query(
+ app_state.database.delete_album(id).await,
+ "POST".to_string(),
+ )
}
diff --git a/src/api/artist.rs b/src/api/artist.rs
index 81ae773..1fdae37 100644
--- a/src/api/artist.rs
+++ b/src/api/artist.rs
@@ -1,33 +1,50 @@
-use crate::database::{Artist, ArtistPost, ArtistPut, Delete};
+use crate::api::{get_response_from_query, Response};
+use crate::database::artist::{Artist, ArtistPost, ArtistPut};
+use crate::database::Delete;
+use crate::extractors::auth_token::AuthenticationToken;
use crate::AppState;
use actix_web::{delete, get, post, put, web, HttpResponse};
use serde::Deserialize;
+use utoipa::IntoParams;
-#[derive(Deserialize)]
-struct ArtistQueryOptions {
+/* Possible arguments ( /artist?arg=value ) */
+#[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>,
get_args: web::Query<ArtistQueryOptions>,
) -> HttpResponse {
- let default = String::from("");
+ /* Avoid lifespan issues */
+ let default = String::default();
+ /* Handle individual result for search-by-id */
if get_args.id.is_some() {
let id: &str = get_args.id.as_ref().unwrap_or(&default);
- let search_attempt: sqlx::Result<Option<Artist>> =
+ let query_result: sqlx::Result<Option<Artist>> =
app_state.database.select_artist_by_id(id).await;
- return match search_attempt {
+ return match query_result {
Ok(song_list) => HttpResponse::Ok().json(song_list),
Err(e) => HttpResponse::Ok().body(format!("{}", e)),
};
}
- let search_attempt: sqlx::Result<Vec<Artist>, sqlx::Error> = match true {
+ /* Handle N results */
+ let query_result: sqlx::Result<Vec<Artist>, sqlx::Error> = match true {
_ if get_args.name.is_some() => {
let name: &str = &get_args.name.as_ref().unwrap_or(&default);
app_state.database.select_artists_by_name(name).await
@@ -35,44 +52,89 @@ pub async fn get_artist(
_ => app_state.database.select_artists().await,
};
- match search_attempt {
+ match query_result {
Ok(artist_list) => HttpResponse::Ok().json(artist_list),
Err(e) => HttpResponse::Ok().body(format!("{}", e)),
}
}
+#[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>,
- post_data: web::Json<ArtistPost>,
+ request_data: web::Json<ArtistPost>,
+ _auth_token: AuthenticationToken,
) -> HttpResponse {
- match app_state
- .database
- .create_artist(post_data.into_inner())
- .await
- {
- Ok(_) => HttpResponse::Ok().body("Post succeeded\n"),
- Err(e) => HttpResponse::Ok().body(format!("{}", e)),
- }
+ get_response_from_query(
+ app_state
+ .database
+ .create_artist(request_data.into_inner())
+ .await,
+ "POST".to_string(),
+ )
}
+#[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>,
- post_data: web::Json<ArtistPut>,
+ request_data: web::Json<ArtistPut>,
+ _auth_token: AuthenticationToken,
) -> HttpResponse {
- match app_state.database.edit_artist(post_data.into_inner()).await {
- Ok(_) => HttpResponse::Ok().body("Put succeeded\n"),
- Err(e) => HttpResponse::Ok().body(format!("{}", e)),
- }
+ get_response_from_query(
+ app_state
+ .database
+ .edit_artist(request_data.into_inner())
+ .await,
+ "PUT".to_string(),
+ )
}
+#[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>,
- post_data: web::Json<Delete>,
+ request_data: web::Json<Delete>,
+ _auth_token: AuthenticationToken,
) -> HttpResponse {
- let id: i32 = post_data
+ /* Check if ID is valid (return -1 if invalid) */
+ let id: i32 = request_data
.into_inner()
.id
.unwrap_or(String::default())
@@ -80,11 +142,13 @@ pub async fn delete_artist(
.unwrap_or(-1);
if id == -1 {
- return HttpResponse::Ok().body("Invalid id value, code not executed\n");
+ return HttpResponse::BadRequest().json(Response {
+ message: "Invalid id value, code not executed\n".to_owned(),
+ });
}
- match app_state.database.delete_artist(id).await {
- Ok(_) => HttpResponse::Ok().body("Deletion succeeded\n\n"),
- Err(e) => HttpResponse::Ok().body(format!("There was an issue in the request:\n{}", e)),
- }
+ get_response_from_query(
+ app_state.database.delete_artist(id).await,
+ "DELETE".to_string(),
+ )
}
diff --git a/src/api/mod.rs b/src/api/mod.rs
new file mode 100644
index 0000000..fc97f47
--- /dev/null
+++ b/src/api/mod.rs
@@ -0,0 +1,46 @@
+use actix_web::{web, HttpResponse, Scope};
+use serde::{Deserialize, Serialize};
+use utoipa::ToSchema;
+
+pub mod album;
+pub mod artist;
+pub mod search_results;
+pub mod song;
+
+/* Set up scope */
+pub fn api_scope() -> Scope {
+ web::scope("/api")
+ .service(song::get_song)
+ .service(song::post_song)
+ .service(song::put_song)
+ .service(song::delete_song)
+ .service(album::get_album)
+ .service(album::post_album)
+ .service(album::put_album)
+ .service(album::delete_album)
+ .service(artist::get_artist)
+ .service(artist::post_artist)
+ .service(artist::put_artist)
+ .service(artist::delete_artist)
+ .service(search_results::search_results)
+}
+
+#[derive(Serialize, Deserialize, ToSchema)]
+pub struct Response {
+ #[schema(default = "response")]
+ pub message: String,
+}
+
+pub fn get_response_from_query(
+ query: Result<sqlx::mysql::MySqlQueryResult, sqlx::Error>,
+ method: String,
+) -> HttpResponse {
+ match query {
+ Ok(_) => HttpResponse::Ok().json(Response {
+ message: format!("{} request executed with no errors", method).to_owned(),
+ }),
+ Err(e) => HttpResponse::BadRequest().json(Response {
+ message: format!("There was an issue in the request: {}", e).to_owned(),
+ }),
+ }
+}
diff --git a/src/api/search_results.rs b/src/api/search_results.rs
index 6456ff9..cd47054 100644
--- a/src/api/search_results.rs
+++ b/src/api/search_results.rs
@@ -1,36 +1,30 @@
-use crate::database::{Album, Artist, Song};
+use crate::database::album::Album;
+use crate::database::artist::Artist;
+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 {
- id: Option<String>,
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>,
get_args: web::Query<SearchQueryOptions>,
) -> HttpResponse {
- let default: String = String::from("");
-
- if get_args.id.is_some() {
- let id: &str = get_args.id.as_ref().unwrap_or(&default);
- let search_attempt: (
- sqlx::Result<Option<Artist>, sqlx::Error>,
- sqlx::Result<Option<Album>, sqlx::Error>,
- sqlx::Result<Option<Song>, sqlx::Error>,
- ) = { app_state.database.search_results_by_id(id).await };
-
- return HttpResponse::Ok().json((
- search_attempt.0.unwrap_or(None),
- search_attempt.1.unwrap_or(None),
- search_attempt.2.unwrap_or(None),
- ));
- };
-
let search_attempt: (
sqlx::Result<Vec<Artist>, sqlx::Error>,
sqlx::Result<Vec<Album>, sqlx::Error>,
@@ -42,9 +36,11 @@ pub async fn search_results(
.search_results_by_name(&get_args.name.clone().unwrap())
.await
}
- _ => app_state.database.search_results().await, // Err(sqlx::Error::RowNotFound),
- // Err(sqlx::Error::RowNotFound),
- // Err(sqlx::Error::RowNotFound),
+ _ => (
+ Err(sqlx::Error::RowNotFound),
+ Err(sqlx::Error::RowNotFound),
+ Err(sqlx::Error::RowNotFound),
+ ),
};
return HttpResponse::Ok().json((
diff --git a/src/api/song.rs b/src/api/song.rs
index 850c759..06bc4b6 100644
--- a/src/api/song.rs
+++ b/src/api/song.rs
@@ -1,9 +1,14 @@
-use crate::database::{Delete, Song, SongPost, SongPut};
+use crate::api::{get_response_from_query, Response};
+use crate::database::song::{Song, SongPost, SongPut};
+use crate::database::Delete;
+use crate::extractors::auth_token::AuthenticationToken;
use crate::AppState;
use actix_web::{delete, get, post, put, web, HttpResponse};
use serde::Deserialize;
+use utoipa::IntoParams;
-#[derive(Deserialize)]
+/* Possible arguments ( /song?arg=value ) */
+#[derive(Deserialize, IntoParams)]
struct SongQueryOptions {
id: Option<String>,
name: Option<String>,
@@ -11,25 +16,37 @@ 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>,
get_args: web::Query<SongQueryOptions>,
) -> HttpResponse {
- let default = String::from("");
+ /* Avoid lifespan issues */
+ let default = String::default();
+ /* Handle individual result for search-by-id */
if get_args.id.is_some() {
let id: &str = get_args.id.as_ref().unwrap_or(&default);
- let search_attempt: sqlx::Result<Option<Song>> =
+ let query_result: sqlx::Result<Option<Song>> =
app_state.database.select_song_by_id(id).await;
- return match search_attempt {
+ return match query_result {
Ok(song_list) => HttpResponse::Ok().json(song_list),
Err(e) => HttpResponse::Ok().body(format!("{}", e)),
};
}
- let search_attempt: sqlx::Result<Vec<Song>> = match true {
+ /* Handle N results */
+ let query_result: sqlx::Result<Vec<Song>> = match true {
_ if get_args.name.is_some() => {
let name: &str = get_args.name.as_ref().unwrap_or(&default);
app_state.database.select_songs_by_name(name).await
@@ -45,40 +62,89 @@ pub async fn get_song(
_ => app_state.database.select_songs().await,
};
- match search_attempt {
+ match query_result {
Ok(song_list) => HttpResponse::Ok().json(song_list),
Err(e) => HttpResponse::Ok().body(format!("{}", e)),
}
}
+#[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>,
- post_data: web::Json<SongPost>,
+ request_data: web::Json<SongPost>,
+ _auth_token: AuthenticationToken,
) -> HttpResponse {
- match app_state.database.create_song(post_data.into_inner()).await {
- Ok(_) => HttpResponse::Ok().body("Post succeeded\n"),
- Err(e) => HttpResponse::Ok().body(format!("{}", e)),
- }
+ get_response_from_query(
+ app_state
+ .database
+ .create_song(request_data.into_inner())
+ .await,
+ "POST".to_string(),
+ )
}
+#[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>,
- post_data: web::Json<SongPut>,
+ request_data: web::Json<SongPut>,
+ _auth_token: AuthenticationToken,
) -> HttpResponse {
- match app_state.database.edit_song(post_data.into_inner()).await {
- Ok(_) => HttpResponse::Ok().body("Put succeeded\n"),
- Err(e) => HttpResponse::Ok().body(format!("{}", e)),
- }
+ get_response_from_query(
+ app_state
+ .database
+ .edit_song(request_data.into_inner())
+ .await,
+ "PUT".to_owned(),
+ )
}
+#[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>,
- post_data: web::Json<Delete>,
+ request_data: web::Json<Delete>,
+ _auth_token: AuthenticationToken,
) -> HttpResponse {
- let id: i32 = post_data
+ /* Check if ID is valid (return -1 if invalid) */
+ let id: i32 = request_data
.into_inner()
.id
.unwrap_or(String::default())
@@ -86,11 +152,13 @@ pub async fn delete_song(
.unwrap_or(-1);
if id == -1 {
- return HttpResponse::Ok().body("Invalid id value, code not executed\n");
+ return HttpResponse::BadRequest().json(Response {
+ message: "Invalid id value, code not executed".to_owned(),
+ });
}
- match app_state.database.delete_song(id).await {
- Ok(_) => HttpResponse::Ok().body("Deletion succeeded\n"),
- Err(e) => HttpResponse::Ok().body(format!("There was an issue in the request:\n{}", e)),
- }
+ get_response_from_query(
+ app_state.database.delete_song(id).await,
+ "DELETE".to_owned(),
+ )
}