<feed xmlns='http://www.w3.org/2005/Atom'>
<title>ceres/model/recipe.go, branch step-re-ordering</title>
<subtitle>Recipe server for your favorite dishes</subtitle>
<id>https://cgit.xengineering.eu/ceres/atom/model/recipe.go?h=step-re-ordering</id>
<link rel='self' href='https://cgit.xengineering.eu/ceres/atom/model/recipe.go?h=step-re-ordering'/>
<link rel='alternate' type='text/html' href='https://cgit.xengineering.eu/ceres/'/>
<updated>2026-08-13T19:15:49Z</updated>
<entry>
<title>WIP: Add JSON-based API under `/api`</title>
<updated>2026-08-13T19:15:49Z</updated>
<author>
<name>xengineering</name>
<email>me@xengineering.eu</email>
</author>
<published>2026-08-13T18:53:24Z</published>
<link rel='alternate' type='text/html' href='https://cgit.xengineering.eu/ceres/commit/?id=60bdc926dd7235fd55930ebd5b30c2e346f8ca59'/>
<id>urn:sha1:60bdc926dd7235fd55930ebd5b30c2e346f8ca59</id>
<content type='text'>
TODO: Not all the information is provided there.

This commit provides an unstable JSON API for communication with an
upcoming Dart frontend.

Later it might be part of the public interface for integration into
other services. For this step it has to be ensured the semantics are not
changing a lot in the future which is currently out of scope.
</content>
</entry>
<entry>
<title>model: Implement favorite recipes</title>
<updated>2024-11-03T15:05:55Z</updated>
<author>
<name>xengineering</name>
<email>me@xengineering.eu</email>
</author>
<published>2024-11-03T14:49:43Z</published>
<link rel='alternate' type='text/html' href='https://cgit.xengineering.eu/ceres/commit/?id=7cd3a096a975801a07fb3ff06b6fac70e17234ce'/>
<id>urn:sha1:7cd3a096a975801a07fb3ff06b6fac70e17234ce</id>
<content type='text'>
This adds the ability to store a flag `is_favorite` inside the database.
The corresponding SQL migration is part of this commit.

