use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Project {
pub id: i64,
pub name: String,
pub identifier: String,
pub description: String,
pub emoji: Option<String>,
pub lead_user_id: Option<i64>,
pub sort_order: i64,
pub created_at: String,
pub updated_at: String,
}
#[derive(Debug, Deserialize)]
pub struct ReorderProjects {
pub ids: Vec<i64>,
}
#[derive(Debug, Deserialize)]
pub struct CreateProject {
pub name: String,
pub identifier: String,
#[serde(default)]
pub description: String,
pub emoji: Option<String>,
pub lead_user_id: Option<i64>,
}
#[derive(Debug, Deserialize)]
pub struct UpdateProject {
pub name: Option<String>,
pub identifier: Option<String>,
pub description: Option<String>,
#[serde(default, deserialize_with = "crate::db::models::deserialize_nullable")]
pub emoji: Option<Option<String>>,
#[serde(default, deserialize_with = "crate::db::models::deserialize_nullable")]
pub lead_user_id: Option<Option<i64>>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ProjectGroup {
pub id: i64,
pub user_id: i64,
pub name: String,
pub sort_order: i64,
pub project_ids: Vec<i64>,
pub created_at: String,
pub updated_at: String,
}
#[derive(Debug, Deserialize)]
pub struct CreateProjectGroup {
pub name: String,
}
#[derive(Debug, Deserialize)]
pub struct UpdateProjectGroup {
pub name: Option<String>,
}
#[derive(Debug, Deserialize)]
pub struct AssignProjectGroup {
pub project_id: i64,
pub group_id: Option<i64>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Issue {
pub id: i64,
pub project_id: i64,
pub sequence: i64,
pub identifier: String,
pub title: String,
pub description: String,
pub status: String,
pub priority: String,
pub module_id: Option<i64>,
pub sort_order: f64,
pub start_date: Option<String>,
pub target_date: Option<String>,
pub created_at: String,
pub updated_at: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub source: Option<String>,
#[serde(default)]
pub labels: Vec<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub blocks: Vec<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub blocked_by: Vec<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub relates_to: Vec<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub duplicates: Vec<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub duplicated_by: Vec<String>,
}
#[derive(Debug, Deserialize)]
pub struct CreateIssue {
pub project_id: i64,
pub title: String,
#[serde(default)]
pub description: String,
#[serde(default = "default_status")]
pub status: String,
#[serde(default = "default_priority")]
pub priority: String,
pub module_id: Option<i64>,
pub start_date: Option<String>,
pub target_date: Option<String>,
#[serde(default)]
pub labels: Vec<String>,
#[serde(default)]
pub source: Option<String>,
}
#[derive(Debug, Deserialize)]
pub struct UpdateIssue {
pub title: Option<String>,
pub description: Option<String>,
pub status: Option<String>,
pub priority: Option<String>,
#[serde(default, deserialize_with = "crate::db::models::deserialize_nullable")]
pub module_id: Option<Option<i64>>,
pub sort_order: Option<f64>,
pub start_date: Option<String>,
pub target_date: Option<String>,
pub labels: Option<Vec<String>>,
}
#[derive(Debug, Default, Deserialize)]
pub struct ListIssuesQuery {
pub project_id: Option<i64>,
pub status: Option<String>,
pub priority: Option<String>,
pub module_id: Option<i64>,
pub label: Option<String>,
pub workable: Option<bool>,
pub blocked: Option<bool>,
pub created_since: Option<String>,
pub created_until: Option<String>,
pub updated_since: Option<String>,
pub updated_until: Option<String>,
pub order_by: Option<String>,
pub order: Option<String>,
pub limit: Option<i64>,
pub offset: Option<i64>,
}
#[derive(Debug, Default, Serialize, Deserialize)]
pub struct IssueStatusCounts {
pub backlog: i64,
pub todo: i64,
pub active: i64,
pub done: i64,
pub cancelled: i64,
pub total: i64,
}
fn default_status() -> String {
"backlog".to_string()
}
fn default_priority() -> String {
"none".to_string()
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Module {
pub id: i64,
pub project_id: i64,
pub name: String,
pub description: String,
pub status: String,
pub emoji: Option<String>,
pub created_at: String,
pub updated_at: String,
}
#[derive(Debug, Deserialize)]
pub struct CreateModule {
pub project_id: i64,
pub name: String,
#[serde(default)]
pub description: String,
#[serde(default = "default_module_status")]
pub status: String,
pub emoji: Option<String>,
}
#[derive(Debug, Deserialize)]
pub struct UpdateModule {
pub name: Option<String>,
pub description: Option<String>,
pub status: Option<String>,
#[serde(default, deserialize_with = "crate::db::models::deserialize_nullable")]
pub emoji: Option<Option<String>>,
}
fn default_module_status() -> String {
"active".to_string()
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Label {
pub id: i64,
pub project_id: i64,
pub name: String,
pub color: String,
}
#[derive(Debug, Deserialize)]
pub struct CreateLabel {
pub project_id: i64,
pub name: String,
#[serde(default = "default_label_color")]
pub color: String,
}
#[derive(Debug, Deserialize)]
pub struct UpdateLabel {
pub name: Option<String>,
pub color: Option<String>,
}
fn default_label_color() -> String {
"#6B7280".to_string()
}
#[derive(Debug, Deserialize)]
pub struct UpdateFolder {
pub name: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Page {
pub id: i64,
pub project_id: Option<i64>,
pub sequence: Option<i64>,
pub identifier: String,
pub folder_id: Option<i64>,
pub title: String,
pub content: String,
pub sort_order: f64,
pub status: String,
#[serde(default)]
pub pinned: bool,
pub created_at: String,
pub updated_at: String,
#[serde(default)]
pub labels: Vec<String>,
}
#[derive(Debug, Deserialize)]
pub struct CreatePage {
pub project_id: Option<i64>,
pub folder_id: Option<i64>,
pub title: String,
#[serde(default)]
pub content: String,
#[serde(default = "default_page_status")]
pub status: String,
#[serde(default)]
pub labels: Vec<String>,
}
#[derive(Debug, Deserialize)]
pub struct UpdatePage {
pub title: Option<String>,
pub content: Option<String>,
#[serde(default, deserialize_with = "crate::db::models::deserialize_nullable")]
pub folder_id: Option<Option<i64>>,
pub sort_order: Option<f64>,
pub status: Option<String>,
pub pinned: Option<bool>,
pub labels: Option<Vec<String>>,
}
fn default_page_status() -> String {
"draft".to_string()
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Folder {
pub id: i64,
pub project_id: i64,
pub parent_id: Option<i64>,
pub name: String,
pub sort_order: f64,
}
#[derive(Debug, Deserialize)]
pub struct CreateFolder {
pub project_id: i64,
pub parent_id: Option<i64>,
pub name: String,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum Role {
Viewer,
Maintainer,
Lead,
}
impl Role {
pub fn as_str(self) -> &'static str {
match self {
Role::Viewer => "viewer",
Role::Maintainer => "maintainer",
Role::Lead => "lead",
}
}
}
impl std::fmt::Display for Role {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(self.as_str())
}
}
impl std::str::FromStr for Role {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"viewer" => Ok(Role::Viewer),
"maintainer" => Ok(Role::Maintainer),
"lead" => Ok(Role::Lead),
other => Err(format!("invalid role: {other:?}")),
}
}
}
impl rusqlite::types::FromSql for Role {
fn column_result(value: rusqlite::types::ValueRef<'_>) -> rusqlite::types::FromSqlResult<Self> {
value.as_str()?.parse().map_err(|_| rusqlite::types::FromSqlError::InvalidType)
}
}
impl rusqlite::types::ToSql for Role {
fn to_sql(&self) -> rusqlite::Result<rusqlite::types::ToSqlOutput<'_>> {
Ok(self.as_str().into())
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ProjectMember {
pub project_id: i64,
pub user_id: i64,
pub role: Role,
pub created_at: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MemberWithUser {
pub project_id: i64,
pub user_id: i64,
pub role: Role,
pub created_at: String,
pub username: String,
pub display_name: String,
}
#[derive(Debug, Deserialize)]
pub struct AddMember {
pub user_id: i64,
pub role: Option<String>,
}
#[derive(Debug, Deserialize)]
pub struct ChangeMemberRole {
pub role: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct User {
pub id: i64,
pub username: String,
pub email: String,
#[serde(skip_serializing)]
pub password_hash: String,
pub display_name: String,
pub is_admin: bool,
pub is_bot: bool,
pub created_at: String,
pub updated_at: String,
}
#[derive(Debug, Deserialize)]
pub struct CreateUser {
pub username: String,
pub email: String,
pub password: String,
pub display_name: Option<String>,
#[serde(default)]
pub is_admin: bool,
#[serde(default)]
pub is_bot: bool,
}
#[derive(Debug, Deserialize)]
pub struct LoginRequest {
pub identity: String,
pub password: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AuthUser {
pub id: i64,
pub username: String,
pub display_name: String,
pub is_admin: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Session {
pub token: String,
pub user_id: i64,
pub expires_at: String,
pub created_at: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Bot {
pub id: i64,
pub username: String,
pub display_name: String,
pub owner_id: Option<i64>,
pub created_at: String,
pub has_active_key: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct UserApiKey {
pub id: i64,
pub name: String,
pub created_at: String,
pub expires_at: Option<String>,
pub revoked: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Comment {
pub id: i64,
pub issue_id: Option<i64>,
pub page_id: Option<i64>,
pub user_id: i64,
pub author: String,
pub author_display_name: String,
pub content: String,
pub created_at: String,
pub updated_at: String,
}
#[derive(Debug, Deserialize)]
pub struct CreateComment {
pub content: String,
}
#[derive(Debug, Deserialize)]
pub struct UpdateComment {
pub content: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MentionCandidate {
pub user_id: i64,
pub username: String,
pub display_name: String,
}
#[derive(Debug, Default, Deserialize)]
pub struct SearchQuery {
pub query: String,
pub project_id: Option<i64>,
pub result_type: Option<String>,
pub sort: Option<String>,
pub mode: Option<String>,
pub limit: Option<i64>,
pub offset: Option<i64>,
}
#[derive(Debug, Serialize)]
pub struct SearchResult {
pub result_type: String,
pub id: i64,
pub identifier: Option<String>,
pub title: String,
pub snippet: String,
pub project_id: Option<i64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub parent_page_id: Option<i64>,
}
#[derive(Debug, Clone, serde::Serialize)]
pub struct Activity {
pub id: i64,
pub ts: String,
pub actor_user_id: Option<i64>,
pub actor_username: Option<String>,
pub actor_display_name: Option<String>,
pub actor_is_bot: bool,
pub transport: String,
pub entity_type: String,
pub entity_id: i64,
pub entity_label: Option<String>,
pub project_id: Option<i64>,
pub issue_id: Option<i64>,
pub page_id: Option<i64>,
pub action: String,
pub field: Option<String>,
pub old_value: Option<String>,
pub new_value: Option<String>,
}
#[derive(Debug, serde::Serialize)]
pub struct ActivityFeed {
pub items: Vec<Activity>,
pub has_more: bool,
}
#[derive(Debug, serde::Serialize)]
pub struct ActorStat {
pub actor_user_id: Option<i64>,
pub username: Option<String>,
pub display_name: Option<String>,
pub is_bot: bool,
pub actions: i64,
pub last_ts: String,
pub top_transport: String,
}
#[derive(Debug, Clone, Serialize)]
pub struct WeekPoint {
pub week_start: String,
pub count: i64,
}
#[derive(Debug, Default, Serialize)]
pub struct PriorityCounts {
pub urgent: i64,
pub high: i64,
pub medium: i64,
pub low: i64,
pub none: i64,
pub total: i64,
}
#[derive(Debug, Serialize)]
pub struct ModuleCount {
pub module_id: Option<i64>,
pub name: String,
pub count: i64,
}
#[derive(Debug, Serialize)]
pub struct InsightsPayload {
pub weeks: i64,
pub created_per_week: Vec<WeekPoint>,
pub closed_per_week: Vec<WeekPoint>,
pub status_counts: IssueStatusCounts,
pub priority_counts: PriorityCounts,
pub module_counts: Vec<ModuleCount>,
pub top_actors: Vec<ActorStat>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Plan {
pub id: i64,
pub project_id: i64,
pub sequence: i64,
pub identifier: String,
pub issue_id: Option<i64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub anchor_identifier: Option<String>,
pub title: String,
pub status: String,
pub created_at: String,
pub updated_at: String,
#[serde(default)]
pub steps: Vec<PlanStepNode>,
#[serde(default)]
pub step_count: i64,
#[serde(default)]
pub done_count: i64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PlanStepNode {
pub id: i64,
pub plan_id: i64,
pub parent_step_id: Option<i64>,
pub position: i64,
pub title: String,
pub description: String,
pub issue_id: Option<i64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub issue_identifier: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub issue_status: Option<String>,
pub done: bool,
#[serde(skip_serializing_if = "Option::is_none")]
pub reopened_via_issue_at: Option<String>,
pub created_at: String,
pub edited_at: Option<String>,
#[serde(default)]
pub children: Vec<PlanStepNode>,
}
#[derive(Debug, Deserialize)]
pub struct CreatePlan {
pub project_id: i64,
pub title: String,
pub issue_id: Option<i64>,
#[serde(default)]
pub steps: Vec<CreatePlanStep>,
}
#[derive(Debug, Deserialize)]
pub struct CreatePlanStep {
pub title: String,
#[serde(default)]
pub description: String,
pub issue_id: Option<i64>,
#[serde(default)]
pub done: bool,
#[serde(default)]
pub steps: Vec<CreatePlanStep>,
}
#[derive(Debug, Default, Deserialize)]
pub struct UpdatePlan {
pub title: Option<String>,
pub status: Option<String>,
#[serde(default, deserialize_with = "crate::db::models::deserialize_nullable")]
pub issue_id: Option<Option<i64>>,
}
#[derive(Debug, Default, Deserialize)]
pub struct ListPlansQuery {
pub project_id: Option<i64>,
pub status: Option<String>,
pub limit: Option<i64>,
pub offset: Option<i64>,
pub order_by: Option<String>,
pub before_id: Option<i64>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SavedView {
pub id: i64,
pub project_id: i64,
pub user_id: i64,
pub name: String,
pub config: String,
pub is_default: bool,
pub created_at: String,
pub updated_at: String,
}
#[derive(Debug, Deserialize)]
pub struct CreateSavedView {
pub name: String,
pub config: String,
#[serde(default)]
pub is_default: bool,
}
#[derive(Debug, Default, Deserialize)]
pub struct UpdateSavedView {
pub name: Option<String>,
pub config: Option<String>,
pub is_default: Option<bool>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Attachment {
pub id: i64,
#[serde(skip_serializing)]
pub sha256: String,
pub filename: String,
pub mime: String,
pub size_bytes: i64,
pub uploader_id: Option<i64>,
pub created_at: String,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum AttachmentEntity {
Issue,
Page,
Comment,
}
impl AttachmentEntity {
pub fn as_str(self) -> &'static str {
match self {
AttachmentEntity::Issue => "issue",
AttachmentEntity::Page => "page",
AttachmentEntity::Comment => "comment",
}
}
}
impl std::str::FromStr for AttachmentEntity {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"issue" => Ok(AttachmentEntity::Issue),
"page" => Ok(AttachmentEntity::Page),
"comment" => Ok(AttachmentEntity::Comment),
other => Err(format!("invalid attachment entity: {other:?}")),
}
}
}
pub fn deserialize_nullable<'de, T, D>(deserializer: D) -> Result<Option<Option<T>>, D::Error>
where
T: serde::Deserialize<'de>,
D: serde::Deserializer<'de>,
{
Ok(Some(Option::deserialize(deserializer)?))
}