<feed xmlns='http://www.w3.org/2005/Atom'>
<title>ceres/model, branch 0.4.0</title>
<subtitle>Recipe server for your favorite dishes</subtitle>
<id>https://cgit.xengineering.eu/ceres/atom?h=0.4.0</id>
<link rel='self' href='https://cgit.xengineering.eu/ceres/atom?h=0.4.0'/>
<link rel='alternate' type='text/html' href='https://cgit.xengineering.eu/ceres/'/>
<updated>2024-05-17T21:20:41Z</updated>
<entry>
<title>model: Add strict Ingredient.Validate()</title>
<updated>2024-05-17T21:20:41Z</updated>
<author>
<name>xengineering</name>
<email>me@xengineering.eu</email>
</author>
<published>2024-05-15T18:18:39Z</published>
<link rel='alternate' type='text/html' href='https://cgit.xengineering.eu/ceres/commit/?id=bc149ee58122effc9d4c035da2af3a7ec1f2f862'/>
<id>urn:sha1:bc149ee58122effc9d4c035da2af3a7ec1f2f862</id>
<content type='text'>
</content>
</entry>
<entry>
<title>model: Add strict Step.Validate()</title>
<updated>2024-05-17T21:16:36Z</updated>
<author>
<name>xengineering</name>
<email>me@xengineering.eu</email>
</author>
<published>2024-05-15T18:18:13Z</published>
<link rel='alternate' type='text/html' href='https://cgit.xengineering.eu/ceres/commit/?id=f8f5d296f218f79646a54d3aed8d54fa9f8704c1'/>
<id>urn:sha1:f8f5d296f218f79646a54d3aed8d54fa9f8704c1</id>
<content type='text'>
</content>
</entry>
<entry>
<title>model: Fix another unhandled error</title>
<updated>2024-05-17T21:15:56Z</updated>
<author>
<name>xengineering</name>
<email>me@xengineering.eu</email>
</author>
<published>2024-05-17T21:15:56Z</published>
<link rel='alternate' type='text/html' href='https://cgit.xengineering.eu/ceres/commit/?id=5194ea4b29ffef0fe5206899243339cf109af41d'/>
<id>urn:sha1:5194ea4b29ffef0fe5206899243339cf109af41d</id>
<content type='text'>
This could also lead to bugs.
</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>Inject examples only with new --example flag</title>
<updated>2024-05-09T20:37:03Z</updated>
<author>
<name>xengineering</name>
<email>me@xengineering.eu</email>
</author>
<published>2024-05-09T20:33:42Z</published>
<link rel='alternate' type='text/html' href='https://cgit.xengineering.eu/ceres/commit/?id=0ac3cc76b4b6c824c4b3f7a357d40b487984abfb'/>
<id>urn:sha1:0ac3cc76b4b6c824c4b3f7a357d40b487984abfb</id>
<content type='text'>
The default use case should be to not inject example recipes.
</content>
</entry>
<entry>
<title>model: Fix unit tests</title>
<updated>2024-05-09T20:23:28Z</updated>
<author>
<name>xengineering</name>
<email>me@xengineering.eu</email>
</author>
<published>2024-05-09T20:23:28Z</published>
<link rel='alternate' type='text/html' href='https://cgit.xengineering.eu/ceres/commit/?id=0142af99aba36241c276a56a088e7aac10c62f86'/>
<id>urn:sha1:0142af99aba36241c276a56a088e7aac10c62f86</id>
<content type='text'>
</content>
</entry>
<entry>
<title>model: Rename version to execVersion</title>
<updated>2024-05-09T20:12:54Z</updated>
<author>
<name>xengineering</name>
<email>me@xengineering.eu</email>
</author>
<published>2024-05-09T20:12:54Z</published>
<link rel='alternate' type='text/html' href='https://cgit.xengineering.eu/ceres/commit/?id=f7fca11c47224ff28b10a7f41fb76ce0404a0236'/>
<id>urn:sha1:f7fca11c47224ff28b10a7f41fb76ce0404a0236</id>
<content type='text'>
This makes the code easier to understand because there is an executable
version and a database version handled inside that file.
</content>
</entry>
<entry>
<title>model: Require same version for executable and DB</title>
<updated>2024-05-09T20:04:06Z</updated>
<author>
<name>xengineering</name>
<email>me@xengineering.eu</email>
</author>
<published>2024-05-09T20:04:06Z</published>
<link rel='alternate' type='text/html' href='https://cgit.xengineering.eu/ceres/commit/?id=0e8a199d6144fcfab05eb0c8a793073bc53783be'/>
<id>urn:sha1:0e8a199d6144fcfab05eb0c8a793073bc53783be</id>
<content type='text'>
Currently only an empty database and an existing database with the same
version are supported.

Support for migrations based on semantic versioning will be added in
future versions of Ceres.
</content>
</entry>
<entry>
<title>model: Migrate only in empty databases</title>
<updated>2024-05-09T19:59:01Z</updated>
<author>
<name>xengineering</name>
<email>me@xengineering.eu</email>
</author>
<published>2024-05-09T19:59:01Z</published>
<link rel='alternate' type='text/html' href='https://cgit.xengineering.eu/ceres/commit/?id=e5d57f2c4a9f01ac7a2b11c04b932311bd240611'/>
<id>urn:sha1:e5d57f2c4a9f01ac7a2b11c04b932311bd240611</id>
<content type='text'>
</content>
</entry>
<entry>
<title>model: Initial database version injection</title>
<updated>2024-05-09T19:48:56Z</updated>
<author>
<name>xengineering</name>
<email>me@xengineering.eu</email>
</author>
<published>2024-05-09T19:48:56Z</published>
<link rel='alternate' type='text/html' href='https://cgit.xengineering.eu/ceres/commit/?id=ca96df29085f9ca6567cd474d0920e48c6410b42'/>
<id>urn:sha1:ca96df29085f9ca6567cd474d0920e48c6410b42</id>
<content type='text'>
If the database was empty on startup a metadata table with a key and
value row is created.

In addition the Ceres executable version is inserted as value under the
key 'version'.

This allows to detect on not-empty databases which Ceres version was
used before which is the starting point to implement migrations.
</content>
</entry>
<entry>
<title>model: Detect if database is empty</title>
<updated>2024-05-09T19:31:19Z</updated>
<author>
<name>xengineering</name>
<email>me@xengineering.eu</email>
</author>
<published>2024-05-09T19:31:19Z</published>
<link rel='alternate' type='text/html' href='https://cgit.xengineering.eu/ceres/commit/?id=ca19c73b305932a4ef41d31787eabb25f9417a05'/>
<id>urn:sha1:ca19c73b305932a4ef41d31787eabb25f9417a05</id>
<content type='text'>
An empty database requires to add the metadata table with the version
entry to make migrations possible. Thus this detection will be required.
</content>
</entry>
<entry>
<title>model: Wrap migration completely in transaction</title>
<updated>2024-05-09T19:10:50Z</updated>
<author>
<name>xengineering</name>
<email>me@xengineering.eu</email>
</author>
<published>2024-05-09T19:10:15Z</published>
<link rel='alternate' type='text/html' href='https://cgit.xengineering.eu/ceres/commit/?id=2db08f564ddb38e80c79c9338047f4e8f3a977e8'/>
<id>urn:sha1:2db08f564ddb38e80c79c9338047f4e8f3a977e8</id>
<content type='text'>
This makes it more clear that the full migration will be rolled back on
errors.
</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: Use defer for tx.Rollback()</title>
<updated>2024-05-09T09:13:46Z</updated>
<author>
<name>xengineering</name>
<email>me@xengineering.eu</email>
</author>
<published>2024-05-09T09:13:46Z</published>
<link rel='alternate' type='text/html' href='https://cgit.xengineering.eu/ceres/commit/?id=fd7ee91110cf1de780f3c46822dd69385b3b6318'/>
<id>urn:sha1:fd7ee91110cf1de780f3c46822dd69385b3b6318</id>
<content type='text'>
A committed transaction cannot be rolled back. Using defer to roll back
guarantees that the transaction is always rolled back if not the commit
in the last line of model.Transaction was excuted.

