Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
82 changes: 82 additions & 0 deletions latent-backend/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion latent-backend/api/.env.example
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
DB_URL=postgres://postgres:mysecretpassword@localhost:5432
TWILIO_AUTH_TOKEN="<<TWILIO_AUTH_TOKEN>>"
TWILIO_ACCOUNT_SID="<<TWILIO_ACCOUNT_SID>>"
TWILIO_PHONE_NUMBER="<<TWILIO_PHONE_NUMBER>>"
TWILIO_PHONE_NUMBER="<<TWILIO_PHONE_NUMBER>>"
PORT=8080
5 changes: 4 additions & 1 deletion latent-backend/api/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ edition = "2021"
[dependencies]
db = { path = "../db" }
dotenv = "0.15.0"
jsonwebtoken = "9.3.0"
poem = "3.1.3"
poem-openapi = { version = "5.1.2", features = ["swagger-ui"] }
serde = "1.0.217"
Expand All @@ -16,4 +17,6 @@ sqlx = { version = "0.8", features = ["runtime-tokio", "postgres", "tls-native-t
reqwest = { version = "0.11.13", features = ["json"] }
env_logger = "0.10"
log = "0.4"
sha2 = "0.10"
sha2 = "0.10"
time = { version = "0.3", features = ["macros"] }
chrono = { version = "0.4", features = ["serde"] }
37 changes: 37 additions & 0 deletions latent-backend/api/src/error.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
use db::DBError;
use poem_openapi::{payload::Json, ApiResponse, Object};
use poem::error::Error as PoemError;
use jsonwebtoken::errors::Error as JwtError;

#[derive(Debug, Object)]
pub struct ErrorBody {
Expand Down Expand Up @@ -30,6 +33,10 @@ pub enum AppError {
/// Bad request (400)
#[oai(status = 400)]
BadRequest(Json<ErrorBody>),

/// Admin Not Found (411)
#[oai(status = 411)]
AdminNotFound(Json<ErrorBody>),
}

impl From<sqlx::Error> for AppError {
Expand All @@ -44,3 +51,33 @@ impl From<sqlx::Error> for AppError {
}
}
}

impl From<PoemError> for AppError {
fn from(err: PoemError) -> Self {
AppError::InternalServerError(Json(ErrorBody {
message: err.to_string(),
}))
}
}

impl From<JwtError> for AppError {
fn from(err: JwtError) -> Self {
AppError::InternalServerError(Json(ErrorBody {
message: format!("JWT error: {}", err),
}))
}
}

impl From<DBError> for AppError {
fn from(err: DBError) -> Self {
match err {
DBError::NotFound(msg) => AppError::NotFound(Json(ErrorBody {
message: msg,
})),
DBError::InvalidInput(msg) => AppError::BadRequest(Json(ErrorBody {
message: msg,
})),
DBError::DatabaseError(sqlx_err) => sqlx_err.into(), // Convert sqlx::Error to AppError
}
}
}
61 changes: 42 additions & 19 deletions latent-backend/api/src/main.rs
Original file line number Diff line number Diff line change
@@ -1,57 +1,80 @@
use poem::{
listener::TcpListener,
middleware::Cors,
EndpointExt, Route, Server,
listener::TcpListener, middleware::Cors, EndpointExt, Result,
Route, Server,
};
use poem_openapi::OpenApiService;
use std::sync::Arc;

mod error;
mod middleware;
mod routes;
mod utils;

use db::Db;
use dotenv::dotenv;
use std::env;

#[derive(Clone)]
pub struct AppState {
db: Arc<Db>,
}

pub struct Api;

#[tokio::main]
async fn main() -> Result<(), std::io::Error> {
// Load environment variables
dotenv().ok();

// Initialize logger
env_logger::init_from_env(env_logger::Env::default().default_filter_or("info"));

// Read port from environment variable
let port = env::var("PORT").unwrap_or_else(|_| "8080".to_string());
let server_url = format!("http://localhost:{}/api/v1", port);

// Create and initialize database
let db = Db::new().await;
db.init().await.expect("Failed to initialize database");
let db = Arc::new(db);

// Create API service
let api_service = OpenApiService::new(Api, "Latent Booking", "1.0")
.server("http://localhost:3000/api/v1");

let api_service =
OpenApiService::new(routes::user::UserApi, "Latent Booking", "1.0")
.server(format!("{}/user", server_url));

let admin_api_service =
OpenApiService::new(routes::admin::AdminApi, "Admin Latent Booking", "1.0")
.server(format!("{}/admin", server_url));

let event_api_service =
OpenApiService::new(routes::event::EventApi, "Event Latent Booking", "1.0")
.server(format!("{}/admin/event", server_url));

// Create Swagger UI
let ui = api_service.swagger_ui();

// Create route with CORS
let app = Route::new()
.nest("/api/v1", api_service)
.nest("/docs", ui)
.with(Cors::new())
.data(AppState { db });

println!("Server running at http://localhost:3000");
println!("API docs at http://localhost:3000/docs");

let mut app = Route::new()
.nest("/api/v1/user", api_service)
.nest("/api/v1/admin", admin_api_service)
.nest("/api/v1/admin/event", event_api_service)
.nest("/docs", ui);

if cfg!(debug_assertions) {
let test_api_service =
OpenApiService::new(routes::test::TestApi, "Test Latent Booking", "1.0")
.server(format!("{}/test", server_url));

app = app.nest("/api/v1/test", test_api_service);
println!("Test routes enabled (development mode)");
}

let app = app.with(Cors::new()).data(AppState { db });

println!("Server running at {}", server_url);
println!("API docs at {}/docs", server_url);

// Start server
Server::new(TcpListener::bind("0.0.0.0:3000"))
Server::new(TcpListener::bind(format!("0.0.0.0:{}", port)))
.run(app)
.await
}
Loading