mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-12 14:16:12 +00:00
feat(api): add source filter to measurement and mood lists
Add a source filter to both management lists. Mood gains an optional `source` param (validated against the mood source set); measurements gain `sourceEq` (validated against MeasurementSource) — the `source` key on the measurements list is the rollup-tier opt-in, so the filter rides a distinct param. Both thread into the list `where` alongside the pinned `userId` + `deletedAt: null`. Migration 0136 adds two additive indexes — `(userId, source, measuredAt)` on measurements and `(userId, source, moodLoggedAt)` on mood — so a source filter + recency sort is an index range scan rather than a userId scan plus in-memory sort at 100-200k rows per user. Pure `CREATE INDEX IF NOT EXISTS`, no backfill. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
1fd875b83c
commit
0d69611bbf
8 files changed
+117
-1
No files matched your search
@@ -236,6 +236,48 @@ describe("measurement validation", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("listMeasurementsSchema — v1.15.13 sourceEq filter", () => {
|
||||
it("accepts a valid MeasurementSource on sourceEq", () => {
|
||||
const parsed = listMeasurementsSchema.safeParse({ sourceEq: "WITHINGS" });
|
||||
expect(parsed.success).toBe(true);
|
||||
if (parsed.success) {
|
||||
expect(parsed.data.sourceEq).toBe("WITHINGS");
|
||||
}
|
||||
});
|
||||
|
||||
it("leaves sourceEq undefined when omitted", () => {
|
||||
const parsed = listMeasurementsSchema.safeParse({});
|
||||
expect(parsed.success).toBe(true);
|
||||
if (parsed.success) {
|
||||
expect(parsed.data.sourceEq).toBeUndefined();
|
||||
}
|
||||
});
|
||||
|
||||
it("rejects an unknown sourceEq value", () => {
|
||||
const parsed = listMeasurementsSchema.safeParse({ sourceEq: "BOGUS" });
|
||||
expect(parsed.success).toBe(false);
|
||||
});
|
||||
|
||||
it("rejects the rollup opt-in value on sourceEq (distinct from source)", () => {
|
||||
// `rollup` is the rollup-tier opt-in for `source`, NOT a valid
|
||||
// MeasurementSource filter — sourceEq must not accept it.
|
||||
const parsed = listMeasurementsSchema.safeParse({ sourceEq: "rollup" });
|
||||
expect(parsed.success).toBe(false);
|
||||
});
|
||||
|
||||
it("accepts source=rollup and sourceEq together (independent params)", () => {
|
||||
const parsed = listMeasurementsSchema.safeParse({
|
||||
source: "rollup",
|
||||
sourceEq: "APPLE_HEALTH",
|
||||
});
|
||||
expect(parsed.success).toBe(true);
|
||||
if (parsed.success) {
|
||||
expect(parsed.data.source).toBe("rollup");
|
||||
expect(parsed.data.sourceEq).toBe("APPLE_HEALTH");
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// v1.4.38 — the per-day drill-down branch always returns at most
|
||||
// 1000 rows (one pathological phone-only stepCount day). Push the
|
||||
// cap into the validator so a caller asking for more sees a 422
|
||||
|
||||
@@ -586,6 +586,12 @@ export const listMeasurementsSchema = z
|
||||
// when the rollup bucket set is empty for the requested window so
|
||||
// brand-new accounts still see correct data on their first chart.
|
||||
source: z.enum(["rollup"]).optional(),
|
||||
// v1.15.13 — management-list source FILTER. The `source` key above is
|
||||
// the rollup-tier opt-in (`["rollup"]`), NOT a source filter, so the
|
||||
// filter rides a distinct param. Validated against `MeasurementSource`;
|
||||
// threaded into the plain-list `where`. Backed by the `(userId, source,
|
||||
// measuredAt)` index (migration 0136).
|
||||
sourceEq: measurementSourceEnum.optional(),
|
||||
// v1.4.37 W7c — list-view "one row per day" mode for cumulative
|
||||
// types (steps, active energy, distance, flights, daylight). When
|
||||
// `groupBy=day` is set and `type` is a cumulative HK type, the route
|
||||
|
||||
@@ -165,6 +165,11 @@ export const updateMoodEntrySchema = z.object({
|
||||
|
||||
export const listMoodEntriesSchema = z.object({
|
||||
mood: moodLevelEnum.optional(),
|
||||
// v1.15.13 — source filter for the management list. Validated against
|
||||
// the mood source set; threaded into the list `where` (the `userId` +
|
||||
// `deletedAt: null` pins stay). Backed by the `(userId, source,
|
||||
// moodLoggedAt)` index (migration 0136).
|
||||
source: moodSourceEnum.optional(),
|
||||
from: z
|
||||
.string()
|
||||
.regex(/^\d{4}-\d{2}-\d{2}$/)
|
||||
|
||||
Reference in new issue
Block a user