[1]: https://go.dev/doc/database/execute-transactions
</content>
</entry>
<entry>
<title>Restructure database-related functions</title>
<updated>2024-05-09T08:02:00Z</updated>
<author>
<name>xengineering</name>
<email>me@xengineering.eu</email>
</author>
<published>2024-05-09T08:02:00Z</published>
<link rel='alternate' type='text/html' href='https://cgit.xengineering.eu/ceres/commit/?id=a2038b0ea35d1466c84e6e04a2e4597fc038815a'/>
<id>urn:sha1:a2038b0ea35d1466c84e6e04a2e4597fc038815a</id>
<content type='text'>
</content>
</entry>
<entry>
<title>Introduce model.Transaction()</title>
<updated>2024-05-08T20:26:00Z</updated>
<author>
<name>xengineering</name>
<email>me@xengineering.eu</email>
</author>
<published>2024-05-08T19:53:13Z</published>
<link rel='alternate' type='text/html' href='https://cgit.xengineering.eu/ceres/commit/?id=0ba1a7661a81200db98e40149eef1e39fd22f407'/>
<id>urn:sha1:0ba1a7661a81200db98e40149eef1e39fd22f407</id>
<content type='text'>
It is a very common pattern that some function needs to access the
database and wants to wrap all the actions into one transaction.

The advantage of a transaction is that it is ACID:

- atomic
- consistent
- isolated
- durable

In Go it is required to request a new transaction, execute functionality
on it and handle rollback or commit of this transaction based on the
success of the operation.

All this and the error handling can be written down in the
model.Transaction() function exactly once. The full signature of it is:

	func Transaction(f func(*sql.Tx) error) error

It requires a function or closure passed as argument which takes the
transaction (*sql.Tx) and returns an error which might be nil.

This is very generic. It is applied to:

- injecting test data
- database migrations
- data read requests
- data write requests
</content>
</entry>
<entry>
<title>Fix unit tests</title>
<updated>2024-05-08T19:20:39Z</updated>
<author>
<name>xengineering</name>
<email>me@xengineering.eu</email>
</author>
<published>2024-05-08T19:20:39Z</published>
<link rel='alternate' type='text/html' href='https://cgit.xengineering.eu/ceres/commit/?id=9073adf62f2fa78190c8296c5f6f6c9fb1963063'/>
<id>urn:sha1:9073adf62f2fa78190c8296c5f6f6c9fb1963063</id>
<content type='text'>
</content>
</entry>
<entry>
<title>model: Init database with database path</title>
<updated>2024-05-07T19:25:19Z</updated>
<author>
<name>xengineering</name>
<email>me@xengineering.eu</email>
</author>
<published>2024-05-07T19:25:19Z</published>
<link rel='alternate' type='text/html' href='https://cgit.xengineering.eu/ceres/commit/?id=75c1270c86ac17d056161714ac32a57113696d21'/>
<id>urn:sha1:75c1270c86ac17d056161714ac32a57113696d21</id>
<content type='text'>
</content>
</entry>
<entry>
<title>model: Do not write version.txt inside storage</title>
<updated>2024-05-06T19:42:07Z</updated>
<author>
<name>xengineering</name>
<email>me@xengineering.eu</email>
</author>
<published>2024-05-06T19:42:07Z</published>
<link rel='alternate' type='text/html' href='https://cgit.xengineering.eu/ceres/commit/?id=5f5626314a40a47f53773f386e90e3bb6adfa96a'/>
<id>urn:sha1:5f5626314a40a47f53773f386e90e3bb6adfa96a</id>
<content type='text'>
The intention of this file was that a Ceres executable could compare its
version with the version of the storage folder.

If the versions match the storage folder could be directly used. If the
storage version is lower the executable can apply migrations to the
storage folder until the versions match.

The problem is that executing migrations inside the database and
updating the version.txt cannot be atomic.

In contrast the version string could be saved inside the database itself
in a metadata table. In that case the migration together with the update
of the version string can be executed inside one database transaction
which guarantees atomicity.

The problem could still be that migrations should be applied also to the
files and folders inside the storage folder. This problem can only be
avoided by not using files to store data and instead use the BLOB
datatype if necessary.

