maarcadetweet: initial commit
AT Protocol PDS + AppView + Tauri Desktop Client, 160-char post limit. - PDS (Rust + axum + sqlx) - Auth: createAccount, createSession, refreshSession - Records: createRecord, deleteRecord (race-safe via SELECT FOR UPDATE) - Feed: feed.like.create, feed.repost.create - Sync: getRepo, getBlocks, getLatestCommit, getRecord (with MST proof), listRepos - Identity: resolveHandle - MST: spec-conformant (at-mst crate, 27 tests) - Repo: signed commits, TID counter (monotonic, 4096 wrap safe) - AppView (Rust + axum + sqlx) - Jetstream consumer (WebSocket, exponential backoff, 38k+ events indexed) - REST API: timeline/home (graph-aware), profile, search, post (with thread hydration) - Handle-sync worker (did:plc + did:web) - JSONB embed storage + thread columns (migration 0003) - Like/repost counter cache (migration 0004) - Tauri 2 + Svelte 5 Desktop Client - System tray (Show/Compose/Quit menu) - OS notifications (tauri-plugin-notification) - Auto-update (tauri-plugin-updater, placeholder endpoint) - Window-state (tauri-plugin-window-state) - 160-char compose with live counter - Image/Link embed rendering - LocalStorage-persisted like state - Timeline with poll (prepend new posts) - Custom TitleBar (transparent, no decorations) - Orange/IBM Plex Mono maarcade design Tests: 231 Rust + 9 vitest = 240 passed.
This commit is contained in:
@@ -0,0 +1,78 @@
|
||||
use serde::Deserialize;
|
||||
|
||||
/// Default polling interval for the handle-sync worker, in seconds.
|
||||
/// Bumped to 5 minutes — handle changes are infrequent and a missing
|
||||
/// `@handle` is purely cosmetic, so we don't need to hammer the PLC.
|
||||
fn default_handle_sync_interval() -> u64 {
|
||||
300
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
pub struct AppConfig {
|
||||
pub pds_host: String,
|
||||
pub pds_port: u16,
|
||||
pub pds_public_url: String,
|
||||
pub pds_handle_dns_zone: String,
|
||||
pub pds_jwt_secret: String,
|
||||
pub appview_host: String,
|
||||
pub appview_port: u16,
|
||||
pub appview_public_url: String,
|
||||
pub jetstream_url: String,
|
||||
pub jetstream_collections: Vec<String>,
|
||||
pub database_url_pds: String,
|
||||
pub database_url_appview: String,
|
||||
pub s3_endpoint: String,
|
||||
pub s3_region: String,
|
||||
pub s3_access_key: String,
|
||||
pub s3_secret_key: String,
|
||||
pub s3_bucket_pds: String,
|
||||
pub s3_bucket_appview: String,
|
||||
pub plc_directory_url: String,
|
||||
/// Optional shared secret for `POST /internal/ingest-commit`. If unset,
|
||||
/// the endpoint accepts anonymous requests (dev mode). If set, callers
|
||||
/// must send `X-Ingest-Secret: <value>`.
|
||||
#[serde(default)]
|
||||
pub appview_ingest_secret: Option<String>,
|
||||
/// How often the handle-sync worker scans the `posts` table for rows
|
||||
/// with an empty `handle` column and resolves them via the PLC
|
||||
/// directory. Default: 300s (5 minutes).
|
||||
#[serde(default = "default_handle_sync_interval")]
|
||||
pub appview_handle_sync_interval_secs: u64,
|
||||
}
|
||||
|
||||
impl AppConfig {
|
||||
pub fn from_env() -> anyhow::Result<Self> {
|
||||
let env = |k: &str| std::env::var(k).map_err(|_| anyhow::anyhow!("missing env: {k}"));
|
||||
let collections: Vec<String> = env("JETSTREAM_COLLECTIONS")?
|
||||
.split(',')
|
||||
.map(|s| s.trim().to_string())
|
||||
.filter(|s| !s.is_empty())
|
||||
.collect();
|
||||
Ok(Self {
|
||||
pds_host: env("PDS_HOST")?,
|
||||
pds_port: env("PDS_PORT")?.parse()?,
|
||||
pds_public_url: env("PDS_PUBLIC_URL")?,
|
||||
pds_handle_dns_zone: env("PDS_HANDLE_DNS_ZONE")?,
|
||||
pds_jwt_secret: env("PDS_JWT_SECRET")?,
|
||||
appview_host: env("APPVIEW_HOST")?,
|
||||
appview_port: env("APPVIEW_PORT")?.parse()?,
|
||||
appview_public_url: env("APPVIEW_PUBLIC_URL")?,
|
||||
jetstream_url: env("JETSTREAM_URL")?,
|
||||
jetstream_collections: collections,
|
||||
database_url_pds: env("DATABASE_URL_PDS")?,
|
||||
database_url_appview: env("DATABASE_URL_APPVIEW")?,
|
||||
s3_endpoint: env("S3_ENDPOINT")?,
|
||||
s3_region: env("S3_REGION")?,
|
||||
s3_access_key: env("S3_ACCESS_KEY")?,
|
||||
s3_secret_key: env("S3_SECRET_KEY")?,
|
||||
s3_bucket_pds: env("S3_BUCKET_PDS")?,
|
||||
s3_bucket_appview: env("S3_BUCKET_APPVIEW")?,
|
||||
plc_directory_url: env("PLC_DIRECTORY_URL")?,
|
||||
appview_ingest_secret: std::env::var("APPVIEW_INGEST_SECRET").ok(),
|
||||
appview_handle_sync_interval_secs: std::env::var("APPVIEW_HANDLE_SYNC_INTERVAL_SECS")
|
||||
.ok()
|
||||
.and_then(|s| s.parse().ok())
|
||||
.unwrap_or_else(default_handle_sync_interval),
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user