feat(m1): backend scaffold - entries CRUD, start/stop, auth, migrations

This commit is contained in:
2026-04-30 16:35:06 +02:00
parent 4905c6f570
commit 3aa068efd2
19 changed files with 1483 additions and 0 deletions

View File

@@ -0,0 +1,94 @@
package store
import (
"context"
"database/sql"
"github.com/wotra/wotra/internal/domain"
)
// ClosedDayStore handles persistence for closed days.
type ClosedDayStore struct {
db *sql.DB
}
func NewClosedDayStore(db *sql.DB) *ClosedDayStore {
return &ClosedDayStore{db: db}
}
func (s *ClosedDayStore) Upsert(ctx context.Context, d *domain.ClosedDay) error {
_, err := s.db.ExecContext(ctx,
`INSERT INTO closed_days (day_key, start_time, end_time, worked_ms, kind, closed_at, updated_at)
VALUES (?, ?, ?, ?, ?, ?, ?)
ON CONFLICT(day_key) DO UPDATE SET
start_time=excluded.start_time, end_time=excluded.end_time,
worked_ms=excluded.worked_ms, kind=excluded.kind,
closed_at=excluded.closed_at, updated_at=excluded.updated_at`,
d.DayKey, d.StartTime, d.EndTime, d.WorkedMs, d.Kind, d.ClosedAt, d.UpdatedAt,
)
return err
}
func (s *ClosedDayStore) Delete(ctx context.Context, dayKey string) error {
_, err := s.db.ExecContext(ctx, `DELETE FROM closed_days WHERE day_key=?`, dayKey)
return err
}
func (s *ClosedDayStore) GetByDayKey(ctx context.Context, dayKey string) (*domain.ClosedDay, error) {
row := s.db.QueryRowContext(ctx,
`SELECT day_key, start_time, end_time, worked_ms, kind, closed_at, updated_at
FROM closed_days WHERE day_key=?`, dayKey)
return scanClosedDay(row)
}
func (s *ClosedDayStore) ListByDateRange(ctx context.Context, fromDayKey, toDayKey string) ([]*domain.ClosedDay, error) {
rows, err := s.db.QueryContext(ctx,
`SELECT day_key, start_time, end_time, worked_ms, kind, closed_at, updated_at
FROM closed_days WHERE day_key >= ? AND day_key <= ? ORDER BY day_key ASC`,
fromDayKey, toDayKey)
if err != nil {
return nil, err
}
defer rows.Close()
var result []*domain.ClosedDay
for rows.Next() {
d, err := scanClosedDayRow(rows)
if err != nil {
return nil, err
}
result = append(result, d)
}
return result, rows.Err()
}
func scanClosedDay(row *sql.Row) (*domain.ClosedDay, error) {
var d domain.ClosedDay
var startTime, endTime sql.NullInt64
err := row.Scan(&d.DayKey, &startTime, &endTime, &d.WorkedMs, &d.Kind, &d.ClosedAt, &d.UpdatedAt)
if err != nil {
return nil, err
}
if startTime.Valid {
d.StartTime = &startTime.Int64
}
if endTime.Valid {
d.EndTime = &endTime.Int64
}
return &d, nil
}
func scanClosedDayRow(rows *sql.Rows) (*domain.ClosedDay, error) {
var d domain.ClosedDay
var startTime, endTime sql.NullInt64
err := rows.Scan(&d.DayKey, &startTime, &endTime, &d.WorkedMs, &d.Kind, &d.ClosedAt, &d.UpdatedAt)
if err != nil {
return nil, err
}
if startTime.Valid {
d.StartTime = &startTime.Int64
}
if endTime.Valid {
d.EndTime = &endTime.Int64
}
return &d, nil
}