Academy v4: 3 research-backed lessons (30 slides + audio); new bean logo; custom plan/lot names
Test and deploy / test-and-deploy (push) Successful in 59s

Co-Authored-By: Claude Fable 5 <[email protected]>
This commit is contained in:
Shane Maynard
2026-08-09 14:30:42 -04:00
co-authored by Claude Fable 5
parent ec078116b4
commit 8b80c3fa6f
65 changed files with 1315 additions and 78 deletions
+45 -22
View File
@@ -752,7 +752,7 @@ export function createApp({
ok: true,
plans: (
await db.query(
"SELECT id,plan,created_at,updated_at FROM roast_plans WHERE user_id=$1 ORDER BY updated_at DESC",
"SELECT id,plan,name,created_at,updated_at FROM roast_plans WHERE user_id=$1 ORDER BY updated_at DESC",
[req.user.id],
)
).rows,
@@ -765,10 +765,11 @@ export function createApp({
try {
if (!req.body.plan || typeof req.body.plan !== "object")
return res.status(400).json({ ok: false, code: "bad_plan" });
const name = String(req.body.name ?? "").trim().slice(0, 120);
const p = (
await db.query(
"INSERT INTO roast_plans(user_id,plan) VALUES($1,$2) RETURNING id,plan,created_at,updated_at",
[req.user.id, req.body.plan],
"INSERT INTO roast_plans(user_id,plan,name) VALUES($1,$2,$3) RETURNING id,plan,name,created_at,updated_at",
[req.user.id, req.body.plan, name],
)
).rows[0];
res.status(201).json({ ok: true, plan: p });
@@ -783,11 +784,27 @@ export function createApp({
requireUuidParam("id"),
async (req, res, next) => {
try {
if (!req.body.plan || typeof req.body.plan !== "object")
const hasPlan = req.body.plan !== undefined;
const hasName = req.body.name !== undefined;
if (hasPlan && (!req.body.plan || typeof req.body.plan !== "object"))
return res.status(400).json({ ok: false, code: "bad_plan" });
if (!hasPlan && !hasName)
return res.status(400).json({ ok: false, code: "bad_plan" });
const existing = hasPlan && hasName
? null
: (
await db.query(
"SELECT plan,name FROM roast_plans WHERE id=$1 AND user_id=$2",
[req.params.id, req.user.id],
)
).rows[0];
const plan = hasPlan ? req.body.plan : existing?.plan;
const name = hasName
? String(req.body.name ?? "").trim().slice(0, 120)
: (existing?.name ?? "");
const r = await db.query(
"UPDATE roast_plans SET plan=$1,updated_at=now() WHERE id=$2 AND user_id=$3 RETURNING id,plan,updated_at",
[req.body.plan, req.params.id, req.user.id],
"UPDATE roast_plans SET plan=$1,name=$2,updated_at=now() WHERE id=$3 AND user_id=$4 RETURNING id,plan,name,updated_at",
[plan, name, req.params.id, req.user.id],
);
if (!r.rowCount)
return res.status(404).json({ ok: false, code: "not_found" });
@@ -855,6 +872,7 @@ export function createApp({
}
const toLotRow = (row) => ({
id: row.id,
name: row.name,
origin: row.origin,
variety: row.variety,
process: row.process,
@@ -897,14 +915,16 @@ export function createApp({
} catch (e) {
return res.status(400).json({ ok: false, code: "bad_lot", error: e.message });
}
const name = String(req.body.name ?? "").trim().slice(0, 120);
const row = (
await db.query(
`INSERT INTO green_bean_lots
(user_id,origin,variety,process,producer,purchase_date,initial_weight_g,remaining_weight_g,cost_total,moisture_pct,density_g_l,notes)
VALUES($1,$2,$3,$4,$5,$6,$7,$7,$8,$9,$10,$11) RETURNING *`,
(user_id,origin,name,variety,process,producer,purchase_date,initial_weight_g,remaining_weight_g,cost_total,moisture_pct,density_g_l,notes)
VALUES($1,$2,$3,$4,$5,$6,$7,$8,$8,$9,$10,$11,$12) RETURNING *`,
[
req.user.id,
origin,
name,
String(req.body.variety || ""),
String(req.body.process || ""),
String(req.body.producer || ""),
@@ -937,7 +957,7 @@ export function createApp({
if (!lot) return res.status(404).json({ ok: false, code: "not_found" });
const log = (
await db.query(
`SELECT c.id,c.weight_g,c.roast_plan_id,c.created_at,p.plan->'fields'->>'0.1' AS plan_title
`SELECT c.id,c.weight_g,c.roast_plan_id,c.created_at,CASE WHEN COALESCE(p.name,'')<>'' THEN p.name ELSE p.plan->'fields'->>'0.1' END AS plan_title
FROM bean_consumption c LEFT JOIN roast_plans p ON p.id=c.roast_plan_id AND p.user_id=c.user_id
WHERE c.lot_id=$1 ORDER BY c.created_at DESC`,
[req.params.id],
@@ -984,13 +1004,13 @@ export function createApp({
await db.query(
`SELECT one_change, plan_title, updated_at FROM (
SELECT plan->'afterRoast'->>'oneChange' AS one_change,
plan->'fields'->>'0.1' AS plan_title, updated_at
CASE WHEN COALESCE(name,'')<>'' THEN name ELSE plan->'fields'->>'0.1' END AS plan_title, updated_at
FROM roast_plans
WHERE user_id=$1 AND plan->'inventory'->>'lotId'=$2
AND coalesce(plan->'afterRoast'->>'oneChange','')<>''
UNION ALL
SELECT a.after->>'oneChange' AS one_change,
p.plan->'fields'->>'0.1' AS plan_title, a.updated_at
CASE WHEN COALESCE(p.name,'')<>'' THEN p.name ELSE p.plan->'fields'->>'0.1' END AS plan_title, a.updated_at
FROM actual_roasts a
JOIN roast_plans p ON p.id=a.roast_plan_id AND p.user_id=a.user_id
WHERE a.user_id=$1 AND p.plan->'inventory'->>'lotId'=$2
@@ -1061,17 +1081,20 @@ export function createApp({
// computed inside the UPDATE itself (against the pre-statement initial_weight_g)
// rather than from the `existing` row read above, so a concurrent PUT can't lose
// the other's delta the way a JS-side read-then-write would.
const name =
b.name === undefined ? existing.name : String(b.name || "").trim().slice(0, 120);
const row = (
await db.query(
`UPDATE green_bean_lots SET
origin=$1, variety=$2, process=$3, producer=$4, purchase_date=$5,
remaining_weight_g = remaining_weight_g + COALESCE($6::numeric, initial_weight_g) - initial_weight_g,
initial_weight_g = COALESCE($6::numeric, initial_weight_g),
cost_total=$7, moisture_pct=$8, density_g_l=$9, notes=$10, archived=$11,
origin=$1, name=$2, variety=$3, process=$4, producer=$5, purchase_date=$6,
remaining_weight_g = remaining_weight_g + COALESCE($7::numeric, initial_weight_g) - initial_weight_g,
initial_weight_g = COALESCE($7::numeric, initial_weight_g),
cost_total=$8, moisture_pct=$9, density_g_l=$10, notes=$11, archived=$12,
updated_at=now()
WHERE id=$12 AND user_id=$13 RETURNING *`,
WHERE id=$13 AND user_id=$14 RETURNING *`,
[
origin,
name,
b.variety === undefined ? existing.variety : String(b.variety || ""),
b.process === undefined ? existing.process : String(b.process || ""),
b.producer === undefined ? existing.producer : String(b.producer || ""),
@@ -1611,7 +1634,7 @@ export function createApp({
const rows = (
await db.query(
`SELECT a.id,a.roast_plan_id,a.roaster_id,a.filename,a.parsed,a.evaluation,a.evaluation_status,a.evaluation_error,a.created_at,
p.plan->'fields'->>'0.1' AS plan_title, m.name AS roaster_name
CASE WHEN COALESCE(p.name,'')<>'' THEN p.name ELSE p.plan->'fields'->>'0.1' END AS plan_title, m.name AS roaster_name
FROM actual_roasts a
LEFT JOIN roast_plans p ON p.id=a.roast_plan_id AND p.user_id=a.user_id
LEFT JOIN roasters m ON m.id=a.roaster_id AND m.user_id=a.user_id
@@ -1633,7 +1656,7 @@ export function createApp({
try {
const row = (
await db.query(
`SELECT a.*, p.plan->'fields'->>'0.1' AS plan_title, p.plan, m.name AS roaster_name
`SELECT a.*, CASE WHEN COALESCE(p.name,'')<>'' THEN p.name ELSE p.plan->'fields'->>'0.1' END AS plan_title, p.plan, m.name AS roaster_name
FROM actual_roasts a
LEFT JOIN roast_plans p ON p.id=a.roast_plan_id AND p.user_id=a.user_id
LEFT JOIN roasters m ON m.id=a.roaster_id AND m.user_id=a.user_id
@@ -1657,7 +1680,7 @@ export function createApp({
const row = (
await db.query(
`SELECT a.filename, a.original_content, a.after, a.evaluation, a.parsed,
p.plan->'fields'->>'0.1' AS plan_title, a.roast_plan_id
CASE WHEN COALESCE(p.name,'')<>'' THEN p.name ELSE p.plan->'fields'->>'0.1' END AS plan_title, a.roast_plan_id
FROM actual_roasts a
LEFT JOIN roast_plans p ON p.id=a.roast_plan_id AND p.user_id=a.user_id
WHERE a.id=$1 AND a.user_id=$2`,
@@ -2369,7 +2392,7 @@ export function createApp({
const rows = (
await db.query(
`SELECT s.id,s.roast_plan_id,s.data,s.total_score,s.created_at,s.updated_at,
p.plan->'fields'->>'0.1' AS plan_title
CASE WHEN COALESCE(p.name,'')<>'' THEN p.name ELSE p.plan->'fields'->>'0.1' END AS plan_title
FROM cupping_sessions s LEFT JOIN roast_plans p ON p.id=s.roast_plan_id AND p.user_id=s.user_id
WHERE s.user_id=$1 AND ($2::uuid IS NULL OR s.roast_plan_id=$2)
ORDER BY s.updated_at DESC`,
@@ -2423,7 +2446,7 @@ export function createApp({
try {
const row = (
await db.query(
`SELECT s.*, p.plan->'fields'->>'0.1' AS plan_title
`SELECT s.*, CASE WHEN COALESCE(p.name,'')<>'' THEN p.name ELSE p.plan->'fields'->>'0.1' END AS plan_title
FROM cupping_sessions s LEFT JOIN roast_plans p ON p.id=s.roast_plan_id AND p.user_id=s.user_id
WHERE s.id=$1 AND s.user_id=$2`,
[req.params.id, req.user.id],
@@ -2753,7 +2776,7 @@ export function createApp({
: null;
const rows = (
await db.query(
`SELECT p.id,p.plan->'fields'->>'0.1' AS title,p.updated_at,u.id AS user_id,u.email
`SELECT p.id,CASE WHEN COALESCE(p.name,'')<>'' THEN p.name ELSE p.plan->'fields'->>'0.1' END AS title,p.updated_at,u.id AS user_id,u.email
FROM roast_plans p JOIN users u ON u.id=p.user_id
WHERE $1::uuid IS NULL OR p.user_id=$1
ORDER BY p.updated_at DESC`,