diff --git a/Makefile b/Makefile index 00deac9..2f275a9 100644 --- a/Makefile +++ b/Makefile @@ -9,6 +9,7 @@ help: @echo " make dev - Start development servers" @echo " make build - Build for development" @echo " make release - Build optimized production bundle" + @echo " make migrate - Run database migrations" @echo " make lint - Run linters (Clippy + ESLint)" @echo " make typecheck - Type checking (Rust + TypeScript)" @echo " make test - Run all tests" @@ -16,10 +17,13 @@ help: @echo " make clean - Clean build artifacts" # Backend commands -.PHONY: backend-dev backend-build backend-release backend-lint backend-test +.PHONY: backend-dev backend-build backend-release backend-lint backend-test migrate + +migrate: + cd backend && ./target/debug/zhealth migrate backend-dev: - cd backend && cargo run + cd backend && ./target/debug/zhealth serve backend-build: cd backend && cargo build diff --git a/backend/src/db.rs b/backend/src/db.rs index 4d6c629..a6ff3e3 100644 --- a/backend/src/db.rs +++ b/backend/src/db.rs @@ -5,7 +5,7 @@ use sea_orm::sea_query::SqliteQueryBuilder; use crate::config::Config; use crate::models::bio::{biomarker_entry, biomarker_type}; -use crate::models::user::{session, user}; +use crate::models::user::{role, session, user}; /// Connect to the SQLite database. pub async fn connect(config: &Config) -> Result { @@ -24,9 +24,10 @@ pub async fn connect(config: &Config) -> Result { pub async fn run_migrations(db: &DatabaseConnection) -> Result<(), DbErr> { let schema = Schema::new(DbBackend::Sqlite); - // Create table statements + // Create table statements (order matters for foreign keys) let statements = vec![ - schema.create_table_from_entity(user::Entity), + schema.create_table_from_entity(role::Entity), // roles first + schema.create_table_from_entity(user::Entity), // users references roles schema.create_table_from_entity(session::Entity), schema.create_table_from_entity(biomarker_type::Entity), schema.create_table_from_entity(biomarker_entry::Entity), diff --git a/backend/src/models/user/mod.rs b/backend/src/models/user/mod.rs index 3b224a7..03ed789 100644 --- a/backend/src/models/user/mod.rs +++ b/backend/src/models/user/mod.rs @@ -1,7 +1,9 @@ //! User and session entities for authentication. +pub mod role; pub mod session; pub mod user; +pub use role::Entity as Role; pub use session::Entity as Session; pub use user::Entity as User; diff --git a/backend/src/models/user/role.rs b/backend/src/models/user/role.rs new file mode 100644 index 0000000..cb66e77 --- /dev/null +++ b/backend/src/models/user/role.rs @@ -0,0 +1,33 @@ +//! Role entity for RBAC. + +use sea_orm::entity::prelude::*; +use serde::{Deserialize, Serialize}; + +#[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel, Serialize, Deserialize)] +#[sea_orm(table_name = "roles")] +pub struct Model { + #[sea_orm(primary_key)] + pub id: i32, + + /// Role name: admin, user, reader + #[sea_orm(unique)] + pub name: String, + + /// Human-readable description + #[sea_orm(column_type = "Text", nullable)] + pub description: Option, +} + +#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] +pub enum Relation { + #[sea_orm(has_many = "super::user::Entity")] + Users, +} + +impl Related for Entity { + fn to() -> RelationDef { + Relation::Users.def() + } +} + +impl ActiveModelBehavior for ActiveModel {} diff --git a/backend/src/models/user/user.rs b/backend/src/models/user/user.rs index 1ac0a19..85c0a38 100644 --- a/backend/src/models/user/user.rs +++ b/backend/src/models/user/user.rs @@ -18,6 +18,9 @@ pub struct Model { #[sea_orm(unique)] pub email: String, + /// Foreign key to roles table + pub role_id: i32, + pub created_at: DateTime, pub updated_at: DateTime, } @@ -26,6 +29,13 @@ pub struct Model { pub enum Relation { #[sea_orm(has_many = "super::session::Entity")] Sessions, + + #[sea_orm( + belongs_to = "super::role::Entity", + from = "Column::RoleId", + to = "super::role::Column::Id" + )] + Role, } impl Related for Entity { @@ -34,4 +44,11 @@ impl Related for Entity { } } +impl Related for Entity { + fn to() -> RelationDef { + Relation::Role.def() + } +} + impl ActiveModelBehavior for ActiveModel {} +