Merge pull request #36 from stevedylandev/feat/cellar-add-api
f8b2bd6d
feat: add cellar api
4 file(s) · +40 −2
feat: add cellar api
| 695 | 695 | ||
| 696 | 696 | [[package]] |
|
| 697 | 697 | name = "cellar" |
|
| 698 | - | version = "0.2.1" |
|
| 698 | + | version = "0.2.2" |
|
| 699 | 699 | dependencies = [ |
|
| 700 | 700 | "andromeda-auth", |
|
| 701 | 701 | "andromeda-darkmatter-css", |
|
| 717 | 717 | "serde_rusqlite", |
|
| 718 | 718 | "subtle", |
|
| 719 | 719 | "tokio", |
|
| 720 | + | "tower-http", |
|
| 720 | 721 | "tracing", |
|
| 721 | 722 | "tracing-subscriber", |
|
| 722 | 723 | ] |
|
| 1 | 1 | [package] |
|
| 2 | 2 | name = "cellar" |
|
| 3 | - | version = "0.2.1" |
|
| 3 | + | version = "0.2.2" |
|
| 4 | 4 | edition = "2024" |
|
| 5 | 5 | description = "Personal wine tasting log" |
|
| 6 | 6 | license = "MIT" |
|
| 30 | 30 | image = "0.25" |
|
| 31 | 31 | serde_rusqlite = "0.41" |
|
| 32 | 32 | chrono = "0.4" |
|
| 33 | + | tower-http = { workspace = true, features = ["cors"] } |
|
| 1 | 1 | use askama_web::WebTemplate; |
|
| 2 | 2 | use axum::{ |
|
| 3 | + | Json, |
|
| 3 | 4 | extract::{Path, State}, |
|
| 4 | 5 | http::{HeaderValue, StatusCode}, |
|
| 5 | 6 | response::{Html, IntoResponse, Response}, |
|
| 79 | 80 | Err(e) => { |
|
| 80 | 81 | tracing::error!("Failed to get wine: {}", e); |
|
| 81 | 82 | (StatusCode::INTERNAL_SERVER_ERROR, Html("Server error".to_string())).into_response() |
|
| 83 | + | } |
|
| 84 | + | } |
|
| 85 | + | } |
|
| 86 | + | ||
| 87 | + | pub async fn api_list_wines(State(state): State<Arc<AppState>>) -> Response { |
|
| 88 | + | match db::get_cellar_wines(&state.db) { |
|
| 89 | + | Ok(wines) => Json(wines).into_response(), |
|
| 90 | + | Err(e) => { |
|
| 91 | + | tracing::error!("api_list_wines: {}", e); |
|
| 92 | + | StatusCode::INTERNAL_SERVER_ERROR.into_response() |
|
| 93 | + | } |
|
| 94 | + | } |
|
| 95 | + | } |
|
| 96 | + | ||
| 97 | + | pub async fn api_get_wine( |
|
| 98 | + | State(state): State<Arc<AppState>>, |
|
| 99 | + | Path(short_id): Path<String>, |
|
| 100 | + | ) -> Response { |
|
| 101 | + | match db::get_wine_by_short_id(&state.db, &short_id) { |
|
| 102 | + | Ok(Some(wine)) => Json(wine).into_response(), |
|
| 103 | + | Ok(None) => StatusCode::NOT_FOUND.into_response(), |
|
| 104 | + | Err(e) => { |
|
| 105 | + | tracing::error!("api_get_wine: {}", e); |
|
| 106 | + | StatusCode::INTERNAL_SERVER_ERROR.into_response() |
|
| 82 | 107 | } |
|
| 83 | 108 | } |
|
| 84 | 109 | } |
|
| 7 | 7 | use image::ImageDecoder; |
|
| 8 | 8 | use rust_embed::Embed; |
|
| 9 | 9 | use std::sync::Arc; |
|
| 10 | + | use tower_http::cors::{Any, CorsLayer}; |
|
| 10 | 11 | ||
| 11 | 12 | use crate::db::{self, Db, Wine}; |
|
| 12 | 13 | ||
| 534 | 535 | site_description, |
|
| 535 | 536 | }); |
|
| 536 | 537 | ||
| 538 | + | let cors = CorsLayer::new() |
|
| 539 | + | .allow_origin(Any) |
|
| 540 | + | .allow_methods([axum::http::Method::GET]); |
|
| 541 | + | ||
| 542 | + | let api = Router::new() |
|
| 543 | + | .route("/api/wines", get(public::api_list_wines)) |
|
| 544 | + | .route("/api/wines/{short_id}", get(public::api_get_wine)) |
|
| 545 | + | .layer(cors); |
|
| 546 | + | ||
| 537 | 547 | let app = Router::new() |
|
| 538 | 548 | // Public routes |
|
| 539 | 549 | .route("/", get(public::get_index)) |
|
| 540 | 550 | .route("/feed.xml", get(public::rss_feed)) |
|
| 541 | 551 | .route("/wines/{short_id}", get(public::get_wine_detail)) |
|
| 542 | 552 | .route("/wines/{short_id}/image", get(public::get_wine_image)) |
|
| 553 | + | .merge(api) |
|
| 543 | 554 | // Admin auth routes |
|
| 544 | 555 | .route("/admin/login", get(admin::get_login).post(admin::post_login)) |
|
| 545 | 556 | .route("/admin/logout", get(admin::get_logout)) |
|