Add /v1/activity/daily endpoint returning per-day event counts via generate_series + LEFT JOIN. Frontend renders an SVG contribution graph with circles colored by quantile-based thresholds. Clicking a day navigates to /activity/YYYY-MM-DD showing that day's events. New /activity/:timespan route parses single dates (YYYY-MM-DD) and ranges (YYYY-MM-DD..YYYY-MM-DD) from the URL to initialize the activity timeline filter. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
31 lines
1.1 KiB
Rust
31 lines
1.1 KiB
Rust
pub mod presentation;
|
|
pub mod sources;
|
|
|
|
pub use presentation::reshape;
|
|
pub use sources::{EventSource, PollerState, PollerStateStore, SourceError, run_poller};
|
|
|
|
use async_trait::async_trait;
|
|
use chrono::NaiveDate;
|
|
use moments_entities::{DailyCount, Event, EventQuery, ProjectSummary, SourceSummary};
|
|
|
|
#[derive(Debug, thiserror::Error)]
|
|
pub enum StoreError {
|
|
#[error("database error: {0}")]
|
|
Database(String),
|
|
}
|
|
|
|
/// Read-side port consumed by `moments-api`.
|
|
#[async_trait]
|
|
pub trait EventReader: Send + Sync {
|
|
async fn list_events(&self, query: &EventQuery) -> Result<Vec<Event>, StoreError>;
|
|
async fn source_summaries(&self, include_private: bool) -> Result<Vec<SourceSummary>, StoreError>;
|
|
async fn list_projects(&self) -> Result<Vec<ProjectSummary>, StoreError>;
|
|
async fn daily_counts(&self, from: NaiveDate, to: NaiveDate) -> Result<Vec<DailyCount>, StoreError>;
|
|
}
|
|
|
|
/// Write-side port consumed by `moments-worker`. Idempotent upserts on `id`.
|
|
#[async_trait]
|
|
pub trait EventWriter: Send + Sync {
|
|
async fn upsert_events(&self, events: &[Event]) -> Result<usize, StoreError>;
|
|
}
|