refactor: restructure domain crate by bounded context
This commit is contained in:
158
crates/domain/src/identity/entities.rs
Normal file
158
crates/domain/src/identity/entities.rs
Normal file
@@ -0,0 +1,158 @@
|
||||
use std::collections::HashSet;
|
||||
use chrono::{DateTime, Utc};
|
||||
use crate::common::errors::DomainError;
|
||||
use crate::common::value_objects::{Email, PasswordHash, SystemId};
|
||||
|
||||
// --- Permission ---
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
|
||||
pub enum PermissionAction {
|
||||
ReadAsset,
|
||||
ReadMetadata,
|
||||
ReadLocation,
|
||||
ReadPerson,
|
||||
WriteMetadata,
|
||||
DeleteAsset,
|
||||
ManageAccess,
|
||||
ManageUsers,
|
||||
ManageSystem,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
|
||||
pub enum ResourceType {
|
||||
Asset,
|
||||
Album,
|
||||
Collection,
|
||||
Person,
|
||||
Directory,
|
||||
Global,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
|
||||
pub struct Permission {
|
||||
pub action: PermissionAction,
|
||||
pub resource_type: ResourceType,
|
||||
}
|
||||
|
||||
impl Permission {
|
||||
pub fn new(action: PermissionAction, resource_type: ResourceType) -> Self {
|
||||
Self { action, resource_type }
|
||||
}
|
||||
}
|
||||
|
||||
pub fn viewer_permissions() -> HashSet<Permission> {
|
||||
HashSet::from([
|
||||
Permission::new(PermissionAction::ReadAsset, ResourceType::Global),
|
||||
Permission::new(PermissionAction::ReadMetadata, ResourceType::Global),
|
||||
])
|
||||
}
|
||||
|
||||
pub fn contributor_permissions() -> HashSet<Permission> {
|
||||
let mut perms = viewer_permissions();
|
||||
perms.insert(Permission::new(PermissionAction::WriteMetadata, ResourceType::Global));
|
||||
perms
|
||||
}
|
||||
|
||||
pub fn admin_permissions() -> HashSet<Permission> {
|
||||
let mut perms = contributor_permissions();
|
||||
perms.insert(Permission::new(PermissionAction::DeleteAsset, ResourceType::Global));
|
||||
perms.insert(Permission::new(PermissionAction::ManageAccess, ResourceType::Global));
|
||||
perms.insert(Permission::new(PermissionAction::ManageUsers, ResourceType::Global));
|
||||
perms.insert(Permission::new(PermissionAction::ManageSystem, ResourceType::Global));
|
||||
perms.insert(Permission::new(PermissionAction::ReadLocation, ResourceType::Global));
|
||||
perms.insert(Permission::new(PermissionAction::ReadPerson, ResourceType::Global));
|
||||
perms
|
||||
}
|
||||
|
||||
// --- Role ---
|
||||
|
||||
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
|
||||
pub struct Role {
|
||||
pub role_id: SystemId,
|
||||
pub name: String,
|
||||
pub permissions: HashSet<Permission>,
|
||||
pub is_system_default: bool,
|
||||
}
|
||||
|
||||
impl Role {
|
||||
pub fn new(name: impl Into<String>, permissions: HashSet<Permission>, is_system_default: bool) -> Self {
|
||||
Self {
|
||||
role_id: SystemId::new(),
|
||||
name: name.into(),
|
||||
permissions,
|
||||
is_system_default,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn has_permission(&self, action: PermissionAction, resource_type: ResourceType) -> bool {
|
||||
self.permissions.contains(&Permission::new(action, resource_type))
|
||||
}
|
||||
}
|
||||
|
||||
// --- User ---
|
||||
|
||||
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
|
||||
pub struct User {
|
||||
pub id: SystemId,
|
||||
pub username: String,
|
||||
pub email: Email,
|
||||
pub password_hash: PasswordHash,
|
||||
pub created_at: DateTime<Utc>,
|
||||
}
|
||||
|
||||
impl User {
|
||||
pub fn new(username: impl Into<String>, email: Email, password_hash: PasswordHash) -> Self {
|
||||
Self {
|
||||
id: SystemId::new(),
|
||||
username: username.into(),
|
||||
email,
|
||||
password_hash,
|
||||
created_at: Utc::now(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// --- Group ---
|
||||
|
||||
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
|
||||
pub struct Group {
|
||||
pub group_id: SystemId,
|
||||
pub name: String,
|
||||
pub owner_user_id: SystemId,
|
||||
pub members: HashSet<SystemId>,
|
||||
}
|
||||
|
||||
impl Group {
|
||||
pub fn new(name: impl Into<String>, owner_user_id: SystemId) -> Self {
|
||||
let mut members = HashSet::new();
|
||||
members.insert(owner_user_id);
|
||||
Self {
|
||||
group_id: SystemId::new(),
|
||||
name: name.into(),
|
||||
owner_user_id,
|
||||
members,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn add_member(&mut self, user_id: SystemId) -> Result<(), DomainError> {
|
||||
if self.members.contains(&user_id) {
|
||||
return Err(DomainError::Conflict(format!("User {user_id} is already a member")));
|
||||
}
|
||||
self.members.insert(user_id);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn remove_member(&mut self, user_id: SystemId) -> Result<(), DomainError> {
|
||||
if user_id == self.owner_user_id {
|
||||
return Err(DomainError::Validation("Cannot remove the group owner".to_string()));
|
||||
}
|
||||
if !self.members.remove(&user_id) {
|
||||
return Err(DomainError::NotFound(format!("User {user_id} is not a member")));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn is_member(&self, user_id: &SystemId) -> bool {
|
||||
self.members.contains(user_id)
|
||||
}
|
||||
}
|
||||
7
crates/domain/src/identity/mod.rs
Normal file
7
crates/domain/src/identity/mod.rs
Normal file
@@ -0,0 +1,7 @@
|
||||
pub mod entities;
|
||||
pub mod ports;
|
||||
pub mod services;
|
||||
|
||||
pub use entities::*;
|
||||
pub use ports::*;
|
||||
pub use services::*;
|
||||
50
crates/domain/src/identity/ports.rs
Normal file
50
crates/domain/src/identity/ports.rs
Normal file
@@ -0,0 +1,50 @@
|
||||
use async_trait::async_trait;
|
||||
use crate::common::errors::DomainError;
|
||||
use crate::common::value_objects::{Email, PasswordHash, SystemId};
|
||||
use super::entities::{Group, Role, User};
|
||||
|
||||
// --- UserRepository ---
|
||||
|
||||
#[async_trait]
|
||||
pub trait UserRepository: Send + Sync {
|
||||
async fn find_by_id(&self, id: &SystemId) -> Result<Option<User>, DomainError>;
|
||||
async fn find_by_email(&self, email: &Email) -> Result<Option<User>, DomainError>;
|
||||
async fn find_by_username(&self, username: &str) -> Result<Option<User>, DomainError>;
|
||||
async fn save(&self, user: &User) -> Result<(), DomainError>;
|
||||
async fn delete(&self, id: &SystemId) -> Result<(), DomainError>;
|
||||
}
|
||||
|
||||
// --- RoleRepository ---
|
||||
|
||||
#[async_trait]
|
||||
pub trait RoleRepository: Send + Sync {
|
||||
async fn find_by_id(&self, id: &SystemId) -> Result<Option<Role>, DomainError>;
|
||||
async fn find_by_name(&self, name: &str) -> Result<Option<Role>, DomainError>;
|
||||
async fn find_defaults(&self) -> Result<Vec<Role>, DomainError>;
|
||||
async fn save(&self, role: &Role) -> Result<(), DomainError>;
|
||||
async fn delete(&self, id: &SystemId) -> Result<(), DomainError>;
|
||||
}
|
||||
|
||||
// --- GroupRepository ---
|
||||
|
||||
#[async_trait]
|
||||
pub trait GroupRepository: Send + Sync {
|
||||
async fn find_by_id(&self, id: &SystemId) -> Result<Option<Group>, DomainError>;
|
||||
async fn find_by_user(&self, user_id: &SystemId) -> Result<Vec<Group>, DomainError>;
|
||||
async fn save(&self, group: &Group) -> Result<(), DomainError>;
|
||||
async fn delete(&self, id: &SystemId) -> Result<(), DomainError>;
|
||||
}
|
||||
|
||||
// --- Auth ---
|
||||
|
||||
#[async_trait]
|
||||
pub trait PasswordHasher: Send + Sync {
|
||||
async fn hash(&self, password: &str) -> Result<PasswordHash, DomainError>;
|
||||
async fn verify(&self, password: &str, hash: &PasswordHash) -> Result<bool, DomainError>;
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
pub trait TokenIssuer: Send + Sync {
|
||||
async fn issue(&self, user_id: &SystemId, role: &str) -> Result<String, DomainError>;
|
||||
async fn verify(&self, token: &str) -> Result<(SystemId, String), DomainError>;
|
||||
}
|
||||
21
crates/domain/src/identity/services.rs
Normal file
21
crates/domain/src/identity/services.rs
Normal file
@@ -0,0 +1,21 @@
|
||||
use std::collections::HashSet;
|
||||
use super::entities::{Permission, PermissionAction, ResourceType, Role};
|
||||
|
||||
pub struct PermissionChecker;
|
||||
|
||||
impl PermissionChecker {
|
||||
pub fn has_permission(
|
||||
roles: &[Role],
|
||||
action: PermissionAction,
|
||||
resource_type: ResourceType,
|
||||
) -> bool {
|
||||
roles.iter().any(|role| {
|
||||
role.has_permission(action, resource_type)
|
||||
|| role.has_permission(action, ResourceType::Global)
|
||||
})
|
||||
}
|
||||
|
||||
pub fn effective_permissions(roles: &[Role]) -> HashSet<Permission> {
|
||||
roles.iter().flat_map(|r| r.permissions.iter().copied()).collect()
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user