Phase 2.2: MongoDB connection and models
This commit is contained in:
parent
1cf927f527
commit
154c3d1152
13 changed files with 544 additions and 5 deletions
108
backend/src/config/mod.rs
Normal file
108
backend/src/config/mod.rs
Normal file
|
|
@ -0,0 +1,108 @@
|
|||
use serde::{Deserialize, Serialize};
|
||||
use std::time::Duration;
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct Config {
|
||||
pub server: ServerConfig,
|
||||
pub database: DatabaseConfig,
|
||||
pub jwt: JwtConfig,
|
||||
pub encryption: EncryptionConfig,
|
||||
pub cors: CorsConfig,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct ServerConfig {
|
||||
pub host: String,
|
||||
pub port: u16,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct DatabaseConfig {
|
||||
pub uri: String,
|
||||
pub database: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct JwtConfig {
|
||||
pub secret: String,
|
||||
pub access_token_expiry_minutes: i64,
|
||||
pub refresh_token_expiry_days: i64,
|
||||
}
|
||||
|
||||
impl JwtConfig {
|
||||
pub fn access_token_expiry_duration(&self) -> Duration {
|
||||
Duration::from_secs(self.access_token_expiry_minutes as u64 * 60)
|
||||
}
|
||||
|
||||
pub fn refresh_token_expiry_duration(&self) -> Duration {
|
||||
Duration::from_secs(self.refresh_token_expiry_days as u64 * 86400)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct EncryptionConfig {
|
||||
pub algorithm: String,
|
||||
pub key_length: usize,
|
||||
pub pbkdf2_iterations: u32,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct CorsConfig {
|
||||
pub allowed_origins: Vec<String>,
|
||||
}
|
||||
|
||||
impl Config {
|
||||
pub fn from_env() -> anyhow::Result<Self> {
|
||||
dotenv::dotenv().ok();
|
||||
|
||||
let server_host = std::env::var("SERVER_HOST")
|
||||
.unwrap_or_else(|_| "0.0.0.0".to_string());
|
||||
let server_port = std::env::var("SERVER_PORT")
|
||||
.unwrap_or_else(|_| "8000".to_string())
|
||||
.parse::<u16>()?;
|
||||
|
||||
let mongodb_uri = std::env::var("MONGODB_URI")
|
||||
.unwrap_or_else(|_| "mongodb://localhost:27017".to_string());
|
||||
let mongodb_database = std::env::var("MONGODB_DATABASE")
|
||||
.unwrap_or_else(|_| "normogen".to_string());
|
||||
|
||||
let jwt_secret = std::env::var("JWT_SECRET")
|
||||
.expect("JWT_SECRET must be set");
|
||||
let access_token_expiry = std::env::var("JWT_ACCESS_TOKEN_EXPIRY_MINUTES")
|
||||
.unwrap_or_else(|_| "15".to_string())
|
||||
.parse::<i64>()?;
|
||||
let refresh_token_expiry = std::env::var("JWT_REFRESH_TOKEN_EXPIRY_DAYS")
|
||||
.unwrap_or_else(|_| "30".to_string())
|
||||
.parse::<i64>()?;
|
||||
|
||||
let cors_origins = std::env::var("CORS_ALLOWED_ORIGINS")
|
||||
.unwrap_or_else(|_| "http://localhost:3000,http://localhost:6001".to_string())
|
||||
.split(',')
|
||||
.map(|s| s.trim().to_string())
|
||||
.collect();
|
||||
|
||||
Ok(Config {
|
||||
server: ServerConfig {
|
||||
host: server_host,
|
||||
port: server_port,
|
||||
},
|
||||
database: DatabaseConfig {
|
||||
uri: mongodb_uri,
|
||||
database: mongodb_database,
|
||||
},
|
||||
jwt: JwtConfig {
|
||||
secret: jwt_secret,
|
||||
access_token_expiry_minutes: access_token_expiry,
|
||||
refresh_token_expiry_days: refresh_token_expiry,
|
||||
},
|
||||
encryption: EncryptionConfig {
|
||||
algorithm: "aes-256-gcm".to_string(),
|
||||
key_length: 32,
|
||||
pbkdf2_iterations: 100000,
|
||||
},
|
||||
cors: CorsConfig {
|
||||
allowed_origins: cors_origins,
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
44
backend/src/db/mod.rs
Normal file
44
backend/src/db/mod.rs
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
use mongodb::{Client, Database, options::{ClientOptions, ServerApi, ServerApiVersion}};
|
||||
use anyhow::Result;
|
||||
|
||||
pub struct MongoDb {
|
||||
pub client: Client,
|
||||
pub database: Database,
|
||||
}
|
||||
|
||||
impl MongoDb {
|
||||
pub async fn new(uri: &str, database_name: &str) -> Result<Self> {
|
||||
let mut client_options = ClientOptions::parse(uri).await?;
|
||||
|
||||
let server_api = ServerApi::builder()
|
||||
.version(ServerApiVersion::V1)
|
||||
.build();
|
||||
client_options.server_api = Some(server_api);
|
||||
|
||||
let client = Client::with_options(client_options)?;
|
||||
let database = client.database(database_name);
|
||||
|
||||
Ok(MongoDb { client, database })
|
||||
}
|
||||
|
||||
pub fn get_database(&self) -> Database {
|
||||
self.database.clone()
|
||||
}
|
||||
|
||||
pub fn collection<T>(&self, name: &str) -> mongodb::Collection<T> {
|
||||
self.database.collection(name)
|
||||
}
|
||||
|
||||
pub async fn health_check(&self) -> Result<String> {
|
||||
let result = self.client
|
||||
.database("admin")
|
||||
.run_command(mongodb::bson::doc! { "ping": 1 }, None)
|
||||
.await?;
|
||||
|
||||
if result.get_i32("ok").unwrap_or(0) == 1 {
|
||||
Ok("connected".to_string())
|
||||
} else {
|
||||
Ok("error".to_string())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -2,13 +2,31 @@ use axum::{
|
|||
routing::get,
|
||||
Router,
|
||||
response::Json,
|
||||
extract::State,
|
||||
};
|
||||
use serde_json::json;
|
||||
use tower_http::trace::TraceLayer;
|
||||
use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt};
|
||||
use std::sync::Arc;
|
||||
|
||||
mod config;
|
||||
mod db;
|
||||
mod models;
|
||||
|
||||
use config::Config;
|
||||
use db::MongoDb;
|
||||
|
||||
#[derive(Clone)]
|
||||
struct AppState {
|
||||
db: Arc<MongoDb>,
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
async fn main() -> anyhow::Result<()> {
|
||||
// Load configuration
|
||||
let config = Config::from_env()?;
|
||||
|
||||
// Initialize tracing
|
||||
tracing_subscriber::registry()
|
||||
.with(
|
||||
tracing_subscriber::EnvFilter::try_from_default_env()
|
||||
|
|
@ -18,22 +36,39 @@ async fn main() {
|
|||
.init();
|
||||
|
||||
tracing::info!("Starting Normogen backend server");
|
||||
tracing::info!("MongoDB URI: {}", config.database.uri);
|
||||
tracing::info!("Database: {}", config.database.database);
|
||||
|
||||
// Connect to MongoDB
|
||||
let mongodb = MongoDb::new(&config.database.uri, &config.database.database).await?;
|
||||
tracing::info!("Connected to MongoDB");
|
||||
|
||||
// Health check
|
||||
let health_status = mongodb.health_check().await?;
|
||||
tracing::info!("MongoDB health: {}", health_status);
|
||||
|
||||
let app_state = AppState {
|
||||
db: Arc::new(mongodb),
|
||||
};
|
||||
|
||||
let app = Router::new()
|
||||
.route("/health", get(health_check))
|
||||
.route("/ready", get(readiness_check))
|
||||
.with_state(app_state)
|
||||
.layer(TraceLayer::new_for_http());
|
||||
|
||||
let addr = std::net::SocketAddr::from(([0, 0, 0, 0], 8000));
|
||||
let addr = format!("{}:{}", config.server.host, config.server.port);
|
||||
tracing::info!("Listening on {}", addr);
|
||||
|
||||
let listener = tokio::net::TcpListener::bind(addr)
|
||||
let listener = tokio::net::TcpListener::bind(&addr)
|
||||
.await
|
||||
.expect("Failed to bind address");
|
||||
|
||||
axum::serve(listener, app)
|
||||
.await
|
||||
.expect("Server error");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn health_check() -> Json<serde_json::Value> {
|
||||
|
|
@ -43,10 +78,12 @@ async fn health_check() -> Json<serde_json::Value> {
|
|||
}))
|
||||
}
|
||||
|
||||
async fn readiness_check() -> Json<serde_json::Value> {
|
||||
async fn readiness_check(State(state): State<AppState>) -> Json<serde_json::Value> {
|
||||
let db_status = state.db.health_check().await.unwrap_or_else(|_| "disconnected".to_string());
|
||||
|
||||
Json(json!({
|
||||
"status": "ready",
|
||||
"database": "not_connected",
|
||||
"database": db_status,
|
||||
"timestamp": chrono::Utc::now().to_rfc3339(),
|
||||
}))
|
||||
}
|
||||
|
|
|
|||
31
backend/src/models/appointment.rs
Normal file
31
backend/src/models/appointment.rs
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
use serde::{Deserialize, Serialize};
|
||||
use mongodb::bson::{oid::ObjectId, DateTime};
|
||||
use super::health_data::EncryptedField;
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct Appointment {
|
||||
#[serde(rename = "_id", skip_serializing_if = "Option::is_none")]
|
||||
pub id: Option<ObjectId>,
|
||||
#[serde(rename = "appointmentId")]
|
||||
pub appointment_id: String,
|
||||
#[serde(rename = "userId")]
|
||||
pub user_id: String,
|
||||
#[serde(rename = "profileId")]
|
||||
pub profile_id: String,
|
||||
#[serde(rename = "appointmentData")]
|
||||
pub appointment_data: EncryptedField,
|
||||
#[serde(rename = "reminders")]
|
||||
pub reminders: Vec<AppointmentReminder>,
|
||||
#[serde(rename = "createdAt")]
|
||||
pub created_at: DateTime,
|
||||
#[serde(rename = "updatedAt")]
|
||||
pub updated_at: DateTime,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct AppointmentReminder {
|
||||
#[serde(rename = "reminderId")]
|
||||
pub reminder_id: String,
|
||||
#[serde(rename = "scheduledTime")]
|
||||
pub scheduled_time: String,
|
||||
}
|
||||
43
backend/src/models/family.rs
Normal file
43
backend/src/models/family.rs
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
use serde::{Deserialize, Serialize};
|
||||
use mongodb::{bson::{doc, oid::ObjectId, DateTime}, Collection};
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct Family {
|
||||
#[serde(rename = "_id", skip_serializing_if = "Option::is_none")]
|
||||
pub id: Option<ObjectId>,
|
||||
#[serde(rename = "familyId")]
|
||||
pub family_id: String,
|
||||
#[serde(rename = "name")]
|
||||
pub name: String,
|
||||
#[serde(rename = "nameIv")]
|
||||
pub name_iv: String,
|
||||
#[serde(rename = "nameAuthTag")]
|
||||
pub name_auth_tag: String,
|
||||
#[serde(rename = "memberIds")]
|
||||
pub member_ids: Vec<String>,
|
||||
#[serde(rename = "createdAt")]
|
||||
pub created_at: DateTime,
|
||||
#[serde(rename = "updatedAt")]
|
||||
pub updated_at: DateTime,
|
||||
}
|
||||
|
||||
pub struct FamilyRepository {
|
||||
collection: Collection<Family>,
|
||||
}
|
||||
|
||||
impl FamilyRepository {
|
||||
pub fn new(collection: Collection<Family>) -> Self {
|
||||
Self { collection }
|
||||
}
|
||||
|
||||
pub async fn create(&self, family: &Family) -> mongodb::error::Result<()> {
|
||||
self.collection.insert_one(family, None).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn find_by_family_id(&self, family_id: &str) -> mongodb::error::Result<Option<Family>> {
|
||||
self.collection
|
||||
.find_one(doc! { "familyId": family_id }, None)
|
||||
.await
|
||||
}
|
||||
}
|
||||
32
backend/src/models/health_data.rs
Normal file
32
backend/src/models/health_data.rs
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
use serde::{Deserialize, Serialize};
|
||||
use mongodb::bson::{oid::ObjectId, DateTime};
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct HealthData {
|
||||
#[serde(rename = "_id", skip_serializing_if = "Option::is_none")]
|
||||
pub id: Option<ObjectId>,
|
||||
#[serde(rename = "healthDataId")]
|
||||
pub health_data_id: String,
|
||||
#[serde(rename = "userId")]
|
||||
pub user_id: String,
|
||||
#[serde(rename = "profileId")]
|
||||
pub profile_id: String,
|
||||
#[serde(rename = "familyId")]
|
||||
pub family_id: Option<String>,
|
||||
#[serde(rename = "healthData")]
|
||||
pub health_data: Vec<EncryptedField>,
|
||||
#[serde(rename = "createdAt")]
|
||||
pub created_at: DateTime,
|
||||
#[serde(rename = "updatedAt")]
|
||||
pub updated_at: DateTime,
|
||||
#[serde(rename = "dataSource")]
|
||||
pub data_source: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct EncryptedField {
|
||||
pub encrypted: bool,
|
||||
pub data: String,
|
||||
pub iv: String,
|
||||
pub auth_tag: String,
|
||||
}
|
||||
21
backend/src/models/lab_result.rs
Normal file
21
backend/src/models/lab_result.rs
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
use serde::{Deserialize, Serialize};
|
||||
use mongodb::bson::{oid::ObjectId, DateTime};
|
||||
use super::health_data::EncryptedField;
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct LabResult {
|
||||
#[serde(rename = "_id", skip_serializing_if = "Option::is_none")]
|
||||
pub id: Option<ObjectId>,
|
||||
#[serde(rename = "labResultId")]
|
||||
pub lab_result_id: String,
|
||||
#[serde(rename = "userId")]
|
||||
pub user_id: String,
|
||||
#[serde(rename = "profileId")]
|
||||
pub profile_id: String,
|
||||
#[serde(rename = "labData")]
|
||||
pub lab_data: EncryptedField,
|
||||
#[serde(rename = "createdAt")]
|
||||
pub created_at: DateTime,
|
||||
#[serde(rename = "updatedAt")]
|
||||
pub updated_at: DateTime,
|
||||
}
|
||||
31
backend/src/models/medication.rs
Normal file
31
backend/src/models/medication.rs
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
use serde::{Deserialize, Serialize};
|
||||
use mongodb::bson::{oid::ObjectId, DateTime};
|
||||
use super::health_data::EncryptedField;
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct Medication {
|
||||
#[serde(rename = "_id", skip_serializing_if = "Option::is_none")]
|
||||
pub id: Option<ObjectId>,
|
||||
#[serde(rename = "medicationId")]
|
||||
pub medication_id: String,
|
||||
#[serde(rename = "userId")]
|
||||
pub user_id: String,
|
||||
#[serde(rename = "profileId")]
|
||||
pub profile_id: String,
|
||||
#[serde(rename = "medicationData")]
|
||||
pub medication_data: EncryptedField,
|
||||
#[serde(rename = "reminders")]
|
||||
pub reminders: Vec<MedicationReminder>,
|
||||
#[serde(rename = "createdAt")]
|
||||
pub created_at: DateTime,
|
||||
#[serde(rename = "updatedAt")]
|
||||
pub updated_at: DateTime,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct MedicationReminder {
|
||||
#[serde(rename = "reminderId")]
|
||||
pub reminder_id: String,
|
||||
#[serde(rename = "scheduledTime")]
|
||||
pub scheduled_time: String,
|
||||
}
|
||||
9
backend/src/models/mod.rs
Normal file
9
backend/src/models/mod.rs
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
pub mod user;
|
||||
pub mod family;
|
||||
pub mod profile;
|
||||
pub mod health_data;
|
||||
pub mod lab_result;
|
||||
pub mod medication;
|
||||
pub mod appointment;
|
||||
pub mod share;
|
||||
pub mod refresh_token;
|
||||
49
backend/src/models/profile.rs
Normal file
49
backend/src/models/profile.rs
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
use serde::{Deserialize, Serialize};
|
||||
use mongodb::{bson::{doc, oid::ObjectId, DateTime}, Collection};
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct Profile {
|
||||
#[serde(rename = "_id", skip_serializing_if = "Option::is_none")]
|
||||
pub id: Option<ObjectId>,
|
||||
#[serde(rename = "profileId")]
|
||||
pub profile_id: String,
|
||||
#[serde(rename = "userId")]
|
||||
pub user_id: String,
|
||||
#[serde(rename = "familyId")]
|
||||
pub family_id: Option<String>,
|
||||
#[serde(rename = "name")]
|
||||
pub name: String,
|
||||
#[serde(rename = "nameIv")]
|
||||
pub name_iv: String,
|
||||
#[serde(rename = "nameAuthTag")]
|
||||
pub name_auth_tag: String,
|
||||
#[serde(rename = "role")]
|
||||
pub role: String,
|
||||
#[serde(rename = "permissions")]
|
||||
pub permissions: Vec<String>,
|
||||
#[serde(rename = "createdAt")]
|
||||
pub created_at: DateTime,
|
||||
#[serde(rename = "updatedAt")]
|
||||
pub updated_at: DateTime,
|
||||
}
|
||||
|
||||
pub struct ProfileRepository {
|
||||
collection: Collection<Profile>,
|
||||
}
|
||||
|
||||
impl ProfileRepository {
|
||||
pub fn new(collection: Collection<Profile>) -> Self {
|
||||
Self { collection }
|
||||
}
|
||||
|
||||
pub async fn create(&self, profile: &Profile) -> mongodb::error::Result<()> {
|
||||
self.collection.insert_one(profile, None).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn find_by_profile_id(&self, profile_id: &str) -> mongodb::error::Result<Option<Profile>> {
|
||||
self.collection
|
||||
.find_one(doc! { "profileId": profile_id }, None)
|
||||
.await
|
||||
}
|
||||
}
|
||||
22
backend/src/models/refresh_token.rs
Normal file
22
backend/src/models/refresh_token.rs
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
use serde::{Deserialize, Serialize};
|
||||
use mongodb::bson::{oid::ObjectId, DateTime};
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct RefreshToken {
|
||||
#[serde(rename = "_id", skip_serializing_if = "Option::is_none")]
|
||||
pub id: Option<ObjectId>,
|
||||
#[serde(rename = "tokenId")]
|
||||
pub token_id: String,
|
||||
#[serde(rename = "userId")]
|
||||
pub user_id: String,
|
||||
#[serde(rename = "tokenHash")]
|
||||
pub token_hash: String,
|
||||
#[serde(rename = "expiresAt")]
|
||||
pub expires_at: DateTime,
|
||||
#[serde(rename = "createdAt")]
|
||||
pub created_at: DateTime,
|
||||
#[serde(rename = "revoked")]
|
||||
pub revoked: bool,
|
||||
#[serde(rename = "revokedAt")]
|
||||
pub revoked_at: Option<DateTime>,
|
||||
}
|
||||
24
backend/src/models/share.rs
Normal file
24
backend/src/models/share.rs
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
use serde::{Deserialize, Serialize};
|
||||
use mongodb::bson::{oid::ObjectId, DateTime};
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct Share {
|
||||
#[serde(rename = "_id", skip_serializing_if = "Option::is_none")]
|
||||
pub id: Option<ObjectId>,
|
||||
#[serde(rename = "shareId")]
|
||||
pub share_id: String,
|
||||
#[serde(rename = "userId")]
|
||||
pub user_id: String,
|
||||
#[serde(rename = "encryptedDataKey")]
|
||||
pub encrypted_data_key: String,
|
||||
#[serde(rename = "dataKeyIv")]
|
||||
pub data_key_iv: String,
|
||||
#[serde(rename = "dataKeyAuthTag")]
|
||||
pub data_key_auth_tag: String,
|
||||
#[serde(rename = "expiresAt")]
|
||||
pub expires_at: DateTime,
|
||||
#[serde(rename = "createdAt")]
|
||||
pub created_at: DateTime,
|
||||
#[serde(rename = "accessCount")]
|
||||
pub access_count: i32,
|
||||
}
|
||||
88
backend/src/models/user.rs
Normal file
88
backend/src/models/user.rs
Normal file
|
|
@ -0,0 +1,88 @@
|
|||
use serde::{Deserialize, Serialize};
|
||||
use mongodb::{bson::{doc, oid::ObjectId, DateTime}, Collection};
|
||||
use chrono::{Utc, TimeZone};
|
||||
use validator::Validate;
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct User {
|
||||
#[serde(rename = "_id", skip_serializing_if = "Option::is_none")]
|
||||
pub id: Option<ObjectId>,
|
||||
#[serde(rename = "userId")]
|
||||
pub user_id: String,
|
||||
#[serde(rename = "email")]
|
||||
pub email: String,
|
||||
#[serde(rename = "passwordHash")]
|
||||
pub password_hash: String,
|
||||
#[serde(rename = "encryptedRecoveryPhrase")]
|
||||
pub encrypted_recovery_phrase: String,
|
||||
#[serde(rename = "recoveryPhraseIv")]
|
||||
pub recovery_phrase_iv: String,
|
||||
#[serde(rename = "recoveryPhraseAuthTag")]
|
||||
pub recovery_phrase_auth_tag: String,
|
||||
#[serde(rename = "tokenVersion")]
|
||||
pub token_version: i32,
|
||||
#[serde(rename = "familyId")]
|
||||
pub family_id: Option<String>,
|
||||
#[serde(rename = "profileIds")]
|
||||
pub profile_ids: Vec<String>,
|
||||
#[serde(rename = "createdAt")]
|
||||
pub created_at: DateTime,
|
||||
#[serde(rename = "updatedAt")]
|
||||
pub updated_at: DateTime,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Validate)]
|
||||
pub struct RegisterUserRequest {
|
||||
#[validate(email)]
|
||||
pub email: String,
|
||||
pub password_hash: String,
|
||||
pub encrypted_recovery_phrase: String,
|
||||
pub recovery_phrase_iv: String,
|
||||
pub recovery_phrase_auth_tag: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Validate)]
|
||||
pub struct LoginRequest {
|
||||
#[validate(email)]
|
||||
pub email: String,
|
||||
pub password_hash: String,
|
||||
}
|
||||
|
||||
pub struct UserRepository {
|
||||
collection: Collection<User>,
|
||||
}
|
||||
|
||||
impl UserRepository {
|
||||
pub fn new(collection: Collection<User>) -> Self {
|
||||
Self { collection }
|
||||
}
|
||||
|
||||
pub async fn create(&self, user: &User) -> mongodb::error::Result<()> {
|
||||
self.collection.insert_one(user, None).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn find_by_email(&self, email: &str) -> mongodb::error::Result<Option<User>> {
|
||||
self.collection
|
||||
.find_one(doc! { "email": email }, None)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn find_by_user_id(&self, user_id: &str) -> mongodb::error::Result<Option<User>> {
|
||||
self.collection
|
||||
.find_one(doc! { "userId": user_id }, None)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn update_token_version(&self, user_id: &str, version: i32) -> mongodb::error::Result<()> {
|
||||
let now = DateTime::now();
|
||||
self.collection
|
||||
.update_one(
|
||||
doc! { "userId": user_id },
|
||||
doc! { "$set": { "tokenVersion": version, "updatedAt": now } },
|
||||
None,
|
||||
)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue