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,
|
routing::get,
|
||||||
Router,
|
Router,
|
||||||
response::Json,
|
response::Json,
|
||||||
|
extract::State,
|
||||||
};
|
};
|
||||||
use serde_json::json;
|
use serde_json::json;
|
||||||
use tower_http::trace::TraceLayer;
|
use tower_http::trace::TraceLayer;
|
||||||
use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt};
|
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]
|
#[tokio::main]
|
||||||
async fn main() {
|
async fn main() -> anyhow::Result<()> {
|
||||||
|
// Load configuration
|
||||||
|
let config = Config::from_env()?;
|
||||||
|
|
||||||
|
// Initialize tracing
|
||||||
tracing_subscriber::registry()
|
tracing_subscriber::registry()
|
||||||
.with(
|
.with(
|
||||||
tracing_subscriber::EnvFilter::try_from_default_env()
|
tracing_subscriber::EnvFilter::try_from_default_env()
|
||||||
|
|
@ -18,22 +36,39 @@ async fn main() {
|
||||||
.init();
|
.init();
|
||||||
|
|
||||||
tracing::info!("Starting Normogen backend server");
|
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()
|
let app = Router::new()
|
||||||
.route("/health", get(health_check))
|
.route("/health", get(health_check))
|
||||||
.route("/ready", get(readiness_check))
|
.route("/ready", get(readiness_check))
|
||||||
|
.with_state(app_state)
|
||||||
.layer(TraceLayer::new_for_http());
|
.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);
|
tracing::info!("Listening on {}", addr);
|
||||||
|
|
||||||
let listener = tokio::net::TcpListener::bind(addr)
|
let listener = tokio::net::TcpListener::bind(&addr)
|
||||||
.await
|
.await
|
||||||
.expect("Failed to bind address");
|
.expect("Failed to bind address");
|
||||||
|
|
||||||
axum::serve(listener, app)
|
axum::serve(listener, app)
|
||||||
.await
|
.await
|
||||||
.expect("Server error");
|
.expect("Server error");
|
||||||
|
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn health_check() -> Json<serde_json::Value> {
|
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!({
|
Json(json!({
|
||||||
"status": "ready",
|
"status": "ready",
|
||||||
"database": "not_connected",
|
"database": db_status,
|
||||||
"timestamp": chrono::Utc::now().to_rfc3339(),
|
"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