fix(backend): store an empty tag colour as NULL

The PATCH "clear colour" path sent an empty string, which violates the
hex CHECK constraint and never falls back to the category colour. Map ''
to NULL via NULLIF in the tag insert/update so a cleared or omitted
colour is stored as NULL.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-11 23:31:59 +03:00
parent 4def59c86d
commit 21c7aa31ea
2 changed files with 37 additions and 2 deletions
+2 -2
View File
@@ -272,7 +272,7 @@ func (r *TagRepo) Create(ctx context.Context, t *domain.Tag) (*domain.Tag, error
const query = `
WITH ins AS (
INSERT INTO data.tags (name, notes, color, category_id, metadata, creator_id, is_public)
VALUES ($1, $2, $3, $4, $5, $6, $7)
VALUES ($1, $2, NULLIF($3, ''), $4, $5, $6, $7)
RETURNING *
)
SELECT
@@ -321,7 +321,7 @@ WITH upd AS (
UPDATE data.tags SET
name = $2,
notes = $3,
color = $4,
color = NULLIF($4, ''),
category_id = $5,
metadata = COALESCE($6, metadata),
is_public = $7
@@ -891,6 +891,41 @@ func TestRecordPoolView(t *testing.T) {
require.Equal(t, http.StatusNotFound, resp.StatusCode, resp.String())
}
// TestTagColorOptional verifies a tag can be created without a colour (stored as
// NULL rather than the colour input's default) and that an existing colour can
// be cleared back to none.
func TestTagColorOptional(t *testing.T) {
if testing.Short() {
t.Skip("skipping integration test in short mode")
}
h := setupSuite(t)
adminToken := h.login("admin", "admin")
// Created without a colour → color is null.
resp := h.doJSON("POST", "/tags", map[string]any{"name": "plain"}, adminToken)
require.Equal(t, http.StatusCreated, resp.StatusCode, resp.String())
var plain map[string]any
resp.decode(t, &plain)
assert.Nil(t, plain["color"], "tag created without a colour should have null color")
// Created with a colour → kept verbatim.
resp = h.doJSON("POST", "/tags", map[string]any{"name": "red", "color": "aabbcc"}, adminToken)
require.Equal(t, http.StatusCreated, resp.StatusCode, resp.String())
var red map[string]any
resp.decode(t, &red)
assert.Equal(t, "aabbcc", red["color"])
redID := red["id"].(string)
// Clearing the colour (color: null) must store NULL — an empty string would
// violate the hex CHECK constraint and fail the update.
resp = h.doJSON("PATCH", "/tags/"+redID, map[string]any{"color": nil}, adminToken)
require.Equal(t, http.StatusOK, resp.StatusCode, resp.String())
var cleared map[string]any
resp.decode(t, &cleared)
assert.Nil(t, cleared["color"], "cleared colour should be null")
}
// TestBulkTagAutoRule verifies the bulk add path also applies then_tags.
func TestBulkTagAutoRule(t *testing.T) {
if testing.Short() {