Furthermore the Go code in the model package is adapted.
</content>
</entry>
<entry>
<title>Apply go fmt to all source files</title>
<updated>2024-09-11T18:25:54Z</updated>
<author>
<name>xengineering</name>
<email>me@xengineering.eu</email>
</author>
<published>2024-09-11T18:25:31Z</published>
<link rel='alternate' type='text/html' href='https://cgit.xengineering.eu/ceres/commit/?id=54029aac92f17ec87d3ef0bb73fcdac271b209c9'/>
<id>urn:sha1:54029aac92f17ec87d3ef0bb73fcdac271b209c9</id>
<content type='text'>
This applies default Go code style recommendations.
</content>
</entry>
<entry>
<title>model: Fix ignored error</title>
<updated>2024-05-17T21:10:17Z</updated>
<author>
<name>xengineering</name>
<email>me@xengineering.eu</email>
</author>
<published>2024-05-17T21:08:05Z</published>
<link rel='alternate' type='text/html' href='https://cgit.xengineering.eu/ceres/commit/?id=5d5580285a366e37ac00a4e1a6f2f1bda853ff28'/>
<id>urn:sha1:5d5580285a366e37ac00a4e1a6f2f1bda853ff28</id>
<content type='text'>
An ignored return value here caused a serious bug as soon as validation
for ingredients was tried. The validation could raise an error e.g. on a
negative amount for the ingredient. This error was ignored at the
changed line which resulted into deleted ingredients for the whole
recipe.
</content>
</entry>
<entry>
<title>model: Rework recipe validation</title>
<updated>2024-05-17T15:14:40Z</updated>
<author>
<name>xengineering</name>
<email>me@xengineering.eu</email>
</author>
<published>2024-05-17T15:12:50Z</published>
<link rel='alternate' type='text/html' href='https://cgit.xengineering.eu/ceres/commit/?id=5eca3267d37cdc51f775b5452727efebbb7e7e9e'/>
<id>urn:sha1:5eca3267d37cdc51f775b5452727efebbb7e7e9e</id>
<content type='text'>
This reduces code duplication and enforces time stamps.
</content>
</entry>
<entry>
<title>model: Make Recipe.Validate() more strict</title>
<updated>2024-05-15T18:09:35Z</updated>
<author>
<name>xengineering</name>
<email>me@xengineering.eu</email>
</author>
<published>2024-05-15T18:09:35Z</published>
<link rel='alternate' type='text/html' href='https://cgit.xengineering.eu/ceres/commit/?id=ae771a44c2ffe31dc1bd67a12b65849f7d7d2d11'/>
<id>urn:sha1:ae771a44c2ffe31dc1bd67a12b65849f7d7d2d11</id>
<content type='text'>
Before the next release this method should be as strict as possible to
avoid cases where actually invalid enters databases.
</content>
</entry>
<entry>
<title>view: Add ingredient overview to recipe read page</title>
<updated>2024-05-12T18:52:25Z</updated>
<author>
<name>xengineering</name>
<email>me@xengineering.eu</email>
</author>
<published>2024-04-07T10:04:11Z</published>
<link rel='alternate' type='text/html' href='https://cgit.xengineering.eu/ceres/commit/?id=bf328dd38a28cf44cfa81c7f1dcd95936d2d0301'/>
<id>urn:sha1:bf328dd38a28cf44cfa81c7f1dcd95936d2d0301</id>
<content type='text'>
</content>
</entry>
<entry>
<title>view: Show ingredients on read page</title>
<updated>2024-05-12T18:52:25Z</updated>
<author>
<name>xengineering</name>
<email>me@xengineering.eu</email>
</author>
<published>2024-04-07T09:56:20Z</published>
<link rel='alternate' type='text/html' href='https://cgit.xengineering.eu/ceres/commit/?id=01d97189d245300d65ca31d650dd868d1d1fc0d8'/>
<id>urn:sha1:01d97189d245300d65ca31d650dd868d1d1fc0d8</id>
<content type='text'>
</content>
</entry>
<entry>
<title>model: Add per-step ingredients</title>
<updated>2024-05-12T18:52:25Z</updated>
<author>
<name>xengineering</name>
<email>me@xengineering.eu</email>
</author>
<published>2024-04-04T12:54:21Z</published>
<link rel='alternate' type='text/html' href='https://cgit.xengineering.eu/ceres/commit/?id=87ae71413e47ef34da57bc1e0b8dddbf84b0c66a'/>
<id>urn:sha1:87ae71413e47ef34da57bc1e0b8dddbf84b0c66a</id>
<content type='text'>
</content>
</entry>
<entry>
<title>model: Enforce recipe titles</title>
<updated>2024-05-09T10:07:48Z</updated>
<author>
<name>xengineering</name>
<email>me@xengineering.eu</email>
</author>
<published>2024-04-22T19:50:26Z</published>
<link rel='alternate' type='text/html' href='https://cgit.xengineering.eu/ceres/commit/?id=0455da359b9fe36bd6808b7e506fe24e48257bea'/>
<id>urn:sha1:0455da359b9fe36bd6808b7e506fe24e48257bea</id>
<content type='text'>
If a recipe has no title it is hard to reference in the front end.
Especially the /recipes page makes problems in that case since it is
impossible to click on that recipes and thus also to remove it.
</content>
</entry>
<entry>
<title>model: CRUD methods only for targeted objects</title>
<updated>2024-04-06T18:22:04Z</updated>
<author>
<name>xengineering</name>
<email>me@xengineering.eu</email>
</author>
<published>2024-04-06T16:45:49Z</published>
<link rel='alternate' type='text/html' href='https://cgit.xengineering.eu/ceres/commit/?id=4aee2c3b45caa890537a0fc0c850c4523a4eb56c'/>
<id>urn:sha1:4aee2c3b45caa890537a0fc0c850c4523a4eb56c</id>
<content type='text'>
A create, read, update or delete (CRUD) method should only care about
the object which provides the receiver and the relations to its child
objects.

For example the method

	func (r *Recipe) Create(tx *sql.Tx) error {}

should only create the relational data inside the database for the
recipe, not for the steps nested into this Recipe struct. This should be
covered by the

	func (s *Step) Create(tx *sql.Tx) error {}

method which is then called by `func (r *Recipe) Create()`.

This has the advantage that every CRUD method has a constraint scope and
is more unified since the Step CRUD methods now have a Step struct as
receiver instead of a Recipe receiver.
</content>
</entry>
<entry>
<title>model: Always pass *sql.Tx to CRUD methods</title>
<updated>2024-04-06T11:12:17Z</updated>
<author>
<name>xengineering</name>
<email>me@xengineering.eu</email>
</author>
<published>2024-04-06T11:12:17Z</published>
<link rel='alternate' type='text/html' href='https://cgit.xengineering.eu/ceres/commit/?id=f65b11a5b3011f370df5b4d32239225f3708ecd5'/>
<id>urn:sha1:f65b11a5b3011f370df5b4d32239225f3708ecd5</id>
<content type='text'>
When nesting objects like steps into other objects like recipes it is
required to pass a *sql.Tx value to the CRUD methods of the inner
object to be able to roll back the whole transaction.

