5.4 KiB
+++ title = "Filter quotes by date range (before/after with year/month/day granularity)" priority = 5 status = "done" ticket_type = "feature" dependencies = [] +++
Feature
Extend GET /api/quotes with date range filtering. Users can specify a "before" and/or "after" bound, with year/month/day granularity for each. Quotes without a date (date IS NULL) are excluded when a date filter is active.
Query parameter design
Six optional query params are added:
| Param | Type | Description |
|---|---|---|
date_after_year |
u16 | Only include quotes dated on or after this year |
date_after_month |
u8 (1–12) | Narrows after-bound to this month |
date_after_day |
u8 (1–31) | Narrows after-bound to this day |
date_before_year |
u16 | Only include quotes dated on or before this year |
date_before_month |
u8 (1–12) | Narrows before-bound to this month |
date_before_day |
u8 (1–31) | Narrows before-bound to this day |
The handler constructs two partial ISO strings from these fields before calling list_quotes:
- after bound (
>=):YYYY,YYYY-MM, orYYYY-MM-DD— string comparison anchors to the start of the specified period (e.g.,2020means ≥2020-01-01in text comparison, but actually>= '2020'which works correctly since any 2020 date starts with2020). - before bound (
<=):YYYY-MM-DDwhere missing month defaults to12and missing day defaults to31— sodate_before_year=2020means<= '2020-12-31'.
Validation rules:
- Month must be 1–12 if provided (without its year, it is an error).
- Day must be 1–31 if provided (without its year+month, it is an error).
- Return
400 Bad Requestfor invalid combinations.
Part 1 — API: handlers/mod.rs
Extend ListParams:
#[derive(Debug, Deserialize)]
struct ListParams {
#[serde(default = "default_page")]
page: u32,
author: Option<String>,
tag: Option<String>,
// Date range filter
date_after_year: Option<u16>,
date_after_month: Option<u8>,
date_after_day: Option<u8>,
date_before_year: Option<u16>,
date_before_month: Option<u8>,
date_before_day: Option<u8>,
}
In list_handler, construct the date_after and date_before strings before calling repo.list_quotes:
fn build_date_bound(year: Option<u16>, month: Option<u8>, day: Option<u8>) -> Option<String> {
match (year, month, day) {
(None, _, _) => None,
(Some(y), None, _) => Some(format!("{y:04}")),
(Some(y), Some(m), None) => Some(format!("{y:04}-{m:02}")),
(Some(y), Some(m), Some(d)) => Some(format!("{y:04}-{m:02}-{d:02}")),
}
}
Return 400 if month is present without year, or day is present without year+month.
Part 2 — Repository trait: db/mod.rs
Extend list_quotes signature:
async fn list_quotes(
&self,
page: u32,
author: Option<&str>,
tag: Option<&str>,
date_after: Option<&str>, // new
date_before: Option<&str>, // new
) -> Result<ListResult, DbError>;
Part 3 — Native implementation: db/native.rs
In NativeRepository::list_quotes, build the WHERE clause dynamically. Current query filters on author and tag via subquery. Extend it:
-- Additional clauses appended when filters are present:
AND q.date IS NOT NULL
AND q.date >= ? -- when date_after is Some
AND q.date <= ? -- when date_before is Some
Use string comparison — ISO YYYY-MM-DD format sorts lexicographically, so >=/<= on the date TEXT column is correct. Prefix matching (e.g. date >= '2020' with date = '2020-06-15') works because '2020-06-15' >= '2020' is true in SQLite string comparison.
Part 4 — D1 implementation: db/d1.rs
Apply the same WHERE clause extensions to the D1 query builder.
Part 5 — UI: Browse page
File: src/bin/ui/pages/browse.rs
Add date filter controls to the filter panel alongside author and tag filters:
- "After:" year input (
<input type="number" min="0" max="9999">), optional month select (1–12), optional day input. - "Before:" same.
On filter apply, include the populated fields as query params. When fields are empty, omit them.
Part 6 — Mock repo in tests: handlers/mod.rs
Update MockRepo::list_quotes to accept the two new date_after / date_before params (ignore them in the mock — just extend the signature).
Part 7 — OpenAPI spec: api/openapi.yaml
Add the six new query parameters to the GET /api/quotes operation with descriptions and schema: {type: integer}.
Files touched
src/bin/api/handlers/mod.rs—ListParams+list_handler+MockReposrc/bin/api/db/mod.rs—QuoteRepository::list_quotessignaturesrc/bin/api/db/native.rs— SQL query extensionsrc/bin/api/db/d1.rs— SQL query extensionsrc/bin/ui/pages/browse.rs— date filter UIapi/openapi.yaml— new query params
Validation
# From quotesdb/ root
cargo fmt && cargo check && cargo clippy && cargo test
trunk build
redocly lint api/openapi.yaml
Test manually:
GET /api/quotes?date_after_year=1900&date_before_year=2000— should return only quotes with dates in the 20th century.GET /api/quotes?date_after_year=2020&date_after_month=6— on or after June 2020.GET /api/quotes?date_after_month=3— should return 400 (month without year).
Commit scope
feat(quotesdb): date range filter for quotes list