f0dbabc409
CI / Build, lint & test (push) Failing after 58s
New notifications table (migration 0009) stores event-driven alerts alongside the existing dynamic state notifications (unclaimed objectives, expiring loans, season ending soon). Persistent notifications are created automatically during match processing: one per level gained, one per expired loan card, one per objective newly completed, and one when a season ends (with promotion/relegation result and rewards in the body). GET /notifications now returns a merged list — persistent entries (newest-first, limit 50) followed by dynamic entries — plus an unread_count for the badge. Each item carries type, title, body, is_read, and (for persistent) id and created_at. PATCH /notifications/:id/read marks a single persistent notification read. POST /notifications/read-all marks all persistent ones read. Four new tests: unread_count field, level-up notification creation, mark-all-read, single-read PATCH. Core now at 77 tests. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
50 lines
1.4 KiB
Rust
50 lines
1.4 KiB
Rust
use crate::{db::Pool, error::AppResult, models::notification::Notification};
|
|
|
|
pub async fn create(pool: &Pool, kind: &str, title: &str, body: &str) -> AppResult<()> {
|
|
let n = Notification::new(kind, title, body);
|
|
sqlx::query(
|
|
"INSERT INTO notifications (id, kind, title, body, is_read, created_at) \
|
|
VALUES (?, ?, ?, ?, 0, ?)",
|
|
)
|
|
.bind(&n.id)
|
|
.bind(&n.kind)
|
|
.bind(&n.title)
|
|
.bind(&n.body)
|
|
.bind(&n.created_at)
|
|
.execute(pool)
|
|
.await?;
|
|
Ok(())
|
|
}
|
|
|
|
pub async fn list(pool: &Pool) -> AppResult<Vec<Notification>> {
|
|
sqlx::query_as::<_, Notification>(
|
|
"SELECT id, kind, title, body, is_read, created_at \
|
|
FROM notifications ORDER BY created_at DESC LIMIT 50",
|
|
)
|
|
.fetch_all(pool)
|
|
.await
|
|
.map_err(Into::into)
|
|
}
|
|
|
|
pub async fn mark_read(pool: &Pool, id: &str) -> AppResult<bool> {
|
|
let r = sqlx::query("UPDATE notifications SET is_read = 1 WHERE id = ?")
|
|
.bind(id)
|
|
.execute(pool)
|
|
.await?;
|
|
Ok(r.rows_affected() > 0)
|
|
}
|
|
|
|
pub async fn mark_all_read(pool: &Pool) -> AppResult<i64> {
|
|
let r = sqlx::query("UPDATE notifications SET is_read = 1 WHERE is_read = 0")
|
|
.execute(pool)
|
|
.await?;
|
|
Ok(r.rows_affected() as i64)
|
|
}
|
|
|
|
pub async fn unread_count(pool: &Pool) -> AppResult<i64> {
|
|
sqlx::query_scalar("SELECT COUNT(*) FROM notifications WHERE is_read = 0")
|
|
.fetch_one(pool)
|
|
.await
|
|
.map_err(Into::into)
|
|
}
|