The top level object used to be responsible for the creation of this
*sql.Tx inside its CRUD methods.

This is now moved to the caller of the CRUD methods (here the HTTP
handler function). The advantage is that all CRUD methods now accept a
*sql.Tx as only argument which makes those methods more consistent.
</content>
</entry>
<entry>
<title>model: Add recipe steps</title>
<updated>2024-03-24T09:10:54Z</updated>
<author>
<name>xengineering</name>
<email>me@xengineering.eu</email>
</author>
<published>2024-03-10T09:59:23Z</published>
<link rel='alternate' type='text/html' href='https://cgit.xengineering.eu/ceres/commit/?id=ddc6e8e1fb8a4b1dfe8421b12e40b15162ae75f1'/>
<id>urn:sha1:ddc6e8e1fb8a4b1dfe8421b12e40b15162ae75f1</id>
<content type='text'>
This provides the infrastructure to create views and HTTP handlers to
provide recipe steps.
</content>
</entry>
<entry>
<title>model: Remove .Touch() method</title>
<updated>2024-03-12T20:25:19Z</updated>
<author>
<name>xengineering</name>
<email>me@xengineering.eu</email>
</author>
<published>2024-03-12T20:25:19Z</published>
<link rel='alternate' type='text/html' href='https://cgit.xengineering.eu/ceres/commit/?id=49ffaf0aac14272c2f64a3005ad651b78eb51ad1'/>
<id>urn:sha1:49ffaf0aac14272c2f64a3005ad651b78eb51ad1</id>
<content type='text'>
The model package should never modify the data. Thus the functionality
to update timestamps is moved to the controller package which is
intended to modify data.
</content>
</entry>
<entry>
<title>model: Move timestamp updates to controller</title>
<updated>2024-03-05T19:17:58Z</updated>
<author>
<name>xengineering</name>
<email>me@xengineering.eu</email>
</author>
<published>2024-03-05T19:09:38Z</published>
<link rel='alternate' type='text/html' href='https://cgit.xengineering.eu/ceres/commit/?id=a1edb7fa63dd290654884eeabf89acc9e5965ae5'/>
<id>urn:sha1:a1edb7fa63dd290654884eeabf89acc9e5965ae5</id>
<content type='text'>
The model package should handle the object relational mapping (ORM).
This requires implementing the four CRUD methods:

- create
- read
- update
- delete

On create and update the model package used to modify the timestamps
like `last_changed`. This was detected by the unit tests which tested
that the data is not changed in a create / read cycle or is updated
correctly in an update / read cycle.

This raised the question if it is a good idea that the model package is
"smart" and updates timestamps. To keep the model package and the
included unit tests simple the new design enforces that the complete
data - including metadata - is always exactly the same after using any
CRUD methods.

The functionality of updating the timestamp is moved to the HTTP handler
inside the controller package. This also matches the definition of the
controller package as the part of the code which is alone responsible to
actually change the data.

This commit finally fixes the unit test suite.
</content>
</entry>
<entry>
<title>model: Implement Stringer interface for Recipe</title>
<updated>2024-03-04T19:45:11Z</updated>
<author>
<name>xengineering</name>
<email>me@xengineering.eu</email>
</author>
<published>2024-03-04T19:39:17Z</published>
<link rel='alternate' type='text/html' href='https://cgit.xengineering.eu/ceres/commit/?id=74fc87dcfaa8a65c14c0ef6fe88b6df02f2e90cb'/>
<id>urn:sha1:74fc87dcfaa8a65c14c0ef6fe88b6df02f2e90cb</id>
<content type='text'>
This allows to print a recipe with a fmt.Printf() call more easily:

	fmt.Printf("%s\n", recipe)

This is also used for better error output in unit tests with t.Fatalf().

