feat(api): wire POST /tabs/parse endpoint with fetcher and parser
This commit is contained in:
27
crates/api/Cargo.toml
Normal file
27
crates/api/Cargo.toml
Normal file
@@ -0,0 +1,27 @@
|
||||
[package]
|
||||
name = "api"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
anyhow = { workspace = true }
|
||||
axum = { version = "0.8.8", features = ["macros"] }
|
||||
serde = { workspace = true }
|
||||
serde_json = { workspace = true }
|
||||
thiserror = { workspace = true }
|
||||
tokio = { workspace = true }
|
||||
tower-http = { version = "0.6.8", features = [
|
||||
"cors",
|
||||
"fs",
|
||||
"trace",
|
||||
"tracing",
|
||||
] }
|
||||
tracing = { workspace = true }
|
||||
tracing-subscriber = { workspace = true }
|
||||
uuid = { workspace = true }
|
||||
rand = { workspace = true }
|
||||
|
||||
persistence = { path = "../infrastructure/persistence" }
|
||||
common = { path = "../common" }
|
||||
domain = { path = "../domain" }
|
||||
ug-parser = { path = "../infrastructure/ug-parser" }
|
||||
24
crates/api/src/main.rs
Normal file
24
crates/api/src/main.rs
Normal file
@@ -0,0 +1,24 @@
|
||||
mod routes;
|
||||
|
||||
use axum::{routing::post, Router};
|
||||
use routes::tabs::{parse_tab, AppState};
|
||||
use std::sync::Arc;
|
||||
use ug_parser::{UgHtmlParser, UgTabFetcher};
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
tracing_subscriber::fmt::init();
|
||||
|
||||
let state = Arc::new(AppState {
|
||||
fetcher: Box::new(UgTabFetcher::new()),
|
||||
parser: Box::new(UgHtmlParser),
|
||||
});
|
||||
|
||||
let app = Router::new()
|
||||
.route("/tabs/parse", post(parse_tab))
|
||||
.with_state(state);
|
||||
|
||||
let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap();
|
||||
tracing::info!("listening on {}", listener.local_addr().unwrap());
|
||||
axum::serve(listener, app).await.unwrap();
|
||||
}
|
||||
1
crates/api/src/routes/mod.rs
Normal file
1
crates/api/src/routes/mod.rs
Normal file
@@ -0,0 +1 @@
|
||||
pub mod tabs;
|
||||
41
crates/api/src/routes/tabs.rs
Normal file
41
crates/api/src/routes/tabs.rs
Normal file
@@ -0,0 +1,41 @@
|
||||
use axum::{extract::State, http::StatusCode, Json};
|
||||
use domain::{TabFetcherPort, TabParserPort, TabSource};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::{path::PathBuf, sync::Arc};
|
||||
|
||||
pub struct AppState {
|
||||
pub fetcher: Box<dyn TabFetcherPort>,
|
||||
pub parser: Box<dyn TabParserPort>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct ParseRequest {
|
||||
pub source: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub struct ErrorResponse {
|
||||
pub error: String,
|
||||
}
|
||||
|
||||
pub async fn parse_tab(
|
||||
State(state): State<Arc<AppState>>,
|
||||
Json(body): Json<ParseRequest>,
|
||||
) -> Result<Json<domain::Song>, (StatusCode, Json<ErrorResponse>)> {
|
||||
let source = if body.source.starts_with("file://") {
|
||||
let path = body.source.trim_start_matches("file://");
|
||||
TabSource::File(PathBuf::from(path))
|
||||
} else {
|
||||
TabSource::Url(body.source)
|
||||
};
|
||||
|
||||
let html = state.fetcher.fetch(source).await.map_err(|e| {
|
||||
(StatusCode::BAD_GATEWAY, Json(ErrorResponse { error: e.to_string() }))
|
||||
})?;
|
||||
|
||||
let song = state.parser.parse(&html).map_err(|e| {
|
||||
(StatusCode::UNPROCESSABLE_ENTITY, Json(ErrorResponse { error: e.to_string() }))
|
||||
})?;
|
||||
|
||||
Ok(Json(song))
|
||||
}
|
||||
Reference in New Issue
Block a user