use chrono::{DateTime, Utc}; use serde::{Deserialize, Serialize}; #[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Hash)] #[serde(rename_all = "lowercase")] pub enum Source { Github, Gitea, Hg, Bugzilla, } impl Source { pub const ALL: &'static [Source] = &[ Source::Github, Source::Gitea, Source::Hg, Source::Bugzilla, ]; pub fn as_str(&self) -> &'static str { match self { Source::Github => "github", Source::Gitea => "gitea", Source::Hg => "hg", Source::Bugzilla => "bugzilla", } } } impl std::str::FromStr for Source { type Err = ParseSourceError; fn from_str(s: &str) -> Result { match s { "github" => Ok(Source::Github), "gitea" => Ok(Source::Gitea), "hg" => Ok(Source::Hg), "bugzilla" => Ok(Source::Bugzilla), other => Err(ParseSourceError(other.to_string())), } } } #[derive(Debug, thiserror::Error)] #[error("unknown source: {0}")] pub struct ParseSourceError(pub String); /// Raw event as stored. The presentation reshape lives in `moments-core` /// and runs at API request time. #[derive(Debug, Clone, Serialize, Deserialize)] pub struct Event { pub id: String, pub source: Source, pub action: String, pub occurred_at: DateTime, /// True when the upstream marks this event as visible to anyone (e.g. /// GitHub's top-level `public` flag). The DB stores everything; the API /// uses this to gate what gets surfaced on the public timeline. pub public: bool, pub payload: serde_json::Value, } /// Filters accepted by `GET /v1/events`. #[derive(Debug, Clone, Default)] pub struct EventQuery { pub from: Option>, pub to: Option>, pub sources: Option>, /// Filter to events matching a specific repo (matched against payload). pub repo: Option, /// When false (default), only `public = true` rows are returned. The API /// pins this to false today; a future authenticated path can flip it. pub include_private: bool, pub limit: u32, } /// Per-source rollup returned by `GET /v1/sources`. #[derive(Debug, Clone, Serialize, Deserialize)] pub struct SourceSummary { pub source: Source, pub count: i64, pub earliest: Option>, pub latest: Option>, } /// Per-day event count for the contribution graph. #[derive(Debug, Clone, Serialize, Deserialize)] pub struct DailyCount { pub date: chrono::NaiveDate, pub count: i64, } /// Per-repo activity rollup for the dashboard. #[derive(Debug, Clone, Serialize, Deserialize)] pub struct ProjectSummary { pub repo: String, pub source: Source, pub host: String, pub commit_count: i64, pub issue_count: i64, pub pr_count: i64, pub first_activity: Option>, pub last_activity: Option>, } // --------------------------------------------------------------------- // Presentation shape — what `GET /v1/events` actually returns. // The API reshapes raw payloads into these so the frontend stays dumb. // --------------------------------------------------------------------- #[derive(Debug, Clone, Serialize, Deserialize)] pub struct TimelineItem { pub id: String, pub source: Source, pub action: String, pub occurred_at: DateTime, pub icon: TimelineIcon, /// Primary headline. Mixed plain text + inline links so the UI can /// render the right anchors without parsing. pub title: Vec, pub subtitle: Option>, pub body: Option, } #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] #[serde(tag = "kind", rename_all = "lowercase")] pub enum TitleSegment { Text { text: String }, Link { text: String, url: String }, } impl TitleSegment { pub fn text(s: impl Into) -> Self { Self::Text { text: s.into() } } pub fn link(text: impl Into, url: impl Into) -> Self { Self::Link { text: text.into(), url: url.into(), } } } #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(tag = "kind", rename_all = "lowercase")] pub enum TimelineBody { Markdown { text: String }, Commits { commits: Vec }, Links { items: Vec }, } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct CommitSummary { pub sha: String, pub short_sha: String, pub message: String, pub url: String, pub author: Option, } /// UI icon hint. The frontend maps these to its own icon set; new variants /// here require a frontend update but never break existing renders (the UI /// falls back to the generic icon for unknown values). #[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)] #[serde(rename_all = "kebab-case")] pub enum TimelineIcon { GitPush, GitCommit, GitMerge, GitFork, GitBranchCreate, GitBranchDelete, PullRequest, Issue, Comment, Star, Release, Bug, Generic, }