use std::cmp::Ordering; use std::str::FromStr; use chrono::NaiveDateTime; #[cfg(feature = "backend")] use diesel::*; use serde::{Deserialize, Serialize}; use uuid::Uuid; #[cfg(feature = "backend")] pub use sql::*; #[cfg(feature = "backend")] pub mod sql; pub trait ToVec { type Item; fn ordered() -> Vec; } pub type IssueId = i32; pub type ProjectId = i32; pub type UserId = i32; pub type CommentId = i32; pub type TokenId = i32; pub type IssueStatusId = i32; pub type InvitationId = i32; pub type Position = i32; pub type MessageId = i32; pub type EmailString = String; pub type UsernameString = String; pub type TitleString = String; #[cfg_attr(feature = "backend", derive(FromSqlRow, AsExpression))] #[cfg_attr(feature = "backend", sql_type = "IssueTypeType")] #[derive(Clone, Copy, Deserialize, Serialize, Debug, PartialOrd, PartialEq, Hash)] pub enum IssueType { Task, Bug, Story, } impl ToVec for IssueType { type Item = IssueType; fn ordered() -> Vec { vec![IssueType::Task, IssueType::Bug, IssueType::Story] } } impl Default for IssueType { fn default() -> Self { IssueType::Task } } impl IssueType { pub fn to_label(&self) -> &str { match self { IssueType::Task => "Task", IssueType::Bug => "Bug", IssueType::Story => "Story", } } } impl Into for IssueType { fn into(self) -> u32 { match self { IssueType::Task => 1, IssueType::Bug => 2, IssueType::Story => 3, } } } impl Into for u32 { fn into(self) -> IssueType { match self { 1 => IssueType::Task, 2 => IssueType::Bug, 3 => IssueType::Story, _ => IssueType::Task, } } } impl std::fmt::Display for IssueType { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { IssueType::Task => f.write_str("task"), IssueType::Bug => f.write_str("bug"), IssueType::Story => f.write_str("story"), } } } #[cfg_attr(feature = "backend", derive(FromSqlRow, AsExpression))] #[cfg_attr(feature = "backend", sql_type = "IssuePriorityType")] #[derive(Clone, Copy, Deserialize, Serialize, Debug, PartialOrd, PartialEq, Hash)] pub enum IssuePriority { Highest, High, Medium, Low, Lowest, } impl ToVec for IssuePriority { type Item = IssuePriority; fn ordered() -> Vec { vec![ IssuePriority::Highest, IssuePriority::High, IssuePriority::Medium, IssuePriority::Low, IssuePriority::Lowest, ] } } impl FromStr for IssuePriority { type Err = String; fn from_str(s: &str) -> Result { match s.to_lowercase().trim() { "highest" => Ok(IssuePriority::Highest), "high" => Ok(IssuePriority::High), "medium" => Ok(IssuePriority::Medium), "low" => Ok(IssuePriority::Low), "lowest" => Ok(IssuePriority::Lowest), _ => Err(format!("Unknown priority {}", s)), } } } impl Default for IssuePriority { fn default() -> Self { IssuePriority::Medium } } impl std::fmt::Display for IssuePriority { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { IssuePriority::Highest => f.write_str("highest"), IssuePriority::High => f.write_str("high"), IssuePriority::Medium => f.write_str("medium"), IssuePriority::Low => f.write_str("low"), IssuePriority::Lowest => f.write_str("lowest"), } } } impl Into for IssuePriority { fn into(self) -> u32 { match self { IssuePriority::Highest => 5, IssuePriority::High => 4, IssuePriority::Medium => 3, IssuePriority::Low => 2, IssuePriority::Lowest => 1, } } } impl Into for u32 { fn into(self) -> IssuePriority { match self { 5 => IssuePriority::Highest, 4 => IssuePriority::High, 3 => IssuePriority::Medium, 2 => IssuePriority::Low, 1 => IssuePriority::Lowest, _ => IssuePriority::Medium, } } } #[cfg_attr(feature = "backend", derive(FromSqlRow, AsExpression))] #[cfg_attr(feature = "backend", sql_type = "UserRoleType")] #[derive(Clone, Copy, Deserialize, Serialize, Debug, PartialEq, Hash)] pub enum UserRole { User, Manager, Owner, } impl PartialOrd for UserRole { fn partial_cmp(&self, other: &Self) -> Option { use UserRole::*; if self == other { return Some(Ordering::Equal); } let order = match (self, other) { (User, Manager) | (User, Owner) | (Manager, Owner) => Ordering::Less, _ => Ordering::Greater, }; Some(order) } } impl ToVec for UserRole { type Item = UserRole; fn ordered() -> Vec { vec![UserRole::User, UserRole::Manager, UserRole::Owner] } } impl FromStr for UserRole { type Err = String; fn from_str(s: &str) -> Result { match s.to_lowercase().trim() { "user" => Ok(UserRole::User), "manager" => Ok(UserRole::Manager), "owner" => Ok(UserRole::Owner), _ => Err(format!("Unknown user role {}", s)), } } } impl Default for UserRole { fn default() -> Self { UserRole::User } } impl std::fmt::Display for UserRole { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { UserRole::User => f.write_str("user"), UserRole::Manager => f.write_str("manager"), UserRole::Owner => f.write_str("owner"), } } } impl Into for UserRole { fn into(self) -> u32 { match self { UserRole::User => 0, UserRole::Manager => 1, UserRole::Owner => 2, } } } impl Into for u32 { fn into(self) -> UserRole { match self { 0 => UserRole::User, 1 => UserRole::Manager, 2 => UserRole::Owner, _ => UserRole::User, } } } #[cfg_attr(feature = "backend", derive(FromSqlRow, AsExpression))] #[cfg_attr(feature = "backend", sql_type = "ProjectCategoryType")] #[derive(Clone, Copy, Deserialize, Serialize, Debug, PartialOrd, PartialEq, Hash)] pub enum ProjectCategory { Software, Marketing, Business, } impl ToVec for ProjectCategory { type Item = ProjectCategory; fn ordered() -> Vec { vec![ ProjectCategory::Software, ProjectCategory::Marketing, ProjectCategory::Business, ] } } impl FromStr for ProjectCategory { type Err = String; fn from_str(s: &str) -> Result { match s.to_lowercase().trim() { "software" => Ok(ProjectCategory::Software), "marketing" => Ok(ProjectCategory::Marketing), "business" => Ok(ProjectCategory::Business), _ => Err(format!("Unknown project category {}", s)), } } } impl Default for ProjectCategory { fn default() -> Self { ProjectCategory::Software } } impl std::fmt::Display for ProjectCategory { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { ProjectCategory::Software => f.write_str("software"), ProjectCategory::Marketing => f.write_str("marketing"), ProjectCategory::Business => f.write_str("business"), } } } impl Into for ProjectCategory { fn into(self) -> u32 { match self { ProjectCategory::Software => 0, ProjectCategory::Marketing => 1, ProjectCategory::Business => 2, } } } impl Into for u32 { fn into(self) -> ProjectCategory { match self { 0 => ProjectCategory::Software, 1 => ProjectCategory::Marketing, 2 => ProjectCategory::Business, _ => ProjectCategory::Software, } } } #[cfg_attr(feature = "backend", derive(FromSqlRow, AsExpression))] #[cfg_attr(feature = "backend", sql_type = "InvitationStateType")] #[derive(Clone, Deserialize, Serialize, Debug, PartialOrd, PartialEq, Hash)] pub enum InvitationState { Sent, Accepted, Revoked, } impl Default for InvitationState { fn default() -> Self { InvitationState::Sent } } impl std::fmt::Display for InvitationState { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { InvitationState::Sent => f.write_str("sent"), InvitationState::Accepted => f.write_str("accepted"), InvitationState::Revoked => f.write_str("revoked"), } } } #[cfg_attr(feature = "backend", derive(FromSqlRow, AsExpression))] #[cfg_attr(feature = "backend", sql_type = "TimeTrackingType")] #[derive(Clone, Copy, Deserialize, Serialize, Debug, PartialOrd, PartialEq, Hash)] pub enum TimeTracking { Untracked, Fibonacci, Hourly, } impl Into for TimeTracking { fn into(self) -> u32 { match self { TimeTracking::Untracked => 0, TimeTracking::Fibonacci => 1, TimeTracking::Hourly => 2, } } } impl Into for u32 { fn into(self) -> TimeTracking { match self { 0 => TimeTracking::Untracked, 1 => TimeTracking::Fibonacci, 2 => TimeTracking::Hourly, _ => TimeTracking::Untracked, } } } #[derive(Clone, Serialize, Debug, PartialEq)] pub struct ErrorResponse { pub errors: Vec, } #[cfg_attr(feature = "backend", derive(Queryable))] #[derive(Clone, Serialize, Deserialize, Debug, PartialEq)] pub struct Project { pub id: ProjectId, pub name: String, pub url: String, pub description: String, pub category: ProjectCategory, pub created_at: NaiveDateTime, pub updated_at: NaiveDateTime, pub time_tracking: TimeTracking, } #[derive(Clone, Serialize, Deserialize, Debug, PartialEq)] pub struct Issue { pub id: IssueId, pub title: String, pub issue_type: IssueType, pub priority: IssuePriority, pub list_position: i32, pub description: Option, pub description_text: Option, pub estimate: Option, pub time_spent: Option, pub time_remaining: Option, pub reporter_id: UserId, pub project_id: ProjectId, pub created_at: NaiveDateTime, pub updated_at: NaiveDateTime, pub issue_status_id: IssueStatusId, pub user_ids: Vec, } #[cfg_attr(feature = "backend", derive(Queryable))] #[derive(Clone, Serialize, Deserialize, Debug, PartialEq)] pub struct IssueStatus { pub id: IssueStatusId, pub name: String, pub position: ProjectId, pub project_id: ProjectId, pub created_at: NaiveDateTime, pub updated_at: NaiveDateTime, } #[cfg_attr(feature = "backend", derive(Queryable))] #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] pub struct Invitation { pub id: i32, pub name: String, pub email: String, pub state: InvitationState, pub project_id: i32, pub invited_by_id: i32, pub created_at: NaiveDateTime, pub updated_at: NaiveDateTime, pub bind_token: Uuid, } #[cfg_attr(feature = "backend", derive(Queryable))] #[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] pub struct Comment { pub id: CommentId, pub body: String, pub user_id: UserId, pub issue_id: IssueId, pub created_at: NaiveDateTime, pub updated_at: NaiveDateTime, } #[cfg_attr(feature = "backend", derive(Queryable))] #[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] pub struct User { pub id: UserId, pub name: String, pub email: String, pub avatar_url: Option, pub project_id: ProjectId, pub created_at: NaiveDateTime, pub updated_at: NaiveDateTime, pub user_role: UserRole, } #[cfg_attr(feature = "backend", derive(Queryable))] #[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] pub struct Token { pub id: TokenId, pub user_id: UserId, pub access_token: Uuid, pub refresh_token: Uuid, pub created_at: NaiveDateTime, pub updated_at: NaiveDateTime, pub bind_token: Option, } #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, PartialOrd)] pub struct UpdateIssuePayload { pub title: String, pub issue_type: IssueType, pub priority: IssuePriority, pub list_position: i32, pub description: Option, pub description_text: Option, pub estimate: Option, pub time_spent: Option, pub time_remaining: Option, pub project_id: ProjectId, pub reporter_id: UserId, pub issue_status_id: IssueStatusId, pub user_ids: Vec, } #[cfg_attr(feature = "backend", derive(Queryable))] #[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] pub struct IssueAssignee { pub id: i32, pub issue_id: IssueId, pub user_id: UserId, pub created_at: NaiveDateTime, pub updated_at: NaiveDateTime, } impl From for UpdateIssuePayload { fn from(issue: Issue) -> Self { Self { title: issue.title, issue_type: issue.issue_type, priority: issue.priority, list_position: issue.list_position, description: issue.description, description_text: issue.description_text, estimate: issue.estimate, time_spent: issue.time_spent, time_remaining: issue.time_remaining, project_id: issue.project_id, reporter_id: issue.reporter_id, user_ids: issue.user_ids, issue_status_id: issue.issue_status_id, } } } #[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] pub struct Message { pub id: MessageId, pub receiver_id: UserId, pub sender_id: UserId, pub summary: String, pub description: String, pub message_type: String, pub hyper_link: String, pub created_at: NaiveDateTime, pub updated_at: NaiveDateTime, } #[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] pub struct CreateCommentPayload { pub user_id: Option, pub issue_id: IssueId, pub body: String, } #[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] pub struct UpdateCommentPayload { pub id: CommentId, pub body: String, } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq)] pub struct CreateIssuePayload { pub title: String, pub issue_type: IssueType, pub priority: IssuePriority, pub description: Option, pub description_text: Option, pub estimate: Option, pub time_spent: Option, pub time_remaining: Option, pub project_id: ProjectId, pub user_ids: Vec, pub reporter_id: UserId, pub issue_status_id: IssueStatusId, } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq)] pub struct UpdateProjectPayload { pub id: ProjectId, pub name: Option, pub url: Option, pub description: Option, pub category: Option, pub time_tracking: Option, } #[derive(Serialize, Deserialize, Clone, Debug, PartialEq)] pub enum PayloadVariant { OptionI32(Option), VecI32(Vec), I32(i32), String(String), IssueType(IssueType), IssuePriority(IssuePriority), ProjectCategory(ProjectCategory), } #[derive(Serialize, Deserialize, Clone, Debug, PartialOrd, PartialEq, Hash)] pub enum ProjectFieldId { Name, Url, Description, Category, TimeTracking, IssueStatusName, } #[derive(Serialize, Deserialize, Clone, Debug, PartialOrd, PartialEq, Hash)] pub enum SignInFieldId { Username, Email, Token, } #[derive(Serialize, Deserialize, Clone, Debug, PartialOrd, PartialEq, Hash)] pub enum SignUpFieldId { Username, Email, } #[derive(Serialize, Deserialize, Clone, Debug, PartialOrd, PartialEq, Hash)] pub enum UsersFieldId { Username, Email, UserRole, Avatar, } #[derive(Serialize, Deserialize, Clone, Debug, PartialOrd, PartialEq, Hash)] pub enum InviteFieldId { Token, } #[derive(Serialize, Deserialize, Clone, Debug, PartialOrd, PartialEq, Hash)] pub enum CommentFieldId { Body, } #[derive(Serialize, Deserialize, Clone, Debug, PartialOrd, PartialEq, Hash)] pub enum IssueFieldId { Type, Title, Description, ListPosition, Assignees, Reporter, Priority, Estimate, TimeSpent, TimeRemaining, IssueStatusId, } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq)] pub enum WsMsg { Ping, Pong, // auth AuthorizeRequest(Uuid), AuthorizeLoaded(Result), AuthorizeExpired, AuthenticateRequest(EmailString, UsernameString), AuthenticateSuccess, BindTokenCheck(Uuid), BindTokenBad, BindTokenOk(Uuid), // Sign up SignUpRequest(EmailString, UsernameString), SignUpSuccess, SignUpPairTaken, // invitations InvitationListRequest, InvitationListLoaded(Vec), // InvitedUsersRequest, InvitedUsersLoaded(Vec), // InvitationSendRequest { name: UsernameString, email: EmailString, }, InvitationSendSuccess, InvitationSendFailure, // InvitationRevokeRequest(InvitationId), InvitationRevokeSuccess(InvitationId), // InvitationAcceptRequest(InvitationId), InvitationAcceptSuccess(InvitationId), InvitedUserRemoveRequest(EmailString), InvitedUserRemoveSuccess(EmailString), // project page ProjectRequest, ProjectLoaded(Project), ProjectIssuesRequest, ProjectIssuesLoaded(Vec), ProjectUsersRequest, ProjectUsersLoaded(Vec), ProjectUpdateRequest(UpdateProjectPayload), // issue IssueUpdateRequest(IssueId, IssueFieldId, PayloadVariant), IssueUpdated(Issue), IssueDeleteRequest(IssueId), IssueDeleted(IssueId), IssueCreateRequest(CreateIssuePayload), IssueCreated(Issue), // issue status IssueStatusesRequest, IssueStatusesResponse(Vec), IssueStatusUpdate(IssueStatusId, TitleString, Position), IssueStatusUpdated(IssueStatus), IssueStatusCreate(TitleString, Position), IssueStatusCreated(IssueStatus), IssueStatusDelete(IssueStatusId), IssueStatusDeleted(IssueStatusId), // comments IssueCommentsRequest(IssueId), IssueCommentsLoaded(Vec), CreateComment(CreateCommentPayload), UpdateComment(UpdateCommentPayload), CommentDeleteRequest(CommentId), CommentDeleted(CommentId), // users AvatarUrlChanged(UserId, String), ProfileUpdate(EmailString, UsernameString), ProfileUpdated, // messages Message(Message), }