From 80de661f5bb6262b71208671546001f11bcfda08 Mon Sep 17 00:00:00 2001 From: db123 Date: Thu, 25 Jun 2026 02:29:30 +0330 Subject: [PATCH] refactor: eliminate store bypass and fix GetLastEvent style - Add GetDistinctEventDates, UpdateEventWorkType, UpdateEventTime, DeleteEvent, GetEventByID store methods to calendar.go - Replace all 5 raw h.DB.DB().Exec/Query calls with proper store methods - Remove unused database/sql import from calendar.go - Rewrite GetLastEvent to use QueryRow instead of Query+manual iteration (fixing row-vs-rows naming issue; removing rows.Close boilerplate) - Add errors import to store.go for errors.Is --- internal/bot/calendar.go | 39 ++++----------------- internal/db/store.go | 73 +++++++++++++++++++++++++++++++++------- 2 files changed, 67 insertions(+), 45 deletions(-) diff --git a/internal/bot/calendar.go b/internal/bot/calendar.go index 28c36ed..8afac1b 100644 --- a/internal/bot/calendar.go +++ b/internal/bot/calendar.go @@ -2,7 +2,6 @@ package bot import ( "context" - "database/sql" "fmt" "log/slog" "strconv" @@ -264,22 +263,11 @@ func (h *Handler) editCalendar(ctx context.Context, chatID int64, msgID int, yea hasEvent := make(map[string]bool) startDate, endDate := monthGregorianRange(cal, year, month) - rows, err := h.DB.DB().Query( - "SELECT DISTINCT date FROM days WHERE user_id=? AND date >= ? AND date <= ?", - user.ID, startDate, endDate, - ) + dates, err := h.DB.GetDistinctEventDates(user.ID, startDate, endDate) if err == nil { - for rows.Next() { - var d string - if err := rows.Scan(&d); err != nil { - slog.Error("failed to scan day row", "error", err) - continue - } + for _, d := range dates { hasEvent[d] = true } - if err := rows.Close(); err != nil { - slog.Error("failed to close rows", "error", err) - } } kbRows := [][]models.InlineKeyboardButton{} @@ -439,8 +427,7 @@ func (h *Handler) editEventType(ctx context.Context, chatID int64, msgID int, us // setEventWorkType updates an event's work type and returns to the day view. func (h *Handler) setEventWorkType(ctx context.Context, chatID int64, msgID int, user *db.User, eventID, workTypeID int64, loc *time.Location) { - _, err := h.DB.DB().Exec("UPDATE events SET work_type_id=? WHERE id=?", workTypeID, eventID) - if err != nil { + if err := h.DB.UpdateEventWorkType(eventID, workTypeID); err != nil { slog.Error("failed to update event work type", "event_id", eventID, "error", err) h.sendDayView(ctx, chatID, msgID, user, "", loc, false) return @@ -557,8 +544,7 @@ func (h *Handler) editEventTimeSet(ctx context.Context, chatID int64, msgID int, } } - _, err = h.DB.DB().Exec("UPDATE events SET occurred_at=? WHERE id=?", newTime.Unix(), eventID) - if err != nil { + if err = h.DB.UpdateEventTime(eventID, newTime.Unix()); err != nil { slog.Error("failed to update event occurred_at", "event_id", eventID, "error", err) h.sendDayView(ctx, chatID, msgID, user, date, loc, false) return @@ -615,8 +601,7 @@ func (h *Handler) deleteEventConfirm(ctx context.Context, chatID int64, msgID in t := time.Unix(event.OccurredAt, 0).In(loc) date := t.Format(DateLayout) - _, err = h.DB.DB().Exec("DELETE FROM events WHERE id=?", eventID) - if err != nil { + if err = h.DB.DeleteEvent(eventID); err != nil { slog.Error("failed to delete event", "event_id", eventID, "error", err) h.sendDayView(ctx, chatID, msgID, user, date, loc, false) return @@ -654,19 +639,7 @@ func (h *Handler) backToDayView(ctx context.Context, chatID int64, msgID int, us // getEventByID fetches a single event by ID, scoped to the user. func (h *Handler) getEventByID(userID, eventID int64) (*db.Event, error) { - row := h.DB.DB().QueryRow( - "SELECT id, user_id, day_id, event_type, work_type_id, occurred_at, note, created_at FROM events WHERE id=? AND user_id=?", - eventID, userID, - ) - var e db.Event - var wt sql.NullInt64 - if err := row.Scan(&e.ID, &e.UserID, &e.DayID, &e.EventType, &wt, &e.OccurredAt, &e.Note, &e.CreatedAt); err != nil { - return nil, err - } - if wt.Valid { - e.WorkTypeID = &wt.Int64 - } - return &e, nil + return h.DB.GetEventByID(eventID, userID) } // addEventTime shows an hour picker for adding a new event to a day. diff --git a/internal/db/store.go b/internal/db/store.go index 1178cd8..a569c9a 100644 --- a/internal/db/store.go +++ b/internal/db/store.go @@ -4,6 +4,7 @@ package db import ( "database/sql" "embed" + "errors" "fmt" "log/slog" "time" @@ -945,29 +946,77 @@ func (s *Store) CreateEvent(userID int64, dayID int64, eventType string, workTyp return err } +// GetDistinctEventDates returns distinct dates from the days table on which the user has events within range. +func (s *Store) GetDistinctEventDates(userID int64, startDate, endDate string) ([]string, error) { + rows, err := s.db.Query( + "SELECT DISTINCT d.date FROM days d JOIN events e ON e.day_id = d.id WHERE d.user_id=? AND d.date >= ? AND d.date <= ? ORDER BY d.date", + userID, startDate, endDate, + ) + if err != nil { + return nil, err + } + defer rows.Close() + var dates []string + for rows.Next() { + var d string + if err := rows.Scan(&d); err != nil { + return nil, err + } + dates = append(dates, d) + } + return dates, rows.Err() +} + +// UpdateEventWorkType sets the work type for an event. +func (s *Store) UpdateEventWorkType(eventID, workTypeID int64) error { + _, err := s.db.Exec("UPDATE events SET work_type_id=? WHERE id=?", workTypeID, eventID) + return err +} + +// UpdateEventTime sets the occurred_at timestamp for an event. +func (s *Store) UpdateEventTime(eventID, occurredAt int64) error { + _, err := s.db.Exec("UPDATE events SET occurred_at=? WHERE id=?", occurredAt, eventID) + return err +} + +// DeleteEvent removes an event by ID. +func (s *Store) DeleteEvent(eventID int64) error { + _, err := s.db.Exec("DELETE FROM events WHERE id=?", eventID) + return err +} + +// GetEventByID returns a single event scoped to a user. +func (s *Store) GetEventByID(eventID, userID int64) (*Event, error) { + row := s.db.QueryRow( + "SELECT id, user_id, day_id, event_type, work_type_id, occurred_at, note, created_at FROM events WHERE id=? AND user_id=?", + eventID, userID, + ) + var e Event + var wt sql.NullInt64 + if err := row.Scan(&e.ID, &e.UserID, &e.DayID, &e.EventType, &wt, &e.OccurredAt, &e.Note, &e.CreatedAt); err != nil { + return nil, err + } + if wt.Valid { + e.WorkTypeID = &wt.Int64 + } + return &e, nil +} + // GetLastEvent returns the most recent event for the user, ordered by occurrence time. func (s *Store) GetLastEvent(userID int64) (*Event, error) { - row, err := s.db.Query(` + row := s.db.QueryRow(` SELECT id, user_id, day_id, event_type, work_type_id, occurred_at, note, created_at FROM events WHERE user_id = ? ORDER BY occurred_at DESC LIMIT 1 `, userID) - if err != nil { - return nil, err - } - defer func() { - if err := row.Close(); err != nil { - slog.Warn("GetLastEvent: row.Close failed", "error", err) - } - }() - if !row.Next() { - return nil, sql.ErrNoRows - } var e Event var wt sql.NullInt64 if err := row.Scan(&e.ID, &e.UserID, &e.DayID, &e.EventType, &wt, &e.OccurredAt, &e.Note, &e.CreatedAt); err != nil { + if errors.Is(err, sql.ErrNoRows) { + return nil, sql.ErrNoRows + } return nil, err } if wt.Valid {