161 lines
6.1 KiB
Rust
161 lines
6.1 KiB
Rust
use axum::{
|
|
routing::{get, post, put, delete},
|
|
Router, middleware as axum_middleware,
|
|
};
|
|
use sea_orm::{sqlx, Database, DatabaseConnection, DbErr};
|
|
use sea_orm_migration::prelude::*;
|
|
use std::net::SocketAddr;
|
|
use utoipa::OpenApi;
|
|
use utoipa_swagger_ui::SwaggerUi;
|
|
use tower_sessions::{Expiry, SessionManagerLayer};
|
|
use tower_sessions_sqlx_store::PostgresStore;
|
|
use axum_login::AuthManagerLayerBuilder;
|
|
use time::Duration;
|
|
use tower_http::cors::CorsLayer;
|
|
use axum::http::{Method, HeaderValue};
|
|
|
|
pub mod models;
|
|
pub mod services;
|
|
pub mod migration;
|
|
pub mod routes;
|
|
pub mod auth;
|
|
pub mod middleware;
|
|
|
|
pub use auth::AuthBackend;
|
|
pub use middleware::require_admin;
|
|
|
|
#[derive(OpenApi)]
|
|
#[openapi(
|
|
paths(
|
|
routes::auth::login,
|
|
routes::auth::logout,
|
|
routes::family::create_family,
|
|
routes::family::get_family,
|
|
routes::family::get_all_families,
|
|
routes::family::update_family,
|
|
routes::family::delete_family,
|
|
routes::category::create_category,
|
|
routes::category::get_category,
|
|
routes::category::get_categories_by_family,
|
|
routes::category::update_category,
|
|
routes::category::delete_category,
|
|
routes::expense::create_expense,
|
|
routes::expense::get_expense,
|
|
routes::expense::get_expenses_by_category,
|
|
routes::expense::update_expense,
|
|
routes::expense::delete_expense,
|
|
routes::expense::get_remaining_limit,
|
|
),
|
|
components(
|
|
schemas(
|
|
models::family::Model,
|
|
models::category::Model,
|
|
models::expense::Model,
|
|
routes::auth::LoginRequest,
|
|
routes::auth::LoginResponse,
|
|
routes::family::CreateFamilyRequest,
|
|
routes::family::UpdateFamilyRequest,
|
|
routes::category::CreateCategoryRequest,
|
|
routes::category::UpdateCategoryRequest,
|
|
routes::expense::CreateExpenseRequest,
|
|
routes::expense::UpdateExpenseRequest,
|
|
routes::expense::RemainingLimitResponse,
|
|
)
|
|
),
|
|
tags(
|
|
(name = "auth", description = "Authentication endpoints"),
|
|
(name = "families", description = "Family management endpoints"),
|
|
(name = "categories", description = "Category management endpoints"),
|
|
(name = "expenses", description = "Expense management endpoints")
|
|
),
|
|
info(
|
|
title = "Family Budget API",
|
|
version = "0.1.0",
|
|
description = "REST API"
|
|
)
|
|
)]
|
|
struct ApiDoc;
|
|
|
|
pub async fn establish_connection() -> Result<DatabaseConnection, DbErr> {
|
|
dotenvy::dotenv().ok();
|
|
|
|
let database_url = std::env::var("DATABASE_URL")
|
|
.expect("DATABASE_URL must be set in .env file");
|
|
|
|
Database::connect(&database_url).await
|
|
}
|
|
|
|
pub async fn create_app(db: DatabaseConnection) -> Result<Router, DbErr> {
|
|
let database_url = std::env::var("DATABASE_URL")
|
|
.expect("DATABASE_URL must be set in .env file");
|
|
|
|
let session_store = PostgresStore::new(
|
|
sqlx::PgPool::connect(&database_url)
|
|
.await
|
|
.expect("Failed to connect to database for sessions"),
|
|
);
|
|
session_store
|
|
.migrate()
|
|
.await
|
|
.expect("Failed to run session store migrations");
|
|
|
|
let session_layer = SessionManagerLayer::new(session_store)
|
|
.with_secure(false)
|
|
.with_expiry(Expiry::OnInactivity(Duration::days(1)));
|
|
|
|
let backend = auth::AuthBackend { db: db.clone() };
|
|
let auth_layer = AuthManagerLayerBuilder::new(backend, session_layer).build();
|
|
|
|
let protected_routes = Router::new()
|
|
.route("/families", post(routes::family::create_family))
|
|
.route_layer(axum_middleware::from_fn(middleware::require_admin));
|
|
|
|
let api_routes = Router::new()
|
|
.route("/login", post(routes::auth::login))
|
|
.route("/logout", post(routes::auth::logout))
|
|
.merge(protected_routes)
|
|
.route("/families", get(routes::family::get_all_families))
|
|
.route("/families/{id}", get(routes::family::get_family))
|
|
.route("/families/{id}", put(routes::family::update_family))
|
|
.route("/families/{id}", delete(routes::family::delete_family))
|
|
.route("/families/{family_id}/categories", post(routes::category::create_category))
|
|
.route("/families/{family_id}/categories", get(routes::category::get_categories_by_family))
|
|
.route("/families/{family_id}/categories/{category_id}", get(routes::category::get_category))
|
|
.route("/families/{family_id}/categories/{category_id}", put(routes::category::update_category))
|
|
.route("/families/{family_id}/categories/{category_id}", delete(routes::category::delete_category))
|
|
.route("/families/{family_id}/categories/{category_id}/expenses", post(routes::expense::create_expense))
|
|
.route("/families/{family_id}/categories/{category_id}/expenses", get(routes::expense::get_expenses_by_category))
|
|
.route("/families/{family_id}/categories/{category_id}/expenses/{expense_id}", get(routes::expense::get_expense))
|
|
.route("/families/{family_id}/categories/{category_id}/expenses/{expense_id}", put(routes::expense::update_expense))
|
|
.route("/families/{family_id}/categories/{category_id}/expenses/{expense_id}", delete(routes::expense::delete_expense))
|
|
.route("/families/{family_id}/categories/{category_id}/remaining", get(routes::expense::get_remaining_limit))
|
|
.layer(auth_layer)
|
|
.with_state(db);
|
|
|
|
let swagger_ui = SwaggerUi::new("/swagger-ui")
|
|
.url("/api-docs/openapi.json", ApiDoc::openapi());
|
|
|
|
let cors = CorsLayer::new()
|
|
.allow_origin([
|
|
"http://localhost:3000".parse::<HeaderValue>().unwrap(),
|
|
"http://localhost:8080".parse::<HeaderValue>().unwrap(),
|
|
])
|
|
.allow_methods([Method::GET, Method::POST, Method::PUT, Method::DELETE, Method::OPTIONS])
|
|
.allow_headers([
|
|
axum::http::header::CONTENT_TYPE,
|
|
axum::http::header::AUTHORIZATION,
|
|
axum::http::header::ACCEPT,
|
|
])
|
|
.allow_credentials(true);
|
|
|
|
let app = api_routes
|
|
.layer(cors)
|
|
.merge(swagger_ui);
|
|
|
|
Ok(app)
|
|
}
|
|
|
|
pub fn server_address() -> SocketAddr {
|
|
SocketAddr::from(([0, 0, 0, 0], 8080))
|
|
}
|