Files
Gardomatic/internal/storage/postgres/species_task_templates.go
T
kleiax 904d14b64c
CI / test (push) Canceled after 0s
Initial commit
2026-09-12 22:22:17 +02:00

125 lines
6.0 KiB
Go

package postgres
import (
"database/sql"
"gardomatic.kleiax.de/internal/storage"
)
// SpeciesTaskTemplateModel stores recurring species task rules in PostgreSQL.
// SpeciesTaskTemplateModel implements storage.SpeciesTaskTemplateModelInterface
// and resolves global species through the requesting garden boundary.
type SpeciesTaskTemplateModel struct{ DB *sql.DB }
const speciesTaskTemplateColumns = `t.id, t.species_id, t.origin, t.title, t.description, t.trigger_type,
t.month_from, t.day_from, t.month_to, t.day_to, t.offset_days_from, t.offset_days_to,
t.interval_days, t.trigger_offset, t.trigger_offset_unit, t.duration, t.duration_unit,
t.recurrence, t.recurrence_interval, t.priority, t.active, t.created_at, t.updated_at, t.version`
func scanSpeciesTaskTemplate(s scanner) (storage.SpeciesTaskTemplate, error) {
var template storage.SpeciesTaskTemplate
err := s.Scan(
&template.ID, &template.SpeciesID, &template.Origin, &template.Title, &template.Description,
&template.TriggerType, &template.MonthFrom, &template.DayFrom, &template.MonthTo,
&template.DayTo, &template.OffsetDaysFrom, &template.OffsetDaysTo,
&template.IntervalDays, &template.TriggerOffset, &template.TriggerOffsetUnit, &template.Duration, &template.DurationUnit,
&template.Recurrence, &template.RecurrenceInterval, &template.Priority, &template.Active, &template.CreatedAt,
&template.UpdatedAt, &template.Version,
)
return template, err
}
// Insert creates a task template for a species.
func (m SpeciesTaskTemplateModel) Insert(template storage.SpeciesTaskTemplate) (storage.SpeciesTaskTemplate, error) {
ctx, cancel := contextWithTimeout()
defer cancel()
err := m.DB.QueryRowContext(ctx, `
INSERT INTO species_task_templates (
species_id, origin, title, description, trigger_type, month_from, day_from,
month_to, day_to, offset_days_from, offset_days_to, interval_days,
trigger_offset, trigger_offset_unit, duration, duration_unit, recurrence, recurrence_interval, priority, active)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20)
RETURNING id, created_at, updated_at, version`,
template.SpeciesID, template.Origin, template.Title, template.Description, template.TriggerType,
template.MonthFrom, template.DayFrom, template.MonthTo, template.DayTo,
template.OffsetDaysFrom, template.OffsetDaysTo, template.IntervalDays,
template.TriggerOffset, template.TriggerOffsetUnit, template.Duration, template.DurationUnit,
template.Recurrence, template.RecurrenceInterval, template.Priority, template.Active,
).Scan(&template.ID, &template.CreatedAt, &template.UpdatedAt, &template.Version)
if err != nil {
return storage.SpeciesTaskTemplate{}, err
}
return template, nil
}
// Get returns a task template visible in a garden.
func (m SpeciesTaskTemplateModel) Get(gardenID, id int) (storage.SpeciesTaskTemplate, error) {
ctx, cancel := contextWithTimeout()
defer cancel()
template, err := scanSpeciesTaskTemplate(m.DB.QueryRowContext(ctx,
`SELECT `+speciesTaskTemplateColumns+` FROM species_task_templates t JOIN species s ON s.id = t.species_id WHERE (s.garden_id IS NULL OR s.garden_id = $1) AND t.id = $2`, gardenID, id))
if err != nil {
return storage.SpeciesTaskTemplate{}, recordError(err)
}
return template, nil
}
// GetAllForSpecies lists task templates for a species visible in a garden.
func (m SpeciesTaskTemplateModel) GetAllForSpecies(gardenID, speciesID int) ([]storage.SpeciesTaskTemplate, error) {
ctx, cancel := contextWithTimeout()
defer cancel()
rows, err := m.DB.QueryContext(ctx, `SELECT `+speciesTaskTemplateColumns+`
FROM species_task_templates t JOIN species s ON s.id = t.species_id
WHERE (s.garden_id IS NULL OR s.garden_id = $1) AND t.species_id = $2
ORDER BY t.active DESC, t.priority DESC, t.title, t.id`, gardenID, speciesID)
if err != nil {
return nil, err
}
defer rows.Close()
templates := []storage.SpeciesTaskTemplate{}
for rows.Next() {
template, err := scanSpeciesTaskTemplate(rows)
if err != nil {
return nil, err
}
templates = append(templates, template)
}
return templates, rows.Err()
}
// Update changes a task template using optimistic locking.
func (m SpeciesTaskTemplateModel) Update(gardenID int, template storage.SpeciesTaskTemplate) (storage.SpeciesTaskTemplate, error) {
ctx, cancel := contextWithTimeout()
defer cancel()
err := m.DB.QueryRowContext(ctx, `
UPDATE species_task_templates SET species_id = $1, origin = $2, title = $3, description = $4,
trigger_type = $5, month_from = $6, day_from = $7, month_to = $8,
day_to = $9, offset_days_from = $10, offset_days_to = $11,
interval_days = $12, trigger_offset = $13, trigger_offset_unit = $14,
duration = $15, duration_unit = $16, recurrence = $17, recurrence_interval = $18,
priority = $19, active = $20,
updated_at = CURRENT_TIMESTAMP, version = version + 1
WHERE id = $21 AND version = $22
AND EXISTS (SELECT 1 FROM species s WHERE s.id = species_task_templates.species_id AND s.garden_id IS NOT DISTINCT FROM NULLIF($23, 0))
RETURNING updated_at, version`,
template.SpeciesID, template.Origin, template.Title, template.Description, template.TriggerType,
template.MonthFrom, template.DayFrom, template.MonthTo, template.DayTo,
template.OffsetDaysFrom, template.OffsetDaysTo, template.IntervalDays,
template.TriggerOffset, template.TriggerOffsetUnit, template.Duration, template.DurationUnit,
template.Recurrence, template.RecurrenceInterval, template.Priority, template.Active, template.ID, template.Version, gardenID,
).Scan(&template.UpdatedAt, &template.Version)
if err != nil {
if err == sql.ErrNoRows {
return storage.SpeciesTaskTemplate{}, storage.ErrEditConflict
}
return storage.SpeciesTaskTemplate{}, err
}
return template, nil
}
// Delete removes a task template visible in a garden.
func (m SpeciesTaskTemplateModel) Delete(gardenID, id int) error {
return deleteByID(m.DB, `DELETE FROM species_task_templates t USING species s WHERE t.id = $1 AND s.id = t.species_id AND s.garden_id IS NOT DISTINCT FROM NULLIF($2, 0)`, id, gardenID)
}