use std::env; use axum::{ routing::{get, post}, http::StatusCode, Json, Router, }; use axum::extract::{Path, State}; use axum::routing::delete; use dotenv::dotenv; use env_logger::fmt::style::Reset; use http::{header, Method}; use common::models::article::{delete_article, insert_article, load_article, load_articles, update_article, Article}; use common::models::origin::Origin; use sqlx::{ postgres::PgPoolOptions, PgPool }; use tower_http::cors::{AllowOrigin, Any, CorsLayer}; use tracing::info; use http::header::{AUTHORIZATION, ACCEPT, ACCESS_CONTROL_ALLOW_ORIGIN, CONTENT_TYPE, ACCESS_CONTROL_ALLOW_CREDENTIALS}; use common::models::article; use common::models::unit::Unit; use common::models::variant::{delete_variant, insert_variant, update_variant, Variant}; #[tokio::main] async fn main() { dotenv().ok(); let server_address = env::var("SERVER_ADDRESS").expect("SERVER_ADDRESS is not set in .env file"); let server_port = env::var("SERVER_PORT").expect("SERVER_PORT is not set in .env file"); let database = env::var("DATABASE_URL").expect("DATABASE is not set in .env file"); let addr = format!("{}:{}", server_address, server_port); // initialize tracing tracing_subscriber::fmt().init(); let pool = PgPoolOptions::new() .max_connections(50) .connect(&database) .await .expect(&format!("Can't connect to database: {}", &database)); sqlx::migrate!().run(&pool).await.expect("Migrations failed"); let cors = CorsLayer::new() .allow_methods([Method::GET, Method::POST, Method::DELETE]) .allow_headers([ACCEPT, ACCESS_CONTROL_ALLOW_ORIGIN, CONTENT_TYPE]) .allow_origin(AllowOrigin::any()); // build our application with a route let app = Router::new() .route("/articles", get(get_articles)) .route("/article", post(create_article)) .route("/article/{id}", get(get_article)) .route("/article/{id}", post(update_article_route)) .route("/article/{id}", delete(delete_article_route)) .route("/article/{id}/variant", post(add_variant)) .route("/variant/{id}", post(update_variant_route)) .route("/variant/{id}", delete(delete_variant_route)) .with_state(pool) .layer(cors); let listener = tokio::net::TcpListener::bind(&addr).await.unwrap(); info!("Server started on: {}", &addr); axum::serve(listener, app).await.unwrap(); } #[tracing::instrument(ret)] async fn get_articles( State(pool): State, ) -> (StatusCode, Json>) { info!("Get all articles"); let result_articles: Option> = load_articles(pool).await; match result_articles { Some(articles) => { (StatusCode::OK, Json(articles)) } None => { (StatusCode::INTERNAL_SERVER_ERROR, Json(vec![])) } } } #[tracing::instrument(ret)] async fn get_article( Path(id): Path, State(pool): State, ) -> (StatusCode, Json
) { info!("Get article with id: {}", id); let result_article: Option
= load_article(id, pool).await; match result_article { Some(article) => { (StatusCode::OK, Json(article)) } None => { let article = Article { id, name: "".to_string(), bio: false, origin: Origin::EU, description: None, ingredients: None, default_unit: Unit::G, variants: vec![], }; (StatusCode::INTERNAL_SERVER_ERROR, Json(article)) } } } #[tracing::instrument(ret)] async fn create_article( State(pool): State, Json(payload): Json
, ) -> (StatusCode, Json
) { info!("Create article"); match insert_article(&payload, pool).await { Some(article) => { (StatusCode::CREATED, Json(article)) } None => { let article = Article { id: 0, name: "".to_string(), bio: false, origin: Origin::EU, description: None, ingredients: None, default_unit: Unit::G, variants: vec![], }; (StatusCode::INTERNAL_SERVER_ERROR, Json(article)) } } } async fn update_article_route( State(pool): State, Path(article_id): Path, Json(payload): Json
, ) -> (StatusCode, Json
) { info!("Update article"); let update_article = update_article(article_id, &payload, pool).await; match update_article { Some(article) => { (StatusCode::OK, Json(article)) } None => { let article = Article { id: 0, name: "".to_string(), bio: false, origin: Origin::EU, description: None, ingredients: None, default_unit: Unit::G, variants: vec![], }; (StatusCode::INTERNAL_SERVER_ERROR, Json(article)) } } } async fn delete_article_route( State(pool): State, Path(article_id): Path, ) -> StatusCode { info!("Delete article"); let delete_article_result = delete_article(article_id, pool).await; match delete_article_result { Some(_) => { StatusCode::OK } None => { StatusCode::INTERNAL_SERVER_ERROR } } } async fn add_variant( State(pool): State, Path(article_id): Path, Json(payload): Json, ) -> (StatusCode, Json) { info!("Add variant"); let created_variant = insert_variant(article_id, &payload, pool).await; match created_variant { Some(variant) => { (StatusCode::OK, Json(variant)) } None => { let variant = Variant { id: 0, weight: "".to_string(), ean: None, name: None, description: None, ingredients: None, unit: Unit::G, label: None, variant_description: None, }; (StatusCode::INTERNAL_SERVER_ERROR, Json(variant)) } } } async fn update_variant_route( State(pool): State, Path(variant_id): Path, Json(payload): Json, ) -> (StatusCode, Json) { info!("Update variant"); let update_variant = update_variant(variant_id, &payload, pool).await; match update_variant { Some(variant) => { (StatusCode::OK, Json(variant)) } None => { let variant = Variant { id: 0, weight: "".to_string(), ean: None, name: None, description: None, ingredients: None, unit: Unit::G, label: None, variant_description: None, }; (StatusCode::INTERNAL_SERVER_ERROR, Json(variant)) } } } async fn delete_variant_route( State(pool): State, Path(variant_id): Path, ) -> StatusCode { info!("Delete variant"); let delete_variant_result = delete_variant(variant_id, pool).await; match delete_variant_result { Some(_) => { StatusCode::OK } None => { StatusCode::INTERNAL_SERVER_ERROR } } }