domain models: User, Channel, ScheduleConfig, MediaItem, PlaybackRecord
This commit is contained in:
130
crates/domain/src/models/user.rs
Normal file
130
crates/domain/src/models/user.rs
Normal file
@@ -0,0 +1,130 @@
|
||||
use chrono::{DateTime, Utc};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::value_objects::{Email, UserId};
|
||||
|
||||
/// A user in the system.
|
||||
///
|
||||
/// Designed to be OIDC-ready: the `subject` field stores the OIDC subject claim.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct User {
|
||||
id: UserId,
|
||||
subject: String,
|
||||
email: Email,
|
||||
password_hash: Option<String>,
|
||||
is_admin: bool,
|
||||
created_at: DateTime<Utc>,
|
||||
}
|
||||
|
||||
impl User {
|
||||
/// Create a new OIDC user (no local password).
|
||||
pub fn new(subject: impl Into<String>, email: Email) -> Self {
|
||||
Self {
|
||||
id: UserId::generate(),
|
||||
subject: subject.into(),
|
||||
email,
|
||||
password_hash: None,
|
||||
is_admin: false,
|
||||
created_at: Utc::now(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a new local user with a password hash.
|
||||
pub fn new_local(email: Email, password_hash: impl Into<String>) -> Self {
|
||||
Self {
|
||||
id: UserId::generate(),
|
||||
subject: format!("local|{}", uuid::Uuid::new_v4()),
|
||||
email,
|
||||
password_hash: Some(password_hash.into()),
|
||||
is_admin: false,
|
||||
created_at: Utc::now(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Hydrate from persistence — no validation, accepts all fields.
|
||||
pub fn from_persistence(
|
||||
id: UserId,
|
||||
subject: String,
|
||||
email: Email,
|
||||
password_hash: Option<String>,
|
||||
is_admin: bool,
|
||||
created_at: DateTime<Utc>,
|
||||
) -> Self {
|
||||
Self {
|
||||
id,
|
||||
subject,
|
||||
email,
|
||||
password_hash,
|
||||
is_admin,
|
||||
created_at,
|
||||
}
|
||||
}
|
||||
|
||||
// -- Getters --
|
||||
|
||||
pub fn id(&self) -> UserId {
|
||||
self.id
|
||||
}
|
||||
|
||||
pub fn subject(&self) -> &str {
|
||||
&self.subject
|
||||
}
|
||||
|
||||
pub fn email(&self) -> &Email {
|
||||
&self.email
|
||||
}
|
||||
|
||||
pub fn password_hash(&self) -> Option<&str> {
|
||||
self.password_hash.as_deref()
|
||||
}
|
||||
|
||||
pub fn is_admin(&self) -> bool {
|
||||
self.is_admin
|
||||
}
|
||||
|
||||
pub fn created_at(&self) -> DateTime<Utc> {
|
||||
self.created_at
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn new_generates_id_and_timestamp() {
|
||||
let email = Email::new("test@example.com").unwrap();
|
||||
let user = User::new("oidc|123", email);
|
||||
assert!(!user.is_admin());
|
||||
assert!(user.password_hash().is_none());
|
||||
assert_eq!(user.subject(), "oidc|123");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn new_local_sets_password_and_subject() {
|
||||
let email = Email::new("local@example.com").unwrap();
|
||||
let user = User::new_local(email, "hashed_pw");
|
||||
assert!(user.password_hash().is_some());
|
||||
assert!(user.subject().starts_with("local|"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn from_persistence_round_trip() {
|
||||
let email = Email::new("stored@example.com").unwrap();
|
||||
let id = UserId::generate();
|
||||
let now = Utc::now();
|
||||
let user = User::from_persistence(
|
||||
id,
|
||||
"sub".into(),
|
||||
email.clone(),
|
||||
Some("hash".into()),
|
||||
true,
|
||||
now,
|
||||
);
|
||||
assert_eq!(user.id(), id);
|
||||
assert_eq!(user.subject(), "sub");
|
||||
assert!(user.is_admin());
|
||||
assert_eq!(user.password_hash(), Some("hash"));
|
||||
assert_eq!(user.created_at(), now);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user