feat: add SongSearchService and GET /songs?q= search endpoint
This commit is contained in:
6
crates/api/.gitignore
vendored
Normal file
6
crates/api/.gitignore
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
*.sqlite
|
||||
*.log
|
||||
.env
|
||||
*.db
|
||||
*.db-shm
|
||||
*.db-wal
|
||||
@@ -1,9 +1,9 @@
|
||||
mod routes;
|
||||
|
||||
use axum::{Router, routing::{get, post}};
|
||||
use common::SongService;
|
||||
use common::{SongSearchService, SongService};
|
||||
use persistence::SqliteRepositoryFactory;
|
||||
use routes::songs::{create_song, delete_song, get_song, list_songs};
|
||||
use routes::songs::{create_song, delete_song, get_song, list_songs, update_song};
|
||||
use routes::tabs::{AppState, parse_tab};
|
||||
use std::sync::Arc;
|
||||
use tower_http::cors::{Any, CorsLayer};
|
||||
@@ -18,12 +18,14 @@ async fn main() {
|
||||
let repo = SqliteRepositoryFactory::create(&database_url)
|
||||
.await
|
||||
.expect("failed to connect to database");
|
||||
let songs = SongService::new(Box::new(repo));
|
||||
let songs = SongService::new(Box::new(repo.clone()));
|
||||
let search = SongSearchService::new(Box::new(repo));
|
||||
|
||||
let state = Arc::new(AppState {
|
||||
fetcher: Box::new(UgTabFetcher::new()),
|
||||
parser: Box::new(UgHtmlParser),
|
||||
songs,
|
||||
search,
|
||||
});
|
||||
|
||||
let cors = CorsLayer::new()
|
||||
@@ -34,7 +36,7 @@ async fn main() {
|
||||
let app = Router::new()
|
||||
.route("/tabs/parse", post(parse_tab))
|
||||
.route("/songs", post(create_song).get(list_songs))
|
||||
.route("/songs/{id}", get(get_song).delete(delete_song))
|
||||
.route("/songs/{id}", get(get_song).delete(delete_song).patch(update_song))
|
||||
.layer(cors)
|
||||
.with_state(state);
|
||||
|
||||
|
||||
@@ -1,12 +1,18 @@
|
||||
use axum::{
|
||||
extract::{Path, State},
|
||||
extract::{Path, Query, State},
|
||||
http::StatusCode,
|
||||
Json,
|
||||
};
|
||||
use domain::RepositoryError;
|
||||
use serde::Deserialize;
|
||||
use std::sync::Arc;
|
||||
use uuid::Uuid;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct ListQuery {
|
||||
pub q: Option<String>,
|
||||
}
|
||||
|
||||
use crate::routes::tabs::{AppState, ErrorResponse, ParseRequest, resolve_html};
|
||||
|
||||
pub async fn create_song(
|
||||
@@ -30,11 +36,24 @@ pub async fn create_song(
|
||||
|
||||
pub async fn list_songs(
|
||||
State(state): State<Arc<AppState>>,
|
||||
Query(params): Query<ListQuery>,
|
||||
) -> Result<Json<Vec<domain::SongSummary>>, (StatusCode, Json<ErrorResponse>)> {
|
||||
let songs = state.songs.list().await.map_err(|e| {
|
||||
(StatusCode::INTERNAL_SERVER_ERROR, Json(ErrorResponse { error: e.to_string() }))
|
||||
})?;
|
||||
Ok(Json(songs))
|
||||
let result = if let Some(q) = params.q.filter(|s| !s.is_empty()) {
|
||||
state.search.search(&q).await
|
||||
} else {
|
||||
state.songs.list().await
|
||||
};
|
||||
result
|
||||
.map(Json)
|
||||
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, Json(ErrorResponse { error: e.to_string() })))
|
||||
}
|
||||
|
||||
pub async fn update_song(
|
||||
State(_state): State<Arc<AppState>>,
|
||||
Path(_id): Path<String>,
|
||||
Json(_body): Json<serde_json::Value>,
|
||||
) -> StatusCode {
|
||||
StatusCode::NOT_IMPLEMENTED
|
||||
}
|
||||
|
||||
pub async fn get_song(
|
||||
|
||||
@@ -7,6 +7,7 @@ pub struct AppState {
|
||||
pub fetcher: Box<dyn TabFetcherPort>,
|
||||
pub parser: Box<dyn TabParserPort>,
|
||||
pub songs: common::SongService,
|
||||
pub search: common::SongSearchService,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
|
||||
Reference in New Issue
Block a user