This commit is contained in:
2025-04-19 19:46:55 +02:00
parent 9dfe263c60
commit 1b08d57924
18 changed files with 1692 additions and 5 deletions

View File

@@ -1,5 +1,7 @@
// Export models
pub mod user;
pub mod ticket;
// Re-export models for easier imports
pub use user::User;
pub use user::User;
pub use ticket::{Ticket, TicketComment, TicketStatus, TicketPriority, TicketFilter};

View File

@@ -0,0 +1,177 @@
use serde::{Deserialize, Serialize};
use chrono::{DateTime, Utc};
use uuid::Uuid;
/// Represents the status of a support ticket
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub enum TicketStatus {
/// Ticket has been opened but not yet addressed
Open,
/// Ticket is currently being worked on
InProgress,
/// Ticket is waiting for customer response
WaitingForCustomer,
/// Ticket has been resolved
Resolved,
/// Ticket has been closed without resolution
Closed,
}
impl std::fmt::Display for TicketStatus {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
TicketStatus::Open => write!(f, "Open"),
TicketStatus::InProgress => write!(f, "In Progress"),
TicketStatus::WaitingForCustomer => write!(f, "Waiting for Customer"),
TicketStatus::Resolved => write!(f, "Resolved"),
TicketStatus::Closed => write!(f, "Closed"),
}
}
}
/// Represents the priority of a support ticket
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub enum TicketPriority {
/// Low priority ticket
Low,
/// Medium priority ticket
Medium,
/// High priority ticket
High,
/// Critical priority ticket
Critical,
}
impl std::fmt::Display for TicketPriority {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
TicketPriority::Low => write!(f, "Low"),
TicketPriority::Medium => write!(f, "Medium"),
TicketPriority::High => write!(f, "High"),
TicketPriority::Critical => write!(f, "Critical"),
}
}
}
/// Represents a support ticket in the system
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Ticket {
/// Unique identifier for the ticket
pub id: String,
/// User ID of the ticket creator
pub user_id: i32,
/// Title of the ticket
pub title: String,
/// Description of the issue
pub description: String,
/// Current status of the ticket
pub status: TicketStatus,
/// Priority level of the ticket
pub priority: TicketPriority,
/// When the ticket was created
pub created_at: DateTime<Utc>,
/// When the ticket was last updated
pub updated_at: DateTime<Utc>,
/// User ID of the assigned support agent (if any)
pub assigned_to: Option<i32>,
}
impl Ticket {
/// Creates a new ticket
pub fn new(user_id: i32, title: String, description: String, priority: TicketPriority) -> Self {
let now = Utc::now();
Self {
id: Uuid::new_v4().to_string(),
user_id,
title,
description,
status: TicketStatus::Open,
priority,
created_at: now,
updated_at: now,
assigned_to: None,
}
}
/// Updates the ticket status
pub fn update_status(&mut self, status: TicketStatus) {
self.status = status;
self.updated_at = Utc::now();
}
/// Assigns the ticket to a support agent
pub fn assign(&mut self, agent_id: i32) {
self.assigned_to = Some(agent_id);
self.updated_at = Utc::now();
}
/// Unassigns the ticket from any support agent
pub fn unassign(&mut self) {
self.assigned_to = None;
self.updated_at = Utc::now();
}
/// Updates the ticket priority
pub fn update_priority(&mut self, priority: TicketPriority) {
self.priority = priority;
self.updated_at = Utc::now();
}
}
/// Represents a comment on a support ticket
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TicketComment {
/// Unique identifier for the comment
pub id: String,
/// ID of the ticket this comment belongs to
pub ticket_id: String,
/// User ID of the comment author
pub user_id: i32,
/// Content of the comment
pub content: String,
/// When the comment was created
pub created_at: DateTime<Utc>,
/// Whether this comment is from a support agent
pub is_support_response: bool,
}
impl TicketComment {
/// Creates a new ticket comment
pub fn new(ticket_id: String, user_id: i32, content: String, is_support_response: bool) -> Self {
Self {
id: Uuid::new_v4().to_string(),
ticket_id,
user_id,
content,
created_at: Utc::now(),
is_support_response,
}
}
}
/// Represents a filter for searching tickets
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TicketFilter {
/// Filter by ticket status
pub status: Option<TicketStatus>,
/// Filter by ticket priority
pub priority: Option<TicketPriority>,
/// Filter by assigned agent
pub assigned_to: Option<i32>,
/// Filter by user who created the ticket
pub user_id: Option<i32>,
/// Search term for title and description
pub search_term: Option<String>,
}
impl Default for TicketFilter {
fn default() -> Self {
Self {
status: None,
priority: None,
assigned_to: None,
user_id: None,
search_term: None,
}
}
}

View File

@@ -1,5 +1,6 @@
use serde::{Deserialize, Serialize};
use chrono::{DateTime, Utc};
use bcrypt::{hash, verify, DEFAULT_COST};
/// Represents a user in the system
#[derive(Debug, Clone, Serialize, Deserialize)]
@@ -10,6 +11,9 @@ pub struct User {
pub name: String,
/// User's email address
pub email: String,
/// User's hashed password
#[serde(skip_serializing)]
pub password_hash: Option<String>,
/// User's role in the system
pub role: UserRole,
/// When the user was created
@@ -34,24 +38,56 @@ impl User {
id: None,
name,
email,
password_hash: None,
role: UserRole::User,
created_at: Some(Utc::now()),
updated_at: Some(Utc::now()),
}
}
/// Creates a new user with a password
pub fn new_with_password(name: String, email: String, password: &str) -> Result<Self, bcrypt::BcryptError> {
let password_hash = hash(password, DEFAULT_COST)?;
Ok(Self {
id: None,
name,
email,
password_hash: Some(password_hash),
role: UserRole::User,
created_at: Some(Utc::now()),
updated_at: Some(Utc::now()),
})
}
/// Creates a new admin user
pub fn new_admin(name: String, email: String) -> Self {
Self {
id: None,
name,
email,
password_hash: None,
role: UserRole::Admin,
created_at: Some(Utc::now()),
updated_at: Some(Utc::now()),
}
}
/// Creates a new admin user with a password
pub fn new_admin_with_password(name: String, email: String, password: &str) -> Result<Self, bcrypt::BcryptError> {
let password_hash = hash(password, DEFAULT_COST)?;
Ok(Self {
id: None,
name,
email,
password_hash: Some(password_hash),
role: UserRole::Admin,
created_at: Some(Utc::now()),
updated_at: Some(Utc::now()),
})
}
/// Checks if the user is an admin
pub fn is_admin(&self) -> bool {
self.role == UserRole::Admin
@@ -69,6 +105,38 @@ impl User {
self.updated_at = Some(Utc::now());
}
/// Sets or updates the user's password
pub fn set_password(&mut self, password: &str) -> Result<(), bcrypt::BcryptError> {
let password_hash = hash(password, DEFAULT_COST)?;
self.password_hash = Some(password_hash);
self.updated_at = Some(Utc::now());
Ok(())
}
/// Verifies if the provided password matches the stored hash
pub fn verify_password(&self, password: &str) -> Result<bool, bcrypt::BcryptError> {
match &self.password_hash {
Some(hash) => verify(password, hash),
None => Ok(false),
}
}
}
/// Represents user login credentials
#[derive(Debug, Deserialize)]
pub struct LoginCredentials {
pub email: String,
pub password: String,
}
/// Represents user registration data
#[derive(Debug, Deserialize)]
pub struct RegistrationData {
pub name: String,
pub email: String,
pub password: String,
pub password_confirmation: String,
}
#[cfg(test)]