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
|
RUST_LOG=info
|
||||||
BIND_ADDRESS=127.0.0.1:3000
|
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]
|
[dependencies]
|
||||||
axum = "0.8.4"
|
axum = "0.8.4"
|
||||||
|
bson = { version = "2.9.0", features = ["chrono-0_4"] }
|
||||||
|
chrono = { version = "0.4", features = ["serde"] }
|
||||||
dotenvy = "0.15.7"
|
dotenvy = "0.15.7"
|
||||||
|
mongodb = "2.8.2"
|
||||||
serde = { version = "1.0.219", features = ["derive"] }
|
serde = { version = "1.0.219", features = ["derive"] }
|
||||||
serde_json = "1.0.142"
|
serde_json = "1.0.142"
|
||||||
sqlx = { version = "0.8.6", features = ["runtime-tokio", "tls-native-tls"] }
|
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
|
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`:
|
For development, you can run the project directly with `cargo run`:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
cargo run
|
cargo run
|
||||||
```
|
```
|
||||||
|
|
||||||
For a release build, run:
|
For a release build, run:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
cargo run --release
|
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::net::SocketAddr;
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[cfg(feature = "no-auth")]
|
||||||
|
use tracing::error;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
pub struct Config {
|
pub struct Config {
|
||||||
pub bind_address: SocketAddr,
|
pub bind_address: SocketAddr,
|
||||||
// pub database_url: Option<String>,
|
pub mongodb_uri: String,
|
||||||
|
pub database_name: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Config {
|
impl Config {
|
||||||
@@ -24,11 +28,15 @@ impl Config {
|
|||||||
return Err("In no-auth mode, BIND_ADDRESS must be 127.0.0.1".to_string());
|
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 {
|
Ok(Self {
|
||||||
bind_address,
|
bind_address,
|
||||||
// database_url,
|
mongodb_uri,
|
||||||
|
database_name,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
26
src/main.rs
26
src/main.rs
@@ -1,7 +1,9 @@
|
|||||||
// src/main.rs
|
// src/main.rs
|
||||||
|
|
||||||
use std::process::exit;
|
use std::process::exit;
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use ::mongodb::Database;
|
||||||
use axum::Router;
|
use axum::Router;
|
||||||
use dotenvy::dotenv;
|
use dotenvy::dotenv;
|
||||||
use tokio::signal;
|
use tokio::signal;
|
||||||
@@ -11,9 +13,17 @@ use tracing_subscriber::{EnvFilter, fmt, prelude::*};
|
|||||||
|
|
||||||
mod config;
|
mod config;
|
||||||
mod handlers;
|
mod handlers;
|
||||||
|
mod mongodb;
|
||||||
mod routes;
|
mod routes;
|
||||||
|
|
||||||
use config::Config;
|
use config::Config;
|
||||||
|
use mongodb::MongoDb;
|
||||||
|
|
||||||
|
// Shared application state
|
||||||
|
pub struct AppState {
|
||||||
|
pub db: Database,
|
||||||
|
pub config: Config,
|
||||||
|
}
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn 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")]
|
#[cfg(feature = "no-auth")]
|
||||||
info!("NO-AUTH MODE ENABLED");
|
info!("NO-AUTH MODE ENABLED");
|
||||||
|
|
||||||
@@ -42,7 +67,6 @@ async fn main() {
|
|||||||
|
|
||||||
// Build the Axum router
|
// Build the Axum router
|
||||||
let app = Router::new()
|
let app = Router::new()
|
||||||
// .nest("/health", routes::health::health::health_routes())
|
|
||||||
.nest("/health", routes::health::health::health_routes())
|
.nest("/health", routes::health::health::health_routes())
|
||||||
.nest("/user", routes::user::user::user_routes())
|
.nest("/user", routes::user::user::user_routes())
|
||||||
.layer(TraceLayer::new_for_http());
|
.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