The Stringer interface is implemented with the JSON package because an
indented version of a recipe is a useful string representation.
</content>
</entry>
<entry>
<title>model: Create test data with Go instead of SQL</title>
<updated>2024-03-04T19:33:58Z</updated>
<author>
<name>xengineering</name>
<email>me@xengineering.eu</email>
</author>
<published>2024-03-03T19:06:40Z</published>
<link rel='alternate' type='text/html' href='https://cgit.xengineering.eu/ceres/commit/?id=b62c686702622b747ab082b7b12b3e611e1d7e1f'/>
<id>urn:sha1:b62c686702622b747ab082b7b12b3e611e1d7e1f</id>
<content type='text'>
This allows to formulate the test data with an object-based model which
is easier than writing it down in a relational model.
</content>
</entry>
<entry>
<title>controller: Update recipe based on JSON</title>
<updated>2024-03-03T13:00:10Z</updated>
<author>
<name>xengineering</name>
<email>me@xengineering.eu</email>
</author>
<published>2024-02-17T16:51:00Z</published>
<link rel='alternate' type='text/html' href='https://cgit.xengineering.eu/ceres/commit/?id=e105822a4f2227ca97853ac1bf106f8d204d6837'/>
<id>urn:sha1:e105822a4f2227ca97853ac1bf106f8d204d6837</id>
<content type='text'>
</content>
</entry>
<entry>
<title>model: Assert one affected row on .Update()</title>
<updated>2024-03-03T12:50:58Z</updated>
<author>
<name>xengineering</name>
<email>me@xengineering.eu</email>
</author>
<published>2024-03-03T07:49:46Z</published>
<link rel='alternate' type='text/html' href='https://cgit.xengineering.eu/ceres/commit/?id=55b69380ebd704477c4a37ff9b70fcb3cbdc63e5'/>
<id>urn:sha1:55b69380ebd704477c4a37ff9b70fcb3cbdc63e5</id>
<content type='text'>
A missing or wrong .Id field for example otherwise results in a silent
error because nothing is actually updated.
</content>
</entry>
<entry>
<title>model: Use only string types for models</title>
<updated>2024-03-03T12:50:58Z</updated>
<author>
<name>xengineering</name>
<email>me@xengineering.eu</email>
</author>
<published>2024-03-03T06:56:39Z</published>
<link rel='alternate' type='text/html' href='https://cgit.xengineering.eu/ceres/commit/?id=110f9db31a2a2b72031292558f7b1d5bab7649c3'/>
<id>urn:sha1:110f9db31a2a2b72031292558f7b1d5bab7649c3</id>
<content type='text'>
When a HTML form is converted to JSON by JavaScript using `FormData()`,
`Object.fromEntries()` and `JSON.stringify` the data type is always
`string`. This does not match the Go struct definitions using multiple
types including e.g. `int`.

There are several options to solve this conflict:

1. use only strings in Go struct definitions
2. write custom functions to parse string-based JSONs to Go structs
3. implement custom functions in JS to use `number` type if possible

Option 3 seems to be a very clean solution. Nevertheless it is limited
by the fact that JSON anyway has a way more limited type system than Go.
So the types used in Go cannot be used and this would reduce this option
to a variant of option 2.

Option 2 requires significant effort per struct inside the model
package. Every object which is transferred via JSON and serialized into
Go structs would require a second struct definition with string types
and a conversion function. This does not scale.

Thus option 1 seems to be the best fit. The reasons for using types like
`int` or `bool` are:

- less memory consumption than `string` in most cases
- implicit data validation (e.g. enforcing positive numbers with `uint`)
- better compatibility with certain APIs which rely on e.g. `int`

The first argument is not so relevant in this use case. The amount of
required memory is still quite small for servers. Implicit data
validation is a good thing but not enough. There should anyway be
validation method which has to be called on CRUD methods and JSON
deserialization.
</content>
</entry>
<entry>
<title>model: Implement CRUD methods for type Recipe</title>
<updated>2024-02-11T19:21:03Z</updated>
<author>
<name>xengineering</name>
<email>me@xengineering.eu</email>
</author>
<published>2024-02-11T14:00:45Z</published>
<link rel='alternate' type='text/html' href='https://cgit.xengineering.eu/ceres/commit/?id=daa2934451ddb381a6a8dd8f902aacb9095200e0'/>
<id>urn:sha1:daa2934451ddb381a6a8dd8f902aacb9095200e0</id>
<content type='text'>
The new Go type 'Recipe' should contain every information directly
related to a recipe. It should be sufficient to pass it to a template to
directly render a HTML view or edit page for the recipe or to a template
to generate a PDF.

The CRUD methods are:

- func (r *Recipe) Create() error
- func (r *Recipe) Update() error
- func (r *Recipe) Read()   error
- func (r *Recipe) Delete() error

Together with the type itself they are the interface the model package
provides for recipes.
</content>
</entry>
</feed>
