use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use super::common::UserId;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct GroupId(pub i64);
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct PermissionId(pub i64);
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct User {
pub id: UserId,
pub email: String,
pub first_name: String,
pub last_name: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub common_name: Option<String>,
#[serde(default)]
pub is_superuser: bool,
#[serde(default = "default_true")]
pub is_active: bool,
#[serde(default)]
pub is_qbnewb: bool,
pub date_joined: DateTime<Utc>,
#[serde(skip_serializing_if = "Option::is_none")]
pub last_login: Option<DateTime<Utc>>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub group_ids: Vec<GroupId>,
#[serde(skip_serializing_if = "Option::is_none")]
pub locale: Option<String>,
#[serde(default)]
pub google_auth: bool,
#[serde(default)]
pub ldap_auth: bool,
#[serde(skip_serializing_if = "Option::is_none")]
pub login_attributes: Option<serde_json::Value>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub user_group_memberships: Vec<UserGroupMembership>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct UserGroupMembership {
pub id: GroupId,
#[serde(default)]
pub is_default: bool,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct HealthStatus {
pub status: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub version: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub database: Option<bool>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct Group {
pub id: GroupId,
pub name: String,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub members: Vec<UserId>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct Permission {
pub id: PermissionId,
pub object: String,
pub group_id: GroupId,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct PermissionGraph {
pub group_id: GroupId,
#[serde(skip_serializing_if = "Option::is_none")]
pub native: Option<serde_json::Value>,
#[serde(skip_serializing_if = "Option::is_none")]
pub schemas: Option<serde_json::Value>,
}
#[derive(Debug, Clone, Serialize)]
pub struct CreateUserRequest {
pub email: String,
pub first_name: String,
pub last_name: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub password: Option<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub group_ids: Vec<GroupId>,
#[serde(skip_serializing_if = "Option::is_none")]
pub locale: Option<String>,
}
#[derive(Debug, Clone, Default, Serialize)]
pub struct UpdateUserRequest {
#[serde(skip_serializing_if = "Option::is_none")]
pub email: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub first_name: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub last_name: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub password: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub is_active: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub is_superuser: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub group_ids: Option<Vec<GroupId>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub locale: Option<String>,
}
#[derive(Debug, Clone, Serialize)]
pub struct CreateGroupRequest {
pub name: String,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub members: Vec<UserId>,
}
#[derive(Debug, Clone, Default, Serialize)]
pub struct UpdateGroupRequest {
#[serde(skip_serializing_if = "Option::is_none")]
pub name: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub members: Option<Vec<UserId>>,
}
#[derive(Debug, Clone, Serialize)]
pub struct MembershipRequest {
pub group_id: GroupId,
pub user_id: UserId,
}
fn default_true() -> bool {
true
}
impl User {
pub fn builder(
email: impl Into<String>,
first_name: impl Into<String>,
last_name: impl Into<String>,
) -> UserBuilder {
UserBuilder::new(email, first_name, last_name)
}
pub fn full_name(&self) -> String {
format!("{} {}", self.first_name, self.last_name)
}
}
pub struct UserBuilder {
email: String,
first_name: String,
last_name: String,
password: Option<String>,
is_superuser: bool,
group_ids: Vec<GroupId>,
locale: Option<String>,
}
impl UserBuilder {
pub fn new(
email: impl Into<String>,
first_name: impl Into<String>,
last_name: impl Into<String>,
) -> Self {
Self {
email: email.into(),
first_name: first_name.into(),
last_name: last_name.into(),
password: None,
is_superuser: false,
group_ids: Vec::new(),
locale: None,
}
}
pub fn password(mut self, password: impl Into<String>) -> Self {
self.password = Some(password.into());
self
}
pub fn superuser(mut self, is_superuser: bool) -> Self {
self.is_superuser = is_superuser;
self
}
pub fn add_group(mut self, group_id: GroupId) -> Self {
self.group_ids.push(group_id);
self
}
pub fn locale(mut self, locale: impl Into<String>) -> Self {
self.locale = Some(locale.into());
self
}
pub fn build(self) -> User {
User {
id: UserId(0), email: self.email,
first_name: self.first_name.clone(),
last_name: self.last_name.clone(),
common_name: Some(format!("{} {}", self.first_name, self.last_name)),
is_superuser: self.is_superuser,
is_active: true,
is_qbnewb: false,
date_joined: Utc::now(),
last_login: None,
group_ids: self.group_ids,
locale: self.locale,
google_auth: false,
ldap_auth: false,
login_attributes: None,
user_group_memberships: Vec::new(),
}
}
pub fn build_request(self) -> CreateUserRequest {
CreateUserRequest {
email: self.email,
first_name: self.first_name,
last_name: self.last_name,
password: self.password,
group_ids: self.group_ids,
locale: self.locale,
}
}
}
impl Group {
pub fn builder(name: impl Into<String>) -> GroupBuilder {
GroupBuilder::new(name)
}
}
pub struct GroupBuilder {
name: String,
members: Vec<UserId>,
}
impl GroupBuilder {
pub fn new(name: impl Into<String>) -> Self {
Self {
name: name.into(),
members: Vec::new(),
}
}
pub fn add_member(mut self, user_id: UserId) -> Self {
self.members.push(user_id);
self
}
pub fn members(mut self, members: Vec<UserId>) -> Self {
self.members = members;
self
}
pub fn build(self) -> Group {
Group {
id: GroupId(0), name: self.name,
members: self.members,
}
}
pub fn build_request(self) -> CreateGroupRequest {
CreateGroupRequest {
name: self.name,
members: self.members,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_user_creation() {
let user = User::builder("john@example.com", "John", "Doe")
.password("secure123")
.superuser(false)
.add_group(GroupId(1))
.locale("en_US")
.build();
assert_eq!(user.email, "john@example.com");
assert_eq!(user.first_name, "John");
assert_eq!(user.last_name, "Doe");
assert_eq!(user.full_name(), "John Doe");
assert!(!user.is_superuser);
assert_eq!(user.group_ids.len(), 1);
}
#[test]
fn test_create_user_request() {
let request = User::builder("jane@example.com", "Jane", "Smith")
.password("password123")
.add_group(GroupId(2))
.build_request();
assert_eq!(request.email, "jane@example.com");
assert_eq!(request.first_name, "Jane");
assert_eq!(request.last_name, "Smith");
assert_eq!(request.password, Some("password123".to_string()));
assert_eq!(request.group_ids.len(), 1);
}
#[test]
fn test_group_creation() {
let group = Group::builder("Administrators")
.add_member(UserId(1))
.add_member(UserId(2))
.build();
assert_eq!(group.name, "Administrators");
assert_eq!(group.members.len(), 2);
}
#[test]
fn test_update_user_request() {
let request = UpdateUserRequest {
email: Some("newemail@example.com".to_string()),
is_active: Some(false),
..Default::default()
};
assert_eq!(request.email, Some("newemail@example.com".to_string()));
assert_eq!(request.is_active, Some(false));
assert!(request.first_name.is_none());
}
#[test]
fn test_permission() {
let permission = Permission {
id: PermissionId(1),
object: "/db/1/schema/PUBLIC/".to_string(),
group_id: GroupId(1),
};
assert_eq!(permission.object, "/db/1/schema/PUBLIC/");
assert_eq!(permission.group_id, GroupId(1));
}
}