* api: detect an absent copy body by io.EOF from the decoder rather than
Content-Length. A chunked request reports -1, not 0, so the length test
read an empty chunked body as malformed and 400'd instead of taking the
default-name path. Covered by a new unknown-length test.
* store: hoist the garden_objects/plantings INSERTs into shared
objectInsert/plantingInsert statements with matching *InsertArgs
helpers. CopyGarden had its own copies of both column lists, so a
column added to the table could be wired into Create* and silently
dropped from a copy.
* store: move the queryer interface to sqlite.go, next to the other
shared query plumbing, instead of users.go.
* web: derive a copy's prefilled name via defaultCopyName, mirroring the
server's copyName including its 200-BYTE cap. The inline
`${name} (copy)` both duplicated the suffix and, for a garden whose
name was already at the cap, prefilled an over-long name that the
server rejected with a 400. Unit-tested, including multi-byte
truncation on a code-point boundary.
* test: drop a throwaway `_ = kept`.
Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
This commit is contained in:
@@ -213,16 +213,7 @@ func (d *DB) CopyGarden(ctx context.Context, srcID, ownerID int64, name string)
|
||||
// re-parented onto the right new object.
|
||||
objectIDs := make(map[int64]int64, len(srcObjects))
|
||||
for _, o := range srcObjects {
|
||||
copied, err := scanObject(tx.QueryRowContext(ctx,
|
||||
`INSERT INTO garden_objects
|
||||
(garden_id, kind, name, shape, points, x_cm, y_cm, width_cm, height_cm,
|
||||
rotation_deg, z_index, plantable, color, props, grid_size_cm, snap_to_grid, notes)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
RETURNING `+objectColumns,
|
||||
created.ID, o.Kind, o.Name, o.Shape, o.Points, o.XCM, o.YCM, o.WidthCM, o.HeightCM,
|
||||
o.RotationDeg, o.ZIndex, boolToInt(o.Plantable), o.Color, o.Props,
|
||||
o.GridSizeCM, boolToInt(o.SnapToGrid), o.Notes,
|
||||
))
|
||||
copied, err := scanObject(tx.QueryRowContext(ctx, objectInsert, objectInsertArgs(created.ID, &o)...))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("store: copy object: %w", err)
|
||||
}
|
||||
@@ -243,11 +234,7 @@ func (d *DB) CopyGarden(ctx context.Context, srcID, ownerID int64, name string)
|
||||
// Unreachable: every active planting joins an object we just copied.
|
||||
return nil, fmt.Errorf("store: copy planting %d: source object %d not copied", p.ID, p.ObjectID)
|
||||
}
|
||||
if _, err := tx.ExecContext(ctx,
|
||||
`INSERT INTO plantings (object_id, plant_id, x_cm, y_cm, radius_cm, count, label, planted_at)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,
|
||||
objectID, p.PlantID, p.XCM, p.YCM, p.RadiusCM, p.Count, p.Label, p.PlantedAt,
|
||||
); err != nil {
|
||||
if _, err := scanPlanting(tx.QueryRowContext(ctx, plantingInsert, plantingInsertArgs(objectID, &p)...)); err != nil {
|
||||
return nil, fmt.Errorf("store: copy planting: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user