feat: add SongSearchService and GET /songs?q= search endpoint

This commit is contained in:
2026-04-08 03:35:33 +02:00
parent c3b7cb78ab
commit 377fe957bc
9 changed files with 98 additions and 12 deletions

6
crates/api/.gitignore vendored Normal file
View File

@@ -0,0 +1,6 @@
*.sqlite
*.log
.env
*.db
*.db-shm
*.db-wal

View File

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

View File

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

View File

@@ -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)]