Address seed-packet review: 413 mapping, deadline, rollback, dedup
Build image / build-and-push (push) Successful in 6s
Build image / build-and-push (push) Successful in 6s
Gadfly findings on #94, the real ones: - scanSeedPacket extends only the READ deadline; a slow upload + a live vision call runs past the server's absolute 30s WriteTimeout and the successful response is silently dropped (the #78 failure mode). Extend the write deadline too (scanWriteTimeout). - An oversized upload tripping MaxBytesReader was mapped to 400; it's 413. Detect *http.MaxBytesError and report IMAGE_TOO_LARGE. - Split imagenorm error mapping: ErrTooLarge->413, ErrUnsupported->400, genuine read/encode faults (and a failed file.Open)->500, not 400. - CreateFromPacket discarded the plant it created when the lot then failed, contradicting its own doc. Roll the new plant back instead so the confirm is all-or-nothing (a fresh plant has no lots/plantings, so the delete is safe; log-and-continue on cleanup failure). - Dedup: packetLotRequest and seedLotCreateRequest shared every lot field. Extract a seedLotFields base both use. validCategory now reuses plantCategories. EffectiveConfig resolves agent+vision from one settings-row read instead of two. - capabilities swallowed an EffectiveVision error silently; log it. - vision test hand-copied Extract's body (drift risk). Split generate() out of Extract so the hermetic test drives the real request builder. Tests: rollback-on-lot-failure (service), oversized->413 (api). Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]> Claude-Session: https://claude.ai/code/session_01H3zbym8Doka2d7D48maSgZ
This commit is contained in:
@@ -16,9 +16,12 @@ import (
|
||||
// the buyer — a lot is never shared along with a garden — so every handler here
|
||||
// scopes to the session actor with no garden in the picture.
|
||||
|
||||
// seedLotCreateRequest is the body for POST /seed-lots.
|
||||
type seedLotCreateRequest struct {
|
||||
PlantID int64 `json:"plantId" binding:"required"`
|
||||
// seedLotFields is the lot half of a create body — every field EXCEPT which plant
|
||||
// it attaches to. seedLotCreateRequest adds a required plantId; the seed-packet
|
||||
// confirm supplies none (the plant comes from its plantId/newPlant choice), so it
|
||||
// embeds these fields directly. Sharing one struct keeps the two request shapes —
|
||||
// and their validation — from drifting apart.
|
||||
type seedLotFields struct {
|
||||
Vendor string `json:"vendor"`
|
||||
SourceURL string `json:"sourceUrl"`
|
||||
SKU string `json:"sku"`
|
||||
@@ -32,15 +35,28 @@ type seedLotCreateRequest struct {
|
||||
Notes string `json:"notes"`
|
||||
}
|
||||
|
||||
func (r seedLotCreateRequest) toInput() service.SeedLotInput {
|
||||
// toInput builds the service input with no plant attribution; callers that know
|
||||
// the plant (the create handler; the packet confirm) set PlantID afterwards.
|
||||
func (f seedLotFields) toInput() service.SeedLotInput {
|
||||
return service.SeedLotInput{
|
||||
PlantID: r.PlantID, Vendor: r.Vendor, SourceURL: r.SourceURL, SKU: r.SKU,
|
||||
LotCode: r.LotCode, PurchasedAt: r.PurchasedAt, PackedForYear: r.PackedForYear,
|
||||
Quantity: r.Quantity, Unit: r.Unit, CostCents: r.CostCents,
|
||||
GerminationPct: r.GerminationPct, Notes: r.Notes,
|
||||
Vendor: f.Vendor, SourceURL: f.SourceURL, SKU: f.SKU, LotCode: f.LotCode,
|
||||
PurchasedAt: f.PurchasedAt, PackedForYear: f.PackedForYear, Quantity: f.Quantity,
|
||||
Unit: f.Unit, CostCents: f.CostCents, GerminationPct: f.GerminationPct, Notes: f.Notes,
|
||||
}
|
||||
}
|
||||
|
||||
// seedLotCreateRequest is the body for POST /seed-lots.
|
||||
type seedLotCreateRequest struct {
|
||||
PlantID int64 `json:"plantId" binding:"required"`
|
||||
seedLotFields
|
||||
}
|
||||
|
||||
func (r seedLotCreateRequest) toInput() service.SeedLotInput {
|
||||
in := r.seedLotFields.toInput()
|
||||
in.PlantID = r.PlantID
|
||||
return in
|
||||
}
|
||||
|
||||
// seedLotUpdateRequest is the body for PATCH /seed-lots/:id: every field
|
||||
// optional, plus the required current version. The nullable columns are
|
||||
// json.RawMessage so an explicit null (clear it) is distinguishable from an
|
||||
|
||||
Reference in New Issue
Block a user