feat: add MongoDB support with connection pooling and repository pattern
This commit is contained in:
2
.env
2
.env
@@ -1,4 +1,4 @@
|
||||
// .env.example (copy to .env for local use)
|
||||
# .env.example (copy to .env for local use)
|
||||
|
||||
RUST_LOG=info
|
||||
BIND_ADDRESS=127.0.0.1:3000
|
||||
|
||||
1022
Cargo.lock
generated
1022
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -5,7 +5,10 @@ edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
axum = "0.8.4"
|
||||
bson = { version = "2.9.0", features = ["chrono-0_4"] }
|
||||
chrono = { version = "0.4", features = ["serde"] }
|
||||
dotenvy = "0.15.7"
|
||||
mongodb = "2.8.2"
|
||||
serde = { version = "1.0.219", features = ["derive"] }
|
||||
serde_json = "1.0.142"
|
||||
sqlx = { version = "0.8.6", features = ["runtime-tokio", "tls-native-tls"] }
|
||||
|
||||
51
Dockerfile
Normal file
51
Dockerfile
Normal file
@@ -0,0 +1,51 @@
|
||||
# Use the official Rust image as a base
|
||||
FROM rust:bookworm as builder
|
||||
|
||||
# Set the working directory
|
||||
WORKDIR /app
|
||||
|
||||
# Install system dependencies
|
||||
RUN apt-get update && apt-get install -y \
|
||||
pkg-config \
|
||||
libssl-dev \
|
||||
# Add any other dependencies required by your project
|
||||
# For example, if you use postgres/mysql/sqlite, you might need libpq-dev, libmysqlclient-dev, libsqlite3-dev
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Copy the Cargo.toml and Cargo.lock first to leverage Docker cache
|
||||
# This layer only rebuilds if dependencies change
|
||||
COPY Cargo.toml Cargo.lock ./
|
||||
|
||||
# Create a dummy src directory and main.rs to build dependencies
|
||||
# This caches the dependency build
|
||||
RUN mkdir -p src && echo "fn main() {println!(\"hello world\");}" > src/main.rs
|
||||
# Build dependencies
|
||||
RUN cargo build --release && rm -rf src
|
||||
|
||||
# Copy the actual source code
|
||||
COPY . .
|
||||
|
||||
# Build the release binary
|
||||
RUN cargo build --release
|
||||
|
||||
# --- Start a new stage for a smaller final image ---
|
||||
FROM debian:bookworm-slim
|
||||
|
||||
# Set the working directory
|
||||
WORKDIR /app
|
||||
|
||||
# Install runtime dependencies if any
|
||||
# For example, if your Rust application dynamically links to OpenSSL, you might need libssl3
|
||||
RUN apt-get update && apt-get install -y \
|
||||
libssl3 \
|
||||
# Add any other runtime dependencies here
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Copy the built binary from the builder stage
|
||||
COPY --from=builder /app/target/release/employee-tracking-backend .
|
||||
|
||||
# Expose the port your application listens on
|
||||
EXPOSE 3000
|
||||
|
||||
# Set the entrypoint command to run your application
|
||||
CMD ["./employee-tracking-backend"]
|
||||
24
README.md
24
README.md
@@ -55,12 +55,34 @@ curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
|
||||
cargo build
|
||||
```
|
||||
|
||||
4. **Run the application:**
|
||||
4. **Run the application database with Docker (Recommended):**
|
||||
If you have Docker installed
|
||||
|
||||
5. **Run the application (Manual with Docker):**
|
||||
First, start the MongoDB container:
|
||||
|
||||
```sh
|
||||
docker run \
|
||||
--name mongodb \
|
||||
-p 27017:27017 \
|
||||
-e MONGO_INITDB_ROOT_USERNAME=admin \
|
||||
-e MONGO_INITDB_ROOT_PASSWORD=password123 \
|
||||
-e MONGO_INITDB_DATABASE=purenotify \
|
||||
-v mongodb_data:/data/db \
|
||||
mongo:latest
|
||||
```
|
||||
|
||||
(Note: The `purenotify` database name here should match `DATABASE_NAME` in your `.env` or `config.rs` for the backend to connect correctly. The `MONGODB_URI` for the backend would be `mongodb://127.0.0.1:27017` or `mongodb://localhost:27017`.)
|
||||
|
||||
Then, run the Rust application:
|
||||
For development, you can run the project directly with `cargo run`:
|
||||
|
||||
```sh
|
||||
cargo run
|
||||
```
|
||||
|
||||
For a release build, run:
|
||||
|
||||
```sh
|
||||
cargo run --release
|
||||
```
|
||||
|
||||
33
docker-compose.yml
Normal file
33
docker-compose.yml
Normal file
@@ -0,0 +1,33 @@
|
||||
version: "3.8"
|
||||
|
||||
services:
|
||||
purenotify_backend:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile
|
||||
ports:
|
||||
- "3000:3000"
|
||||
environment:
|
||||
# These should match the defaults or your specific configuration in config.rs
|
||||
BIND_ADDRESS: "0.0.0.0:3000"
|
||||
MONGODB_URI: "mongodb://mongodb:27017"
|
||||
DATABASE_NAME: "purenotify"
|
||||
RUST_LOG: "info,tower_http=debug,mongodb=debug"
|
||||
depends_on:
|
||||
- mongodb
|
||||
# Optional: If you want to enable the no-auth feature for local development
|
||||
# command: cargo run --features "no-auth"
|
||||
|
||||
mongodb:
|
||||
image: mongo:6.0
|
||||
ports:
|
||||
- "27017:27017"
|
||||
volumes:
|
||||
- mongodb_data:/data/db
|
||||
# Optional: MongoDB authentication (highly recommended for production)
|
||||
# MONGO_INITDB_ROOT_USERNAME: your_mongo_username
|
||||
# MONGO_INITDB_ROOT_PASSWORD: your_mongo_password
|
||||
# MONGODB_REPLICA_SET_NAME: rs0 # Uncomment for replica set
|
||||
|
||||
volumes:
|
||||
mongodb_data:
|
||||
@@ -4,10 +4,14 @@ use std::env;
|
||||
use std::net::SocketAddr;
|
||||
use std::str::FromStr;
|
||||
|
||||
#[derive(Debug)]
|
||||
#[cfg(feature = "no-auth")]
|
||||
use tracing::error;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Config {
|
||||
pub bind_address: SocketAddr,
|
||||
// pub database_url: Option<String>,
|
||||
pub mongodb_uri: String,
|
||||
pub database_name: String,
|
||||
}
|
||||
|
||||
impl Config {
|
||||
@@ -24,11 +28,15 @@ impl Config {
|
||||
return Err("In no-auth mode, BIND_ADDRESS must be 127.0.0.1".to_string());
|
||||
}
|
||||
|
||||
// let database_url = env::var("DATABASE_URL").ok();
|
||||
let mongodb_uri =
|
||||
env::var("MONGODB_URI").unwrap_or_else(|_| "mongodb://localhost:27017".to_string());
|
||||
|
||||
let database_name = env::var("DATABASE_NAME").unwrap_or_else(|_| "purenotify".to_string());
|
||||
|
||||
Ok(Self {
|
||||
bind_address,
|
||||
// database_url,
|
||||
mongodb_uri,
|
||||
database_name,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
26
src/main.rs
26
src/main.rs
@@ -1,7 +1,9 @@
|
||||
// src/main.rs
|
||||
|
||||
use std::process::exit;
|
||||
use std::sync::Arc;
|
||||
|
||||
use ::mongodb::Database;
|
||||
use axum::Router;
|
||||
use dotenvy::dotenv;
|
||||
use tokio::signal;
|
||||
@@ -11,9 +13,17 @@ use tracing_subscriber::{EnvFilter, fmt, prelude::*};
|
||||
|
||||
mod config;
|
||||
mod handlers;
|
||||
mod mongodb;
|
||||
mod routes;
|
||||
|
||||
use config::Config;
|
||||
use mongodb::MongoDb;
|
||||
|
||||
// Shared application state
|
||||
pub struct AppState {
|
||||
pub db: Database,
|
||||
pub config: Config,
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
@@ -35,6 +45,21 @@ async fn main() {
|
||||
}
|
||||
};
|
||||
|
||||
// Connect to MongoDB using the config
|
||||
let mongodb = match MongoDb::connect(&config).await {
|
||||
Ok(db) => db,
|
||||
Err(e) => {
|
||||
error!("Failed to connect to MongoDB: {}", e);
|
||||
exit(1);
|
||||
}
|
||||
};
|
||||
|
||||
// Create shared state
|
||||
let _shared_state = Arc::new(AppState {
|
||||
db: mongodb.database,
|
||||
config: config.clone(),
|
||||
});
|
||||
|
||||
#[cfg(feature = "no-auth")]
|
||||
info!("NO-AUTH MODE ENABLED");
|
||||
|
||||
@@ -42,7 +67,6 @@ async fn main() {
|
||||
|
||||
// Build the Axum router
|
||||
let app = Router::new()
|
||||
// .nest("/health", routes::health::health::health_routes())
|
||||
.nest("/health", routes::health::health::health_routes())
|
||||
.nest("/user", routes::user::user::user_routes())
|
||||
.layer(TraceLayer::new_for_http());
|
||||
|
||||
6
src/mongodb/mod.rs
Normal file
6
src/mongodb/mod.rs
Normal file
@@ -0,0 +1,6 @@
|
||||
// src/mongodb/mod.rs
|
||||
|
||||
pub mod models;
|
||||
pub mod mongodb;
|
||||
|
||||
pub use mongodb::MongoDb;
|
||||
5
src/mongodb/models/mod.rs
Normal file
5
src/mongodb/models/mod.rs
Normal file
@@ -0,0 +1,5 @@
|
||||
// src/mongodb/models/mod.rs
|
||||
|
||||
pub mod user;
|
||||
|
||||
// Re-exports can be added here when needed
|
||||
49
src/mongodb/models/user.rs
Normal file
49
src/mongodb/models/user.rs
Normal file
@@ -0,0 +1,49 @@
|
||||
use bson::oid::ObjectId;
|
||||
use chrono::{DateTime, Utc};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct User {
|
||||
#[serde(rename = "_id")]
|
||||
id: ObjectId,
|
||||
username: String,
|
||||
email: String,
|
||||
first_name: String,
|
||||
last_name: String,
|
||||
age: u32,
|
||||
is_active: bool,
|
||||
phone_number: String,
|
||||
password: String,
|
||||
salt: String,
|
||||
created_at: DateTime<Utc>,
|
||||
updated_at: DateTime<Utc>,
|
||||
last_login: DateTime<Utc>,
|
||||
role: String,
|
||||
profile: Option<Profile>,
|
||||
preferences: Option<Preferences>,
|
||||
stats: Option<Stats>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct Profile {
|
||||
avatar_url: String,
|
||||
bio: String,
|
||||
location: String,
|
||||
website: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct Preferences {
|
||||
theme: String,
|
||||
language: String,
|
||||
notifications_enabled: bool,
|
||||
email_verified: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct Stats {
|
||||
total_posts: u32,
|
||||
total_comments: u32,
|
||||
total_likes: u32,
|
||||
account_age_days: u32,
|
||||
}
|
||||
38
src/mongodb/mongodb.rs
Normal file
38
src/mongodb/mongodb.rs
Normal file
@@ -0,0 +1,38 @@
|
||||
use crate::config::Config;
|
||||
use mongodb::options::{ClientOptions, ServerApi, ServerApiVersion};
|
||||
use mongodb::{Client, Database};
|
||||
|
||||
pub struct MongoDb {
|
||||
// pub client: Client,
|
||||
pub database: Database,
|
||||
}
|
||||
|
||||
impl MongoDb {
|
||||
pub async fn connect(config: &Config) -> Result<Self, mongodb::error::Error> {
|
||||
// Parse connection string from config
|
||||
let mut client_options = ClientOptions::parse(&config.mongodb_uri).await?;
|
||||
|
||||
// Set the server API version (optional but recommended for MongoDB Atlas)
|
||||
let server_api = ServerApi::builder().version(ServerApiVersion::V1).build();
|
||||
client_options.server_api = Some(server_api);
|
||||
|
||||
// Optional: Set additional options
|
||||
client_options.app_name = Some("PureNotify".to_string());
|
||||
|
||||
// Create client
|
||||
let client = Client::with_options(client_options)?;
|
||||
|
||||
// Ping the server to verify connection
|
||||
client
|
||||
.database("admin")
|
||||
.run_command(mongodb::bson::doc! {"ping": 1}, None)
|
||||
.await?;
|
||||
|
||||
println!("✅ Successfully connected to MongoDB!");
|
||||
|
||||
// Get database handle using the database_name from config
|
||||
let database = client.database(&config.database_name);
|
||||
|
||||
Ok(MongoDb { database })
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user