feat: upgrade needed ressources and fight for each quest

This commit is contained in:
2026-04-25 14:39:16 +02:00
parent de6550cee4
commit 0e577b8efd
13 changed files with 560 additions and 2 deletions

View File

@ -2,6 +2,9 @@ use rusqlite::{Connection, Result, params};
use serde::{Deserialize, Serialize};
use chrono::Utc;
use uuid::Uuid;
use std::collections::HashMap;
use crate::parser::CombatIndicator;
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct Profile {
@ -76,10 +79,125 @@ pub fn migrate(conn: &Connection) -> Result<()> {
PRIMARY KEY (profile_id, resource_name),
FOREIGN KEY (profile_id) REFERENCES profiles(id) ON DELETE CASCADE
);
CREATE TABLE IF NOT EXISTS quest_previews (
quest_url TEXT PRIMARY KEY,
indicators_json TEXT NOT NULL,
cached_at TEXT NOT NULL DEFAULT (datetime('now'))
);
")?;
Ok(())
}
pub fn get_cached_previews(conn: &Connection, urls: &[String]) -> HashMap<String, Vec<CombatIndicator>> {
if urls.is_empty() {
return HashMap::new();
}
// Construit les placeholders : (?1, ?2, …)
let placeholders: String = (1..=urls.len())
.map(|i| format!("?{}", i))
.collect::<Vec<_>>()
.join(", ");
let sql = format!(
"SELECT quest_url, indicators_json FROM quest_previews WHERE quest_url IN ({})",
placeholders
);
let mut stmt = match conn.prepare(&sql) {
Ok(s) => s,
Err(_) => return HashMap::new(),
};
// rusqlite attend &dyn ToSql — on construit un vecteur de références
let params_vec: Vec<&dyn rusqlite::types::ToSql> = urls
.iter()
.map(|u| u as &dyn rusqlite::types::ToSql)
.collect();
let rows = match stmt.query_map(params_vec.as_slice(), |row| {
Ok((row.get::<_, String>(0)?, row.get::<_, String>(1)?))
}) {
Ok(r) => r,
Err(_) => return HashMap::new(),
};
let mut map = HashMap::new();
for row in rows.flatten() {
let (url, json) = row;
if let Ok(indicators) = serde_json::from_str::<Vec<CombatIndicator>>(&json) {
map.insert(url, indicators);
}
}
map
}
pub fn upsert_preview(conn: &Connection, url: &str, indicators: &[CombatIndicator]) -> Result<()> {
let json = serde_json::to_string(indicators).unwrap_or_else(|_| "[]".to_string());
let now = Utc::now().to_rfc3339();
conn.execute(
"INSERT INTO quest_previews (quest_url, indicators_json, cached_at) VALUES (?1, ?2, ?3)
ON CONFLICT(quest_url) DO UPDATE SET indicators_json=excluded.indicators_json, cached_at=excluded.cached_at",
params![url, json, now],
)?;
Ok(())
}
/// Retourne l'ensemble des URLs déjà présentes dans quest_previews parmi celles fournies.
pub fn get_cached_urls(conn: &Connection, urls: &[String]) -> std::collections::HashSet<String> {
if urls.is_empty() {
return std::collections::HashSet::new();
}
let placeholders: String = (1..=urls.len())
.map(|i| format!("?{}", i))
.collect::<Vec<_>>()
.join(", ");
let sql = format!(
"SELECT quest_url FROM quest_previews WHERE quest_url IN ({})",
placeholders
);
let mut stmt = match conn.prepare(&sql) {
Ok(s) => s,
Err(_) => return std::collections::HashSet::new(),
};
let params_vec: Vec<&dyn rusqlite::types::ToSql> = urls
.iter()
.map(|u| u as &dyn rusqlite::types::ToSql)
.collect();
let rows = match stmt.query_map(params_vec.as_slice(), |row| row.get::<_, String>(0)) {
Ok(r) => r,
Err(_) => return std::collections::HashSet::new(),
};
rows.flatten().collect()
}
/// Charge un guide depuis la DB à partir de son gid.
pub fn get_guide(conn: &Connection, gid: &str) -> Result<Option<crate::parser::GuideData>> {
let result = conn.query_row(
"SELECT data FROM guides WHERE gid = ?1",
params![gid],
|row| row.get::<_, String>(0),
);
match result {
Ok(json) => {
let data = serde_json::from_str(&json)
.map_err(|e| rusqlite::Error::FromSqlConversionFailure(
0,
rusqlite::types::Type::Text,
Box::new(e),
))?;
Ok(Some(data))
}
Err(rusqlite::Error::QueryReturnedNoRows) => Ok(None),
Err(e) => Err(e),
}
}
pub fn get_profiles(conn: &Connection) -> Result<Vec<Profile>> {
let mut stmt = conn.prepare("SELECT id, name, created_at FROM profiles ORDER BY created_at ASC")?;
let rows = stmt.query_map([], |row| {