feat: initialize rust backend with axum server, seaorm models, and project scaffolding
This commit is contained in:
17
.gitignore
vendored
Normal file
17
.gitignore
vendored
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
# BACKEND
|
||||||
|
target/
|
||||||
|
Cargo.lock
|
||||||
|
config.yaml
|
||||||
|
backend/data/zhealth.db
|
||||||
|
backend/data/zhealth.db-wal
|
||||||
|
backend/data/zhealth.db-shm
|
||||||
|
|
||||||
|
# FRONTEND
|
||||||
|
|
||||||
|
# AI
|
||||||
|
|
||||||
|
# OS
|
||||||
|
.DS_Store
|
||||||
|
|
||||||
|
# IDE
|
||||||
|
.vscode/
|
||||||
72
Makefile
Normal file
72
Makefile
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
# zhealth Makefile
|
||||||
|
# Run `make help` to see available commands
|
||||||
|
|
||||||
|
.PHONY: help dev build release lint typecheck test clean serve
|
||||||
|
|
||||||
|
# Default target
|
||||||
|
help:
|
||||||
|
@echo "Available commands:"
|
||||||
|
@echo " make dev - Start development servers"
|
||||||
|
@echo " make build - Build for development"
|
||||||
|
@echo " make release - Build optimized production bundle"
|
||||||
|
@echo " make lint - Run linters (Clippy + ESLint)"
|
||||||
|
@echo " make typecheck - Type checking (Rust + TypeScript)"
|
||||||
|
@echo " make test - Run all tests"
|
||||||
|
@echo " make serve - Serve production build locally"
|
||||||
|
@echo " make clean - Clean build artifacts"
|
||||||
|
|
||||||
|
# Backend commands
|
||||||
|
.PHONY: backend-dev backend-build backend-release backend-lint backend-test
|
||||||
|
|
||||||
|
backend-dev:
|
||||||
|
cd backend && cargo run
|
||||||
|
|
||||||
|
backend-build:
|
||||||
|
cd backend && cargo build
|
||||||
|
|
||||||
|
backend-release:
|
||||||
|
cd backend && cargo build --release
|
||||||
|
|
||||||
|
backend-lint:
|
||||||
|
cd backend && cargo clippy -- -D warnings
|
||||||
|
|
||||||
|
backend-test:
|
||||||
|
cd backend && cargo test
|
||||||
|
|
||||||
|
# Frontend commands (placeholder for when frontend is set up)
|
||||||
|
.PHONY: frontend-dev frontend-build frontend-release frontend-lint frontend-test
|
||||||
|
|
||||||
|
frontend-dev:
|
||||||
|
@echo "Frontend not yet configured"
|
||||||
|
|
||||||
|
frontend-build:
|
||||||
|
@echo "Frontend not yet configured"
|
||||||
|
|
||||||
|
frontend-release:
|
||||||
|
@echo "Frontend not yet configured"
|
||||||
|
|
||||||
|
frontend-lint:
|
||||||
|
@echo "Frontend not yet configured"
|
||||||
|
|
||||||
|
frontend-test:
|
||||||
|
@echo "Frontend not yet configured"
|
||||||
|
|
||||||
|
# Combined commands
|
||||||
|
dev: backend-dev
|
||||||
|
|
||||||
|
build: backend-build frontend-build
|
||||||
|
|
||||||
|
release: backend-release frontend-release
|
||||||
|
|
||||||
|
lint: backend-lint frontend-lint
|
||||||
|
|
||||||
|
typecheck: backend-lint frontend-lint
|
||||||
|
|
||||||
|
test: backend-test frontend-test
|
||||||
|
|
||||||
|
serve:
|
||||||
|
cd backend && cargo run --release
|
||||||
|
|
||||||
|
clean:
|
||||||
|
cd backend && cargo clean
|
||||||
|
@echo "Cleaned backend artifacts"
|
||||||
@@ -89,12 +89,17 @@ paths:
|
|||||||
database: "./data/zhealth.db"
|
database: "./data/zhealth.db"
|
||||||
logs: "./logs"
|
logs: "./logs"
|
||||||
|
|
||||||
|
logging:
|
||||||
|
level: "info" # trace | debug | info | warn | error
|
||||||
|
|
||||||
auth:
|
auth:
|
||||||
jwt_secret: "${JWT_SECRET}" # Loaded from env
|
session_secret: "${SESSION_SECRET}" # Loaded from env
|
||||||
token_expiry_hours: 24
|
session_expiry_hours: 24
|
||||||
|
cookie_name: "zhealth_session"
|
||||||
|
|
||||||
ai:
|
ai:
|
||||||
provider: "gemini" # gemini | openai | anthropic
|
provider: "gemini" # gemini | openai | anthropic
|
||||||
|
model: "gemini-3-flash-preview"
|
||||||
api_key: "${AI_API_KEY}"
|
api_key: "${AI_API_KEY}"
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -10,7 +10,7 @@
|
|||||||
## Phase 2: API & Core Auth
|
## Phase 2: API & Core Auth
|
||||||
|
|
||||||
- API Development with Axum.
|
- API Development with Axum.
|
||||||
- Implementation of Auth logic (JWT, Middleware, RBAC enforcement).
|
- Implementation of Auth logic (Session-based, Middleware, RBAC enforcement).
|
||||||
- Frontend Development (Vite + React + TS) - Initial Layout.
|
- Frontend Development (Vite + React + TS) - Initial Layout.
|
||||||
|
|
||||||
## Phase 3: Integration
|
## Phase 3: Integration
|
||||||
|
|||||||
43
backend/Cargo.toml
Normal file
43
backend/Cargo.toml
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
[package]
|
||||||
|
name = "zhealth-backend"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2024"
|
||||||
|
|
||||||
|
[[bin]]
|
||||||
|
name = "zhealth"
|
||||||
|
path = "src/main.rs"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
# Web Framework
|
||||||
|
axum = "0.8"
|
||||||
|
tokio = { version = "1", features = ["full"] }
|
||||||
|
tower = "0.5"
|
||||||
|
tower-http = { version = "0.6", features = ["cors", "trace"] }
|
||||||
|
|
||||||
|
# Database
|
||||||
|
sea-orm = { version = "1.1", features = ["sqlx-sqlite", "runtime-tokio-rustls", "macros"] }
|
||||||
|
|
||||||
|
# Serialization
|
||||||
|
serde = { version = "1", features = ["derive"] }
|
||||||
|
serde_yaml = "0.9"
|
||||||
|
|
||||||
|
# Auth
|
||||||
|
argon2 = "0.5"
|
||||||
|
rand = "0.8"
|
||||||
|
|
||||||
|
# Time
|
||||||
|
chrono = { version = "0.4", features = ["serde"] }
|
||||||
|
|
||||||
|
# Logging
|
||||||
|
tracing = "0.1"
|
||||||
|
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
|
||||||
|
|
||||||
|
# Error handling
|
||||||
|
thiserror = "2"
|
||||||
|
anyhow = "1"
|
||||||
|
|
||||||
|
# Regex for config env expansion
|
||||||
|
regex = "1"
|
||||||
|
|
||||||
|
# CLI
|
||||||
|
argh = "0.1"
|
||||||
25
backend/sample.config.yaml
Normal file
25
backend/sample.config.yaml
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
# Sample Configuration for zhealth-backend
|
||||||
|
# Copy this to config.yaml and update values as needed.
|
||||||
|
# Secrets should be loaded from environment variables using ${VAR_NAME} syntax.
|
||||||
|
|
||||||
|
server:
|
||||||
|
host: "127.0.0.1"
|
||||||
|
port: 3000
|
||||||
|
|
||||||
|
paths:
|
||||||
|
database: "./data/zhealth.db"
|
||||||
|
logs: "./logs"
|
||||||
|
|
||||||
|
logging:
|
||||||
|
level: "info" # Options: trace | debug | info | warn | error
|
||||||
|
|
||||||
|
auth:
|
||||||
|
session_secret: "${SESSION_SECRET}"
|
||||||
|
session_expiry_hours: 24
|
||||||
|
cookie_name: "zhealth_session"
|
||||||
|
cookie_secure: false # Set to true in production with HTTPS
|
||||||
|
|
||||||
|
ai:
|
||||||
|
provider: "gemini" # Options: gemini | openai | anthropic
|
||||||
|
model: "gemini-3-flash-preview"
|
||||||
|
api_key: "${AI_API_KEY}"
|
||||||
33
backend/src/cli.rs
Normal file
33
backend/src/cli.rs
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
//! CLI definition using argh.
|
||||||
|
|
||||||
|
use argh::FromArgs;
|
||||||
|
|
||||||
|
/// zhealth: AI-powered health management platform
|
||||||
|
#[derive(FromArgs)]
|
||||||
|
pub struct Args {
|
||||||
|
#[argh(subcommand)]
|
||||||
|
pub command: Command,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(FromArgs)]
|
||||||
|
#[argh(subcommand)]
|
||||||
|
pub enum Command {
|
||||||
|
Serve(ServeCommand),
|
||||||
|
Migrate(MigrateCommand),
|
||||||
|
Version(VersionCommand),
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Start the server (runs migrations first)
|
||||||
|
#[derive(FromArgs)]
|
||||||
|
#[argh(subcommand, name = "serve")]
|
||||||
|
pub struct ServeCommand {}
|
||||||
|
|
||||||
|
/// Run database migrations only
|
||||||
|
#[derive(FromArgs)]
|
||||||
|
#[argh(subcommand, name = "migrate")]
|
||||||
|
pub struct MigrateCommand {}
|
||||||
|
|
||||||
|
/// Show version information
|
||||||
|
#[derive(FromArgs)]
|
||||||
|
#[argh(subcommand, name = "version")]
|
||||||
|
pub struct VersionCommand {}
|
||||||
71
backend/src/config.rs
Normal file
71
backend/src/config.rs
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
//! Configuration loading and parsing.
|
||||||
|
|
||||||
|
use serde::Deserialize;
|
||||||
|
use std::fs;
|
||||||
|
use std::path::Path;
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize)]
|
||||||
|
pub struct Config {
|
||||||
|
pub server: ServerConfig,
|
||||||
|
pub paths: PathsConfig,
|
||||||
|
pub logging: LoggingConfig,
|
||||||
|
pub auth: AuthConfig,
|
||||||
|
pub ai: AiConfig,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize)]
|
||||||
|
pub struct ServerConfig {
|
||||||
|
pub host: String,
|
||||||
|
pub port: u16,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize)]
|
||||||
|
pub struct PathsConfig {
|
||||||
|
pub database: String,
|
||||||
|
pub logs: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize)]
|
||||||
|
pub struct LoggingConfig {
|
||||||
|
pub level: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize)]
|
||||||
|
pub struct AuthConfig {
|
||||||
|
pub session_secret: String,
|
||||||
|
pub session_expiry_hours: u32,
|
||||||
|
pub cookie_name: String,
|
||||||
|
pub cookie_secure: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize)]
|
||||||
|
pub struct AiConfig {
|
||||||
|
pub provider: String,
|
||||||
|
pub model: String,
|
||||||
|
pub api_key: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Config {
|
||||||
|
/// Load configuration from a YAML file.
|
||||||
|
pub fn load<P: AsRef<Path>>(path: P) -> anyhow::Result<Self> {
|
||||||
|
let content = fs::read_to_string(path)?;
|
||||||
|
// Expand environment variables in the format ${VAR_NAME}
|
||||||
|
let expanded = expand_env_vars(&content);
|
||||||
|
let config: Config = serde_yaml::from_str(&expanded)?;
|
||||||
|
Ok(config)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Expand environment variables in the format ${VAR_NAME}.
|
||||||
|
fn expand_env_vars(content: &str) -> String {
|
||||||
|
let mut result = content.to_string();
|
||||||
|
let re = regex::Regex::new(r"\$\{([^}]+)\}").unwrap();
|
||||||
|
|
||||||
|
for cap in re.captures_iter(content) {
|
||||||
|
let var_name = &cap[1];
|
||||||
|
let var_value = std::env::var(var_name).unwrap_or_default();
|
||||||
|
result = result.replace(&cap[0], &var_value);
|
||||||
|
}
|
||||||
|
|
||||||
|
result
|
||||||
|
}
|
||||||
42
backend/src/db.rs
Normal file
42
backend/src/db.rs
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
//! Database connection and migrations.
|
||||||
|
|
||||||
|
use sea_orm::{ConnectionTrait, Database, DatabaseConnection, DbBackend, DbErr, Schema, Statement};
|
||||||
|
use sea_orm::sea_query::SqliteQueryBuilder;
|
||||||
|
|
||||||
|
use crate::config::Config;
|
||||||
|
use crate::models::bio::{biomarker_entry, biomarker_type};
|
||||||
|
use crate::models::user::{session, user};
|
||||||
|
|
||||||
|
/// Connect to the SQLite database.
|
||||||
|
pub async fn connect(config: &Config) -> Result<DatabaseConnection, DbErr> {
|
||||||
|
let db_path = &config.paths.database;
|
||||||
|
|
||||||
|
// Ensure the data directory exists
|
||||||
|
if let Some(parent) = std::path::Path::new(db_path).parent() {
|
||||||
|
std::fs::create_dir_all(parent).ok();
|
||||||
|
}
|
||||||
|
|
||||||
|
let db_url = format!("sqlite:{}?mode=rwc", db_path);
|
||||||
|
Database::connect(&db_url).await
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Run migrations to create tables if they don't exist.
|
||||||
|
pub async fn run_migrations(db: &DatabaseConnection) -> Result<(), DbErr> {
|
||||||
|
let schema = Schema::new(DbBackend::Sqlite);
|
||||||
|
|
||||||
|
// Create table statements
|
||||||
|
let statements = vec![
|
||||||
|
schema.create_table_from_entity(user::Entity),
|
||||||
|
schema.create_table_from_entity(session::Entity),
|
||||||
|
schema.create_table_from_entity(biomarker_type::Entity),
|
||||||
|
schema.create_table_from_entity(biomarker_entry::Entity),
|
||||||
|
];
|
||||||
|
|
||||||
|
for mut stmt in statements {
|
||||||
|
let sql = stmt.if_not_exists().to_string(SqliteQueryBuilder);
|
||||||
|
db.execute(Statement::from_string(DbBackend::Sqlite, sql))
|
||||||
|
.await?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
75
backend/src/main.rs
Normal file
75
backend/src/main.rs
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
mod cli;
|
||||||
|
mod config;
|
||||||
|
mod db;
|
||||||
|
mod models;
|
||||||
|
|
||||||
|
use axum::{routing::get, Router};
|
||||||
|
use std::net::SocketAddr;
|
||||||
|
use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt};
|
||||||
|
|
||||||
|
use cli::{Args, Command};
|
||||||
|
|
||||||
|
const VERSION: &str = env!("CARGO_PKG_VERSION");
|
||||||
|
|
||||||
|
#[tokio::main]
|
||||||
|
async fn main() -> anyhow::Result<()> {
|
||||||
|
let args: Args = argh::from_env();
|
||||||
|
|
||||||
|
match args.command {
|
||||||
|
Command::Version(_) => {
|
||||||
|
println!("zhealth {}", VERSION);
|
||||||
|
}
|
||||||
|
Command::Migrate(_) => {
|
||||||
|
let config = config::Config::load("config.yaml")?;
|
||||||
|
init_logging(&config);
|
||||||
|
|
||||||
|
tracing::info!("Running migrations...");
|
||||||
|
let db = db::connect(&config).await?;
|
||||||
|
db::run_migrations(&db).await?;
|
||||||
|
tracing::info!("Migrations complete.");
|
||||||
|
}
|
||||||
|
Command::Serve(_) => {
|
||||||
|
let config = config::Config::load("config.yaml")?;
|
||||||
|
init_logging(&config);
|
||||||
|
|
||||||
|
// Run migrations first
|
||||||
|
tracing::info!("Running migrations...");
|
||||||
|
let db = db::connect(&config).await?;
|
||||||
|
db::run_migrations(&db).await?;
|
||||||
|
tracing::info!("Migrations complete.");
|
||||||
|
|
||||||
|
// Start server
|
||||||
|
tracing::info!("Starting zhealth-backend...");
|
||||||
|
let app = Router::new()
|
||||||
|
.route("/", get(root))
|
||||||
|
.route("/health", get(health_check));
|
||||||
|
|
||||||
|
let addr: SocketAddr = format!("{}:{}", config.server.host, config.server.port)
|
||||||
|
.parse()
|
||||||
|
.expect("Invalid server address");
|
||||||
|
|
||||||
|
tracing::info!("Listening on http://{}", addr);
|
||||||
|
|
||||||
|
let listener = tokio::net::TcpListener::bind(addr).await?;
|
||||||
|
axum::serve(listener, app).await?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn init_logging(config: &config::Config) {
|
||||||
|
let log_level = config.logging.level.parse().unwrap_or(tracing::Level::INFO);
|
||||||
|
tracing_subscriber::registry()
|
||||||
|
.with(tracing_subscriber::fmt::layer())
|
||||||
|
.with(tracing_subscriber::filter::LevelFilter::from_level(log_level))
|
||||||
|
.init();
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn root() -> &'static str {
|
||||||
|
"zhealth API"
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn health_check() -> &'static str {
|
||||||
|
"OK"
|
||||||
|
}
|
||||||
56
backend/src/models/bio/biomarker_entry.rs
Normal file
56
backend/src/models/bio/biomarker_entry.rs
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
//! BiomarkerEntry entity - user-logged biomarker values.
|
||||||
|
|
||||||
|
use sea_orm::entity::prelude::*;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Serialize, Deserialize)]
|
||||||
|
#[sea_orm(table_name = "biomarker_entries")]
|
||||||
|
pub struct Model {
|
||||||
|
#[sea_orm(primary_key)]
|
||||||
|
pub id: i32,
|
||||||
|
|
||||||
|
pub user_id: i32,
|
||||||
|
pub biomarker_type_id: i32,
|
||||||
|
|
||||||
|
/// The measured value
|
||||||
|
pub value: f64,
|
||||||
|
|
||||||
|
/// Date when the measurement was taken
|
||||||
|
pub measured_at: Date,
|
||||||
|
|
||||||
|
#[sea_orm(column_type = "Text", nullable)]
|
||||||
|
pub notes: Option<String>,
|
||||||
|
|
||||||
|
pub created_at: DateTime,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
|
||||||
|
pub enum Relation {
|
||||||
|
#[sea_orm(
|
||||||
|
belongs_to = "crate::models::user::user::Entity",
|
||||||
|
from = "Column::UserId",
|
||||||
|
to = "crate::models::user::user::Column::Id"
|
||||||
|
)]
|
||||||
|
User,
|
||||||
|
|
||||||
|
#[sea_orm(
|
||||||
|
belongs_to = "super::biomarker_type::Entity",
|
||||||
|
from = "Column::BiomarkerTypeId",
|
||||||
|
to = "super::biomarker_type::Column::Id"
|
||||||
|
)]
|
||||||
|
BiomarkerType,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Related<crate::models::user::user::Entity> for Entity {
|
||||||
|
fn to() -> RelationDef {
|
||||||
|
Relation::User.def()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Related<super::biomarker_type::Entity> for Entity {
|
||||||
|
fn to() -> RelationDef {
|
||||||
|
Relation::BiomarkerType.def()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ActiveModelBehavior for ActiveModel {}
|
||||||
43
backend/src/models/bio/biomarker_type.rs
Normal file
43
backend/src/models/bio/biomarker_type.rs
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
//! BiomarkerType entity - the knowledge base of available biomarkers.
|
||||||
|
|
||||||
|
use sea_orm::entity::prelude::*;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Serialize, Deserialize)]
|
||||||
|
#[sea_orm(table_name = "biomarker_types")]
|
||||||
|
pub struct Model {
|
||||||
|
#[sea_orm(primary_key)]
|
||||||
|
pub id: i32,
|
||||||
|
|
||||||
|
#[sea_orm(unique)]
|
||||||
|
pub name: String,
|
||||||
|
|
||||||
|
/// Category: lipid_panel, metabolic, vitamins, hormones, etc.
|
||||||
|
pub category: String,
|
||||||
|
|
||||||
|
/// Unit of measurement: mg/dL, mmol/L, ng/mL, etc.
|
||||||
|
pub unit: String,
|
||||||
|
|
||||||
|
/// Lower bound of normal reference range
|
||||||
|
pub reference_min: Option<f64>,
|
||||||
|
|
||||||
|
/// Upper bound of normal reference range
|
||||||
|
pub reference_max: Option<f64>,
|
||||||
|
|
||||||
|
#[sea_orm(column_type = "Text", nullable)]
|
||||||
|
pub description: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
|
||||||
|
pub enum Relation {
|
||||||
|
#[sea_orm(has_many = "super::biomarker_entry::Entity")]
|
||||||
|
Entries,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Related<super::biomarker_entry::Entity> for Entity {
|
||||||
|
fn to() -> RelationDef {
|
||||||
|
Relation::Entries.def()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ActiveModelBehavior for ActiveModel {}
|
||||||
7
backend/src/models/bio/mod.rs
Normal file
7
backend/src/models/bio/mod.rs
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
//! Biomarker entities for health data tracking.
|
||||||
|
|
||||||
|
pub mod biomarker_entry;
|
||||||
|
pub mod biomarker_type;
|
||||||
|
|
||||||
|
pub use biomarker_entry::Entity as BiomarkerEntry;
|
||||||
|
pub use biomarker_type::Entity as BiomarkerType;
|
||||||
7
backend/src/models/mod.rs
Normal file
7
backend/src/models/mod.rs
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
//! Database models for zhealth.
|
||||||
|
|
||||||
|
pub mod bio;
|
||||||
|
pub mod user;
|
||||||
|
|
||||||
|
pub use bio::{BiomarkerEntry, BiomarkerType};
|
||||||
|
pub use user::{Session, User};
|
||||||
7
backend/src/models/user/mod.rs
Normal file
7
backend/src/models/user/mod.rs
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
//! User and session entities for authentication.
|
||||||
|
|
||||||
|
pub mod session;
|
||||||
|
pub mod user;
|
||||||
|
|
||||||
|
pub use session::Entity as Session;
|
||||||
|
pub use user::Entity as User;
|
||||||
33
backend/src/models/user/session.rs
Normal file
33
backend/src/models/user/session.rs
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
//! Session entity for cookie-based authentication.
|
||||||
|
|
||||||
|
use sea_orm::entity::prelude::*;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel, Serialize, Deserialize)]
|
||||||
|
#[sea_orm(table_name = "sessions")]
|
||||||
|
pub struct Model {
|
||||||
|
#[sea_orm(primary_key, auto_increment = false)]
|
||||||
|
pub id: String,
|
||||||
|
|
||||||
|
pub user_id: i32,
|
||||||
|
pub expires_at: DateTime,
|
||||||
|
pub created_at: DateTime,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
|
||||||
|
pub enum Relation {
|
||||||
|
#[sea_orm(
|
||||||
|
belongs_to = "super::user::Entity",
|
||||||
|
from = "Column::UserId",
|
||||||
|
to = "super::user::Column::Id"
|
||||||
|
)]
|
||||||
|
User,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Related<super::user::Entity> for Entity {
|
||||||
|
fn to() -> RelationDef {
|
||||||
|
Relation::User.def()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ActiveModelBehavior for ActiveModel {}
|
||||||
37
backend/src/models/user/user.rs
Normal file
37
backend/src/models/user/user.rs
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
//! User entity for authentication.
|
||||||
|
|
||||||
|
use sea_orm::entity::prelude::*;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel, Serialize, Deserialize)]
|
||||||
|
#[sea_orm(table_name = "users")]
|
||||||
|
pub struct Model {
|
||||||
|
#[sea_orm(primary_key)]
|
||||||
|
pub id: i32,
|
||||||
|
|
||||||
|
#[sea_orm(unique)]
|
||||||
|
pub username: String,
|
||||||
|
|
||||||
|
#[sea_orm(column_type = "Text")]
|
||||||
|
pub password_hash: String,
|
||||||
|
|
||||||
|
#[sea_orm(unique)]
|
||||||
|
pub email: String,
|
||||||
|
|
||||||
|
pub created_at: DateTime,
|
||||||
|
pub updated_at: DateTime,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
|
||||||
|
pub enum Relation {
|
||||||
|
#[sea_orm(has_many = "super::session::Entity")]
|
||||||
|
Sessions,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Related<super::session::Entity> for Entity {
|
||||||
|
fn to() -> RelationDef {
|
||||||
|
Relation::Sessions.def()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ActiveModelBehavior for ActiveModel {}
|
||||||
Reference in New Issue
Block a user