Phase 9: division/season tracking, club customization, pack history, notifications
CI / Build, lint & test (push) Failing after 1m42s

- GET /division returns live season stats (points, record, promotion threshold)
- PUT /club allows updating club name and manager_name
- GET /packs/history returns opened packs with full card definitions
- GET /notifications dynamically surfaces completed objectives, expiring loans, season end
- Club model gains manager_name column (migration 0006 already added it)
- Pack model gains opened_cards and opened_at; pack SELECT queries updated
- 9 new integration tests — all 45 pass, clippy clean

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
funman300
2026-06-25 16:50:35 -07:00
parent bfd6de6896
commit a749fba93c
11 changed files with 412 additions and 11 deletions
+46 -8
View File
@@ -5,19 +5,30 @@ use crate::{
};
use chrono::Utc;
const CLUB_SELECT: &str =
"SELECT id, profile_id, name, coins, level, created_at, updated_at, manager_name \
FROM clubs";
pub async fn get_club_by_profile(pool: &Pool, profile_id: &str) -> AppResult<Club> {
sqlx::query_as::<_, Club>(
"SELECT id, profile_id, name, coins, level, created_at, updated_at FROM clubs WHERE profile_id = ? LIMIT 1"
)
.bind(profile_id)
.fetch_optional(pool)
.await?
.ok_or_else(|| AppError::NotFound("club not found".into()))
sqlx::query_as::<_, Club>(&format!("{CLUB_SELECT} WHERE profile_id = ? LIMIT 1"))
.bind(profile_id)
.fetch_optional(pool)
.await?
.ok_or_else(|| AppError::NotFound("club not found".into()))
}
pub async fn get_club_by_id(pool: &Pool, club_id: &str) -> AppResult<Club> {
sqlx::query_as::<_, Club>(&format!("{CLUB_SELECT} WHERE id = ? LIMIT 1"))
.bind(club_id)
.fetch_optional(pool)
.await?
.ok_or_else(|| AppError::NotFound("club not found".into()))
}
pub async fn create_club(pool: &Pool, club: &Club) -> AppResult<()> {
sqlx::query(
"INSERT INTO clubs (id, profile_id, name, coins, level, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?)"
"INSERT INTO clubs (id, profile_id, name, coins, level, created_at, updated_at, manager_name) \
VALUES (?, ?, ?, ?, ?, ?, ?, ?)",
)
.bind(&club.id)
.bind(&club.profile_id)
@@ -26,11 +37,38 @@ pub async fn create_club(pool: &Pool, club: &Club) -> AppResult<()> {
.bind(club.level)
.bind(club.created_at)
.bind(club.updated_at)
.bind(&club.manager_name)
.execute(pool)
.await?;
Ok(())
}
pub async fn update_club(
pool: &Pool,
club_id: &str,
name: Option<&str>,
manager_name: Option<&str>,
) -> AppResult<Club> {
let now = Utc::now();
if let Some(n) = name {
sqlx::query("UPDATE clubs SET name = ?, updated_at = ? WHERE id = ?")
.bind(n)
.bind(now)
.bind(club_id)
.execute(pool)
.await?;
}
if let Some(m) = manager_name {
sqlx::query("UPDATE clubs SET manager_name = ?, updated_at = ? WHERE id = ?")
.bind(m)
.bind(now)
.bind(club_id)
.execute(pool)
.await?;
}
get_club_by_id(pool, club_id).await
}
pub async fn add_coins(pool: &Pool, club_id: &str, amount: i64) -> AppResult<i64> {
let now = Utc::now();
sqlx::query("UPDATE clubs SET coins = coins + ?, updated_at = ? WHERE id = ?")
+11 -3
View File
@@ -39,6 +39,8 @@ pub async fn grant_pack(pool: &Pool, club_id: &str, definition_id: &str) -> AppR
definition_id: definition_id.to_string(),
opened: false,
created_at: chrono::Utc::now().to_rfc3339(),
opened_cards: None,
opened_at: None,
};
sqlx::query(
"INSERT INTO packs (id, club_id, definition_id, opened, created_at) VALUES (?, ?, ?, ?, ?)",
@@ -61,7 +63,7 @@ pub async fn open_pack(
pack_id: &str,
) -> AppResult<PackOpenResult> {
let pack = sqlx::query_as::<_, Pack>(
"SELECT id, club_id, definition_id, opened, created_at FROM packs WHERE id = ? AND club_id = ?"
"SELECT id, club_id, definition_id, opened, created_at, opened_cards, opened_at FROM packs WHERE id = ? AND club_id = ?"
)
.bind(pack_id)
.bind(club_id)
@@ -122,7 +124,13 @@ pub async fn open_pack(
}
}
sqlx::query("UPDATE packs SET opened = 1 WHERE id = ?")
let card_ids_json = serde_json::to_string(&cards.iter().map(|c| &c.id).collect::<Vec<_>>())
.unwrap_or_default();
let now = chrono::Utc::now().to_rfc3339();
sqlx::query("UPDATE packs SET opened = 1, opened_cards = ?, opened_at = ? WHERE id = ?")
.bind(&card_ids_json)
.bind(&now)
.bind(pack_id)
.execute(pool)
.await?;
@@ -152,7 +160,7 @@ pub async fn buy_pack(
pub async fn get_unopened_packs(pool: &Pool, club_id: &str) -> AppResult<Vec<Pack>> {
let packs = sqlx::query_as::<_, Pack>(
"SELECT id, club_id, definition_id, opened, created_at FROM packs WHERE club_id = ? AND opened = 0"
"SELECT id, club_id, definition_id, opened, created_at, opened_cards, opened_at FROM packs WHERE club_id = ? AND opened = 0"
)
.bind(club_id)
.fetch_all(pool)