Adds a data-driven event system (#47 schema, #48 activation, #49 TOTW): - EventDefinition JSON schema (event_type, effects, date range, is_manual) - data/events/totw_week1.json — TOTW event active by date range (2024–2030), injects 5 TOTW cards into the market as [EVENT] listings - data/events/seasonal_events.json — Spring Festival and Icon Weekend, manual-activation-only example events - migrations/0004_events.sql — events override table (NULL/1/0 per event) - services/event — load_event_definitions, compute_is_active (date range + manual override), get_active_events, get_all_with_status, activate/deactivate - routes/events — GET /events, GET /events/:id, POST .../activate, .../deactivate - services/market::refresh_npc_listings now accepts event_defs, injects bonus market cards from active events at 2× premium price - 5 new integration tests (events list, single, activate/deactivate cycle, 404 on unknown, market injection verification); 19/19 passing Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
+23
-5
@@ -2,13 +2,15 @@ use crate::middleware::{limit_concurrency, MakeRequestUuid};
|
||||
use crate::{
|
||||
config::Config,
|
||||
db::Pool,
|
||||
models::event::EventDefinition,
|
||||
models::objective::{ObjectiveDefinition, ObjectiveType},
|
||||
models::pack::PackDefinition,
|
||||
models::sbc::SbcDefinition,
|
||||
routes,
|
||||
services::{
|
||||
card_db::CardDb, market, objective::load_objective_definitions,
|
||||
pack::load_pack_definitions, sbc::load_sbc_definitions,
|
||||
card_db::CardDb, event::load_event_definitions, market,
|
||||
objective::load_objective_definitions, pack::load_pack_definitions,
|
||||
sbc::load_sbc_definitions,
|
||||
},
|
||||
};
|
||||
use anyhow::Result;
|
||||
@@ -32,6 +34,7 @@ pub struct AppState {
|
||||
pub pack_defs: Arc<Vec<PackDefinition>>,
|
||||
pub obj_defs: Arc<Vec<ObjectiveDefinition>>,
|
||||
pub sbc_defs: Arc<Vec<SbcDefinition>>,
|
||||
pub event_defs: Arc<Vec<EventDefinition>>,
|
||||
}
|
||||
|
||||
pub async fn build(pool: Pool, cfg: Config) -> Result<Router> {
|
||||
@@ -39,13 +42,15 @@ pub async fn build(pool: Pool, cfg: Config) -> Result<Router> {
|
||||
let pack_defs = Arc::new(load_pack_definitions(&cfg.data_dir)?);
|
||||
let obj_defs = Arc::new(load_objective_definitions(&cfg.data_dir)?);
|
||||
let sbc_defs = Arc::new(load_sbc_definitions(&cfg.data_dir)?);
|
||||
let event_defs = Arc::new(load_event_definitions(&cfg.data_dir)?);
|
||||
|
||||
tracing::info!(
|
||||
"Loaded {} cards, {} packs, {} objectives, {} SBCs",
|
||||
"Loaded {} cards, {} packs, {} objectives, {} SBCs, {} events",
|
||||
card_db.cards.len(),
|
||||
pack_defs.len(),
|
||||
obj_defs.len(),
|
||||
sbc_defs.len(),
|
||||
event_defs.len(),
|
||||
);
|
||||
|
||||
let state = AppState {
|
||||
@@ -54,19 +59,22 @@ pub async fn build(pool: Pool, cfg: Config) -> Result<Router> {
|
||||
pack_defs,
|
||||
obj_defs: obj_defs.clone(),
|
||||
sbc_defs,
|
||||
event_defs: event_defs.clone(),
|
||||
};
|
||||
|
||||
// Background task: refresh NPC market at startup and every 24 hours
|
||||
{
|
||||
let pool = pool.clone();
|
||||
let card_db = card_db.clone();
|
||||
let event_defs_bg = event_defs.clone();
|
||||
tokio::spawn(async move {
|
||||
if let Err(e) = market::refresh_npc_listings(&pool, &card_db).await {
|
||||
if let Err(e) = market::refresh_npc_listings(&pool, &card_db, &event_defs_bg).await {
|
||||
tracing::warn!("initial market refresh failed: {e}");
|
||||
}
|
||||
loop {
|
||||
tokio::time::sleep(tokio::time::Duration::from_secs(86_400)).await;
|
||||
if let Err(e) = market::refresh_npc_listings(&pool, &card_db).await {
|
||||
if let Err(e) = market::refresh_npc_listings(&pool, &card_db, &event_defs_bg).await
|
||||
{
|
||||
tracing::warn!("market refresh failed: {e}");
|
||||
}
|
||||
}
|
||||
@@ -148,6 +156,16 @@ pub async fn build(pool: Pool, cfg: Config) -> Result<Router> {
|
||||
.route("/settings", get(routes::settings::get_settings))
|
||||
.route("/settings", put(routes::settings::put_settings))
|
||||
.route("/draft/squad", get(routes::draft::get_draft_squad))
|
||||
.route("/events", get(routes::events::get_events))
|
||||
.route("/events/:event_id", get(routes::events::get_event))
|
||||
.route(
|
||||
"/events/:event_id/activate",
|
||||
post(routes::events::post_activate_event),
|
||||
)
|
||||
.route(
|
||||
"/events/:event_id/deactivate",
|
||||
post(routes::events::post_deactivate_event),
|
||||
)
|
||||
// Layers run outermost-first (last added = outermost).
|
||||
// CORS → body limit → concurrency limit → request ID → trace
|
||||
.layer(PropagateRequestIdLayer::x_request_id())
|
||||
|
||||
Reference in New Issue
Block a user