Even in case of a future filesystem use it is still better to have the
guarantee that the database with file paths and metadata and the there
included version string are in sync.
</content>
</entry>
<entry>
<title>model: Introduce NewStorage() function</title>
<updated>2024-05-06T19:16:22Z</updated>
<author>
<name>xengineering</name>
<email>me@xengineering.eu</email>
</author>
<published>2024-05-06T15:16:54Z</published>
<link rel='alternate' type='text/html' href='https://cgit.xengineering.eu/ceres/commit/?id=250fc80c3c15cd38a518af5c256e2ba619295f5f'/>
<id>urn:sha1:250fc80c3c15cd38a518af5c256e2ba619295f5f</id>
<content type='text'>
It is a common pattern inside the Go standard library to provide a
constructor with this naming scheme to custom types of the package.

Doing this here results in a style closer to the standard library which
improves readability.
</content>
</entry>
<entry>
<title>Move storage path logging to main() function</title>
<updated>2024-05-04T18:52:02Z</updated>
<author>
<name>xengineering</name>
<email>me@xengineering.eu</email>
</author>
<published>2024-05-04T18:48:43Z</published>
<link rel='alternate' type='text/html' href='https://cgit.xengineering.eu/ceres/commit/?id=5c248884afa530bafb7b04dc14c587e3029480f6'/>
<id>urn:sha1:5c248884afa530bafb7b04dc14c587e3029480f6</id>
<content type='text'>
The model package where this used to be implemented should not care too
much about logging. Furthermore it is easier to compare the log output
with the main() function if the log statements are there.
</content>
</entry>
<entry>
<title>model: Add storage.Exists() and storage.Create()</title>
<updated>2024-05-04T18:22:23Z</updated>
<author>
<name>xengineering</name>
<email>me@xengineering.eu</email>
</author>
<published>2024-05-04T18:22:23Z</published>
<link rel='alternate' type='text/html' href='https://cgit.xengineering.eu/ceres/commit/?id=c6fbbbded8463fc94e5af223246eac97d538a9e5'/>
<id>urn:sha1:c6fbbbded8463fc94e5af223246eac97d538a9e5</id>
<content type='text'>
These new methods provide essential functionality related to the storage
folder.
</content>
</entry>
<entry>
<title>model: Introduce type Storage</title>
<updated>2024-05-04T18:07:35Z</updated>
<author>
<name>xengineering</name>
<email>me@xengineering.eu</email>
</author>
<published>2024-05-04T18:07:35Z</published>
<link rel='alternate' type='text/html' href='https://cgit.xengineering.eu/ceres/commit/?id=7d8899e1d2bf45511d10670dd9a6ad386afb8da1'/>
<id>urn:sha1:7d8899e1d2bf45511d10670dd9a6ad386afb8da1</id>
<content type='text'>
This new type definition will make it easier to handle the storage
directory of Ceres and related functionality which can be implemented
with methods.
</content>
</entry>
<entry>
<title>Do not remove storage folder</title>
<updated>2024-05-01T11:20:22Z</updated>
<author>
<name>xengineering</name>
<email>me@xengineering.eu</email>
</author>
<published>2024-05-01T11:20:22Z</published>
<link rel='alternate' type='text/html' href='https://cgit.xengineering.eu/ceres/commit/?id=390642f9bbc12f0b435f343fc35f4a47c5ec7680'/>
<id>urn:sha1:390642f9bbc12f0b435f343fc35f4a47c5ec7680</id>
<content type='text'>
This is not useful in production. Furthermore in the debug use case the
default storage path is now ./storage which can easily be removed by `rm
-rf storage`. This also allows to not remove the storage folder for
further analysis of the storage folder.
</content>
</entry>
<entry>
<title>Use default storage path instead of temp dir</title>
<updated>2024-05-01T11:19:05Z</updated>
<author>
<name>xengineering</name>
<email>me@xengineering.eu</email>
</author>
<published>2024-05-01T11:19:05Z</published>
<link rel='alternate' type='text/html' href='https://cgit.xengineering.eu/ceres/commit/?id=4cc0677b79edeed05ead23def152ac45f35f556d'/>
<id>urn:sha1:4cc0677b79edeed05ead23def152ac45f35f556d</id>
<content type='text'>
</content>
</entry>
</feed>
