Add a Recipe/steps field to brew logging
Test and deploy / test-and-deploy (push) Successful in 1m16s

Free-text recipe (bloom, pours, timings) distinct from tasting notes and
next-time notes: new brews.recipe column (migration 007), form textarea,
shown under the recipe summary in brew history, carried by Brew again,
included in backups and the OpenAPI spec.

Co-Authored-By: Claude Fable 5 <[email protected]>
This commit is contained in:
Shane Maynard
2026-08-09 08:02:06 -04:00
co-authored by Claude Fable 5
parent 16d5af83f7
commit f781930832
7 changed files with 40 additions and 9 deletions
+9 -5
View File
@@ -1657,6 +1657,7 @@ export function createApp({
brewTimeS: row.brew_time_s,
bloomTimeS: row.bloom_time_s,
rating: row.rating == null ? null : Number(row.rating),
recipe: row.recipe,
tastingNotes: row.tasting_notes,
notes: row.notes,
brewedAt: row.brewed_at,
@@ -1698,6 +1699,7 @@ export function createApp({
for (const [field, name] of [
["grinder", "grinder"],
["grindSetting", "grind_setting"],
["recipe", "recipe"],
["tastingNotes", "tasting_notes"],
["notes", "notes"],
])
@@ -1749,8 +1751,8 @@ export function createApp({
const row = (
await db.query(
`INSERT INTO brews
(user_id,bean_id,method,dose_g,water_g,yield_g,grinder,grind_setting,water_temp_c,brew_time_s,bloom_time_s,rating,tasting_notes,notes)
VALUES($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14) RETURNING *`,
(user_id,bean_id,method,dose_g,water_g,yield_g,grinder,grind_setting,water_temp_c,brew_time_s,bloom_time_s,rating,recipe,tasting_notes,notes)
VALUES($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14,$15) RETURNING *`,
[
req.user.id,
beanId,
@@ -1764,6 +1766,7 @@ export function createApp({
v.brew_time_s,
v.bloom_time_s,
v.rating,
v.recipe,
v.tasting_notes,
v.notes,
],
@@ -1816,8 +1819,8 @@ export function createApp({
await db.query(
`UPDATE brews SET bean_id=$1, method=$2, dose_g=$3, water_g=$4, yield_g=$5,
grinder=$6, grind_setting=$7, water_temp_c=$8, brew_time_s=$9, bloom_time_s=$10,
rating=$11, tasting_notes=$12, notes=$13, updated_at=now()
WHERE id=$14 AND user_id=$15 RETURNING *`,
rating=$11, recipe=$12, tasting_notes=$13, notes=$14, updated_at=now()
WHERE id=$15 AND user_id=$16 RETURNING *`,
[
beanId,
v.method,
@@ -1830,6 +1833,7 @@ export function createApp({
v.brew_time_s,
v.bloom_time_s,
v.rating,
v.recipe,
v.tasting_notes,
v.notes,
req.params.id,
@@ -2382,7 +2386,7 @@ export function createApp({
[
"id", "user_id", "bean_id", "method", "dose_g", "water_g", "yield_g", "grinder",
"grind_setting", "water_temp_c", "brew_time_s", "bloom_time_s", "rating",
"tasting_notes", "notes", "brewed_at", "created_at", "updated_at",
"recipe", "tasting_notes", "notes", "brewed_at", "created_at", "updated_at",
],
],
["api_tokens", ["token_hash", "user_id", "name", "created_at", "last_used_at"]],
+5 -3
View File
@@ -44,15 +44,17 @@ const brew = obj({
id: str, beanId: { ...str, nullable: true }, beanName: { ...str, nullable: true },
method: str, doseG: num, waterG: num, yieldG: num, grinder: str, grindSetting: str,
waterTempC: num, brewTimeS: { type: "integer", nullable: true },
bloomTimeS: { type: "integer", nullable: true }, rating: num, tastingNotes: str,
notes: str, brewedAt: str,
bloomTimeS: { type: "integer", nullable: true }, rating: num, recipe: str,
tastingNotes: str, notes: str, brewedAt: str,
});
const brewBody = obj({
beanId: { ...str, nullable: true },
method: { ...str, description: "One of the brew-method keys from GET /api/brew-methods" },
doseG: num, waterG: num, yieldG: num, grinder: str, grindSetting: str, waterTempC: num,
brewTimeS: { type: "integer", nullable: true }, bloomTimeS: { type: "integer", nullable: true },
rating: { ...num, description: "0-10" }, tastingNotes: str, notes: str,
rating: { ...num, description: "0-10" },
recipe: { ...str, description: "Free-text steps (bloom, pours, timings)" },
tastingNotes: str, notes: str,
});
const lot = obj({
id: str, origin: str, variety: str, process: str, producer: str,