PG er autoritativ, SpacetimeDB er varm cache. Frontend snakker kun med SpacetimeDB, worker håndterer toveissynk. Fase 1 — SpacetimeDB-modul: - delete_message med SyncOutbox-event - edit_message reducer - MessageReaction tabell + add/remove_reaction reducers - load_messages med JSON-parsing (erstatter pipe-format) - clear_channel reducer for duplikat-fri warmup - load_reactions reducer Fase 2 — Worker: - warmup.rs: PG→ST oppvarming ved oppstart (100 msg/kanal) - sync.rs: håndter delete/update/reaction actions - Sync-intervall redusert til 1s Fase 3 — Frontend: - spacetime.svelte.ts: ren SpacetimeDB-adapter, ingen PG-hybrid - ChatConnection interface med edit/delete/react metoder - ChatBlock bruker chat.edit/delete/react direkte - PG-adapter som readonly fallback Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
110 lines
3 KiB
Rust
110 lines
3 KiB
Rust
use clap::Parser;
|
|
use sqlx::postgres::PgPoolOptions;
|
|
use std::sync::Arc;
|
|
use tracing::{info, warn};
|
|
|
|
mod handlers;
|
|
mod sync;
|
|
mod warmup;
|
|
mod worker;
|
|
|
|
#[derive(Parser)]
|
|
#[command(name = "sidelinja-worker", about = "Jobbkø-worker for Sidelinja")]
|
|
struct Cli {
|
|
/// PostgreSQL connection string
|
|
#[arg(
|
|
long,
|
|
env = "DATABASE_URL",
|
|
default_value = "postgres://sidelinja:localdev@localhost:5432/sidelinja"
|
|
)]
|
|
database_url: String,
|
|
|
|
/// AI Gateway base URL
|
|
#[arg(
|
|
long,
|
|
env = "AI_GATEWAY_URL",
|
|
default_value = "http://localhost:4000/v1"
|
|
)]
|
|
ai_gateway_url: String,
|
|
|
|
/// Maks samtidige jobber
|
|
#[arg(long, default_value = "3")]
|
|
max_concurrent: usize,
|
|
|
|
/// Polling-intervall i sekunder
|
|
#[arg(long, default_value = "1")]
|
|
poll_interval: u64,
|
|
|
|
/// SpacetimeDB URL
|
|
#[arg(long, env = "SPACETIMEDB_URL", default_value = "http://localhost:3000")]
|
|
spacetimedb_url: String,
|
|
|
|
/// SpacetimeDB modulnavn
|
|
#[arg(long, env = "SPACETIMEDB_MODULE", default_value = "sidelinja-realtime")]
|
|
spacetimedb_module: String,
|
|
|
|
/// Sync-intervall i sekunder (SpacetimeDB → PG)
|
|
#[arg(long, default_value = "1")]
|
|
sync_interval: u64,
|
|
|
|
/// Maks meldinger per kanal ved oppvarming
|
|
#[arg(long, default_value = "100")]
|
|
warmup_limit: i64,
|
|
}
|
|
|
|
#[tokio::main]
|
|
async fn main() -> anyhow::Result<()> {
|
|
tracing_subscriber::fmt()
|
|
.with_env_filter(
|
|
tracing_subscriber::EnvFilter::try_from_default_env()
|
|
.unwrap_or_else(|_| "sidelinja_worker=info,sqlx=warn".into()),
|
|
)
|
|
.json()
|
|
.init();
|
|
|
|
let cli = Cli::parse();
|
|
|
|
info!(
|
|
max_concurrent = cli.max_concurrent,
|
|
poll_interval_s = cli.poll_interval,
|
|
"Starter sidelinja-worker"
|
|
);
|
|
|
|
let pool = PgPoolOptions::new()
|
|
.max_connections(cli.max_concurrent as u32 + 2)
|
|
.connect(&cli.database_url)
|
|
.await?;
|
|
|
|
info!("Tilkoblet PostgreSQL");
|
|
|
|
let registry = Arc::new(handlers::build_registry(
|
|
reqwest::Client::new(),
|
|
cli.ai_gateway_url,
|
|
));
|
|
|
|
let registered: Vec<&str> = registry.keys().map(|k| k.as_str()).collect();
|
|
info!(?registered, "Registrerte jobbtyper");
|
|
|
|
// Oppvarming: last PG-data inn i SpacetimeDB
|
|
let http = reqwest::Client::new();
|
|
if let Err(e) = warmup::run(
|
|
&pool,
|
|
&http,
|
|
&cli.spacetimedb_url,
|
|
&cli.spacetimedb_module,
|
|
cli.warmup_limit,
|
|
).await {
|
|
warn!(error = %e, "Oppvarming feilet — fortsetter uten historikk i SpacetimeDB");
|
|
}
|
|
|
|
// Spawn sync-worker som parallell task
|
|
let sync_pool = pool.clone();
|
|
let spacetimedb_url = cli.spacetimedb_url.clone();
|
|
let spacetimedb_module = cli.spacetimedb_module.clone();
|
|
let sync_interval = cli.sync_interval;
|
|
tokio::spawn(async move {
|
|
sync::run(sync_pool, http, spacetimedb_url, spacetimedb_module, sync_interval).await;
|
|
});
|
|
|
|
worker::run(pool, registry, cli.max_concurrent, cli.poll_interval).await
|
|
}
|