chore: scaffold moments workspace

Cargo workspace with five crates per architecture conventions:

- moments-entities: Source enum, Event, EventQuery, SourceSummary
- moments-core:     EventReader / EventWriter ports
- moments-data:     PgStore (sqlx postgres adapter) + 0001_init.sql
- moments-api:      axum binary; /v1/{healthz,events,sources}
- moments-worker:   skeleton; pollers land in step 2

Sources committed-to for ingestion: github, gitea, hg, bugzilla.
Workstation events explicitly retired (not deferred).

Build + clippy clean. sqlx queries use the runtime API for now;
will switch to compile-time-checked macros + .sqlx offline cache
once magrathea has the moments_{ro,rw} roles and database created.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-03 17:47:06 +03:00
commit 6775309043
16 changed files with 3580 additions and 0 deletions

View File

@@ -0,0 +1,13 @@
[package]
name = "moments-entities"
version.workspace = true
edition.workspace = true
rust-version.workspace = true
license.workspace = true
authors.workspace = true
[dependencies]
serde.workspace = true
serde_json.workspace = true
chrono.workspace = true
thiserror.workspace = true

View File

@@ -0,0 +1,76 @@
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<Self, Self::Err> {
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<Utc>,
pub payload: serde_json::Value,
}
/// Filters accepted by `GET /v1/events`.
#[derive(Debug, Clone, Default)]
pub struct EventQuery {
pub from: Option<DateTime<Utc>>,
pub to: Option<DateTime<Utc>>,
pub sources: Option<Vec<Source>>,
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<DateTime<Utc>>,
pub latest: Option<DateTime<Utc>>,
}