233df1d99d
CI / Build, lint & test (push) Successful in 3m7s
Production is frozen and sits on schema 19, so the content_kind correction it
needs cannot be applied yet. `--dry-run` runs the real UPDATEs and rolls the
transaction back, so an operator can see exactly what a run would touch before
touching a database they cannot easily restore — the counts are measured, not
predicted.
`updated_by_kind` reports the SHAPE of the change ("consumable 17, staff 3"),
which is the thing worth sanity-checking: a different shape means the source
profile moved and the mapping needs regenerating.
Measured against a copy of prod-core.db: 20 rows would change (17 consumable,
3 staff), 1966 already correct, 0 unmatched, and the copy was verified unchanged
afterwards.
98 lines
4.5 KiB
Rust
98 lines
4.5 KiB
Rust
use anyhow::{Context, Result};
|
|
use openfut_core::{config, db, seed};
|
|
use tracing::info;
|
|
use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt, EnvFilter};
|
|
|
|
#[tokio::main]
|
|
async fn main() -> Result<()> {
|
|
dotenvy::dotenv().ok();
|
|
|
|
tracing_subscriber::registry()
|
|
.with(
|
|
EnvFilter::try_from_default_env()
|
|
.unwrap_or_else(|_| "openfut_core=debug,tower_http=debug".into()),
|
|
)
|
|
// Diagnostics on stderr so stdout carries only machine output (the
|
|
// `import`/`seed-dev` subcommands print a clean JSON result there).
|
|
.with(tracing_subscriber::fmt::layer().with_writer(std::io::stderr))
|
|
.init();
|
|
|
|
let cfg = config::Config::from_env()?;
|
|
|
|
// Opt-in dev subcommand: `openfut-core seed-dev` seeds the FIFA 17 dev
|
|
// profile/club from the dev content pack, prints a coverage report, and
|
|
// exits. Normal server startup NEVER seeds dev inventory.
|
|
if std::env::args().nth(1).as_deref() == Some("seed-dev") {
|
|
let pool = db::init_pool(&cfg.database_url, cfg.max_connections).await?;
|
|
db::run_migrations(&pool).await?;
|
|
let mut card_db = openfut_core::services::card_db::CardDb::load(&cfg.data_dir)?;
|
|
card_db.load_game_dev(&cfg.data_dir, seed::FIFA17_GAME)?;
|
|
let report = seed::seed_fifa17_dev(&pool, &card_db).await?;
|
|
println!("{}", serde_json::to_string_pretty(&report)?);
|
|
return Ok(());
|
|
}
|
|
|
|
// Opt-in generic import subcommand: `openfut-core import <request.json>`.
|
|
// Reads a GAME-AGNOSTIC ProfileImportRequest (the importer adapter translates
|
|
// FIFA17 source data into it), loads production content packs, runs preflight,
|
|
// and applies one all-or-nothing transaction. FIFA17 semantics live entirely
|
|
// in the adapter; Core only sees opaque ids + opaque extension bytes.
|
|
if std::env::args().nth(1).as_deref() == Some("import") {
|
|
let path = std::env::args()
|
|
.nth(2)
|
|
.context("usage: openfut-core import <request.json>")?;
|
|
let pool = db::init_pool(&cfg.database_url, cfg.max_connections).await?;
|
|
db::run_migrations(&pool).await?;
|
|
let mut card_db = openfut_core::services::card_db::CardDb::load(&cfg.data_dir)?;
|
|
for pack in &cfg.content_packs {
|
|
card_db.load_pack(pack)?;
|
|
}
|
|
let raw = std::fs::read_to_string(&path)
|
|
.with_context(|| format!("read import request {path}"))?;
|
|
let req: openfut_core::services::import::ProfileImportRequest =
|
|
serde_json::from_str(&raw).context("parse import request JSON")?;
|
|
let outcome =
|
|
openfut_core::services::import::apply_profile_import(&pool, &card_db, &req).await?;
|
|
println!("{}", serde_json::to_string_pretty(&outcome)?);
|
|
return Ok(());
|
|
}
|
|
|
|
// `openfut-core reclassify <request.json> [--dry-run]` — correct the
|
|
// content_kind of already-imported owned rows. A profile import is
|
|
// once-only, so a taxonomy fix cannot arrive by re-importing; the adapter
|
|
// supplies card_id -> kind because only it can map its own taxonomy.
|
|
// Idempotent. `--dry-run` runs the same statements and rolls back, so an
|
|
// operator can see what a production run would touch before it touches it.
|
|
if std::env::args().nth(1).as_deref() == Some("reclassify") {
|
|
let path = std::env::args()
|
|
.nth(2)
|
|
.context("usage: openfut-core reclassify <request.json> [--dry-run]")?;
|
|
let dry_run = std::env::args().any(|a| a == "--dry-run");
|
|
let pool = db::init_pool(&cfg.database_url, cfg.max_connections).await?;
|
|
db::run_migrations(&pool).await?;
|
|
let raw = std::fs::read_to_string(&path)
|
|
.with_context(|| format!("read reclassify request {path}"))?;
|
|
let mut req: openfut_core::services::import::ReclassifyRequest =
|
|
serde_json::from_str(&raw).context("parse reclassify request JSON")?;
|
|
req.dry_run |= dry_run;
|
|
let outcome = openfut_core::services::import::reclassify_owned_content(&pool, &req).await?;
|
|
println!("{}", serde_json::to_string_pretty(&outcome)?);
|
|
return Ok(());
|
|
}
|
|
|
|
info!("OpenFUT Core starting on {}", cfg.listen_addr);
|
|
|
|
let pool = db::init_pool(&cfg.database_url, cfg.max_connections).await?;
|
|
db::run_migrations(&pool).await?;
|
|
|
|
seed::maybe_seed(&pool).await?;
|
|
|
|
let app = openfut_core::app::build(pool, cfg.clone()).await?;
|
|
|
|
let listener = tokio::net::TcpListener::bind(&cfg.listen_addr).await?;
|
|
info!("Listening on http://{}", cfg.listen_addr);
|
|
axum::serve(listener, app).await?;
|
|
|
|
Ok(())
|
|
}
|