fix: more deviations from spec fixed

This commit is contained in:
2026-04-05 08:57:02 +02:00
parent 75cb19a604
commit ac2afc7d50
17 changed files with 161 additions and 67 deletions

View File

@@ -182,17 +182,26 @@ pub struct PostSearchFilters<'a> {
pub offset: Option<usize>,
}
/// Search posts with filters. Returns matching post IDs.
/// Search result envelope with pagination metadata per spec.
#[derive(Debug)]
pub struct SearchResults {
pub post_ids: Vec<String>,
pub total: usize,
pub offset: usize,
pub limit: usize,
}
/// Search posts with filters. Returns matching post IDs with pagination metadata.
pub fn search_posts_filtered(
conn: &Connection,
query: &str,
language: &str,
filters: &PostSearchFilters,
) -> rusqlite::Result<Vec<String>> {
) -> rusqlite::Result<SearchResults> {
// Get FTS matches first
let fts_ids = search_posts(conn, query, language)?;
if fts_ids.is_empty() {
return Ok(vec![]);
return Ok(SearchResults { post_ids: vec![], total: 0, offset: filters.offset.unwrap_or(0), limit: filters.limit.unwrap_or(0) });
}
// Apply filters by querying posts table
@@ -210,6 +219,28 @@ pub fn search_posts_filtered(
param_idx += 1;
}
if let Some(tags) = filters.tags {
// Filter posts whose JSON tags array contains ALL specified tags (case-insensitive)
for tag in tags {
sql.push_str(&format!(
"AND EXISTS (SELECT 1 FROM json_each(posts.tags) WHERE LOWER(json_each.value) = LOWER(?{param_idx})) "
));
params.push(Box::new(tag.clone()));
param_idx += 1;
}
}
if let Some(categories) = filters.categories {
// Filter posts whose JSON categories array contains ALL specified categories (case-insensitive)
for cat in categories {
sql.push_str(&format!(
"AND EXISTS (SELECT 1 FROM json_each(posts.categories) WHERE LOWER(json_each.value) = LOWER(?{param_idx})) "
));
params.push(Box::new(cat.clone()));
param_idx += 1;
}
}
if let Some(year) = filters.year {
// Filter by year from created_at (unix ms)
let start = chrono::NaiveDate::from_ymd_opt(year, 1, 1).unwrap().and_hms_opt(0, 0, 0).unwrap().and_utc().timestamp_millis();
@@ -234,13 +265,22 @@ pub fn search_posts_filtered(
let _ = param_idx; // suppress unused warning
// First get total count (without LIMIT/OFFSET)
let count_sql = sql.replace("SELECT id FROM posts", "SELECT COUNT(*) FROM posts");
let total: usize = {
let mut stmt = conn.prepare(&count_sql)?;
let params_refs: Vec<&dyn rusqlite::types::ToSql> = params.iter().map(|p| p.as_ref()).collect();
stmt.query_row(params_refs.as_slice(), |row| row.get::<_, usize>(0))?
};
sql.push_str("ORDER BY created_at DESC ");
if let Some(limit) = filters.limit {
let offset = filters.offset.unwrap_or(0);
let limit = filters.limit.unwrap_or(total);
if filters.limit.is_some() {
sql.push_str(&format!("LIMIT {limit} "));
if let Some(offset) = filters.offset {
sql.push_str(&format!("OFFSET {offset} "));
}
sql.push_str(&format!("OFFSET {offset} "));
}
let mut stmt = conn.prepare(&sql)?;
@@ -248,7 +288,8 @@ pub fn search_posts_filtered(
let rows = stmt.query_map(params_refs.as_slice(), |row| {
row.get::<_, String>(0)
})?;
rows.collect()
let post_ids: Vec<String> = rows.collect::<rusqlite::Result<Vec<_>>>()?;
Ok(SearchResults { post_ids, total, offset, limit })
}
/// Search media by full-text query. Returns matching media IDs.
@@ -524,7 +565,8 @@ mod tests {
..Default::default()
};
let results = search_posts_filtered(db.conn(), "post", "en", &filters).unwrap();
assert_eq!(results, vec!["post1"]);
assert_eq!(results.post_ids, vec!["post1"]);
assert_eq!(results.total, 1);
}
#[test]
@@ -557,7 +599,8 @@ mod tests {
..Default::default()
};
let results = search_posts_filtered(db.conn(), "year", "en", &filters).unwrap();
assert_eq!(results, vec!["p2024"]);
assert_eq!(results.post_ids, vec!["p2024"]);
assert_eq!(results.total, 1);
}
#[test]
@@ -583,6 +626,9 @@ mod tests {
..Default::default()
};
let results = search_posts_filtered(db.conn(), "searchable", "en", &filters).unwrap();
assert_eq!(results.len(), 2);
assert_eq!(results.post_ids.len(), 2);
assert_eq!(results.total, 5);
assert_eq!(results.offset, 1);
assert_eq!(results.limit, 2);
}
}

View File

@@ -46,11 +46,13 @@ pub fn get_active_project(conn: &Connection) -> rusqlite::Result<Project> {
}
pub fn set_active_project(conn: &Connection, id: &str) -> rusqlite::Result<()> {
conn.execute("UPDATE projects SET is_active = 0 WHERE is_active = 1", [])?;
conn.execute(
let tx = conn.unchecked_transaction()?;
tx.execute("UPDATE projects SET is_active = 0 WHERE is_active = 1", [])?;
tx.execute(
"UPDATE projects SET is_active = 1 WHERE id = ?1",
params![id],
)?;
tx.commit()?;
Ok(())
}