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
This commit is contained in:
@@ -2,7 +2,6 @@ package bot
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"database/sql"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"log/slog"
|
"log/slog"
|
||||||
"strconv"
|
"strconv"
|
||||||
@@ -264,22 +263,11 @@ func (h *Handler) editCalendar(ctx context.Context, chatID int64, msgID int, yea
|
|||||||
|
|
||||||
hasEvent := make(map[string]bool)
|
hasEvent := make(map[string]bool)
|
||||||
startDate, endDate := monthGregorianRange(cal, year, month)
|
startDate, endDate := monthGregorianRange(cal, year, month)
|
||||||
rows, err := h.DB.DB().Query(
|
dates, err := h.DB.GetDistinctEventDates(user.ID, startDate, endDate)
|
||||||
"SELECT DISTINCT date FROM days WHERE user_id=? AND date >= ? AND date <= ?",
|
|
||||||
user.ID, startDate, endDate,
|
|
||||||
)
|
|
||||||
if err == nil {
|
if err == nil {
|
||||||
for rows.Next() {
|
for _, d := range dates {
|
||||||
var d string
|
|
||||||
if err := rows.Scan(&d); err != nil {
|
|
||||||
slog.Error("failed to scan day row", "error", err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
hasEvent[d] = true
|
hasEvent[d] = true
|
||||||
}
|
}
|
||||||
if err := rows.Close(); err != nil {
|
|
||||||
slog.Error("failed to close rows", "error", err)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
kbRows := [][]models.InlineKeyboardButton{}
|
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.
|
// 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) {
|
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 := h.DB.UpdateEventWorkType(eventID, workTypeID); err != nil {
|
||||||
if err != nil {
|
|
||||||
slog.Error("failed to update event work type", "event_id", eventID, "error", err)
|
slog.Error("failed to update event work type", "event_id", eventID, "error", err)
|
||||||
h.sendDayView(ctx, chatID, msgID, user, "", loc, false)
|
h.sendDayView(ctx, chatID, msgID, user, "", loc, false)
|
||||||
return
|
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 = h.DB.UpdateEventTime(eventID, newTime.Unix()); err != nil {
|
||||||
if err != nil {
|
|
||||||
slog.Error("failed to update event occurred_at", "event_id", eventID, "error", err)
|
slog.Error("failed to update event occurred_at", "event_id", eventID, "error", err)
|
||||||
h.sendDayView(ctx, chatID, msgID, user, date, loc, false)
|
h.sendDayView(ctx, chatID, msgID, user, date, loc, false)
|
||||||
return
|
return
|
||||||
@@ -615,8 +601,7 @@ func (h *Handler) deleteEventConfirm(ctx context.Context, chatID int64, msgID in
|
|||||||
t := time.Unix(event.OccurredAt, 0).In(loc)
|
t := time.Unix(event.OccurredAt, 0).In(loc)
|
||||||
date := t.Format(DateLayout)
|
date := t.Format(DateLayout)
|
||||||
|
|
||||||
_, err = h.DB.DB().Exec("DELETE FROM events WHERE id=?", eventID)
|
if err = h.DB.DeleteEvent(eventID); err != nil {
|
||||||
if err != nil {
|
|
||||||
slog.Error("failed to delete event", "event_id", eventID, "error", err)
|
slog.Error("failed to delete event", "event_id", eventID, "error", err)
|
||||||
h.sendDayView(ctx, chatID, msgID, user, date, loc, false)
|
h.sendDayView(ctx, chatID, msgID, user, date, loc, false)
|
||||||
return
|
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.
|
// getEventByID fetches a single event by ID, scoped to the user.
|
||||||
func (h *Handler) getEventByID(userID, eventID int64) (*db.Event, error) {
|
func (h *Handler) getEventByID(userID, eventID int64) (*db.Event, error) {
|
||||||
row := h.DB.DB().QueryRow(
|
return h.DB.GetEventByID(eventID, userID)
|
||||||
"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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// addEventTime shows an hour picker for adding a new event to a day.
|
// addEventTime shows an hour picker for adding a new event to a day.
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ package db
|
|||||||
import (
|
import (
|
||||||
"database/sql"
|
"database/sql"
|
||||||
"embed"
|
"embed"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log/slog"
|
"log/slog"
|
||||||
"time"
|
"time"
|
||||||
@@ -945,29 +946,77 @@ func (s *Store) CreateEvent(userID int64, dayID int64, eventType string, workTyp
|
|||||||
return err
|
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.
|
// GetLastEvent returns the most recent event for the user, ordered by occurrence time.
|
||||||
func (s *Store) GetLastEvent(userID int64) (*Event, error) {
|
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
|
SELECT id, user_id, day_id, event_type, work_type_id, occurred_at, note, created_at
|
||||||
FROM events
|
FROM events
|
||||||
WHERE user_id = ?
|
WHERE user_id = ?
|
||||||
ORDER BY occurred_at DESC
|
ORDER BY occurred_at DESC
|
||||||
LIMIT 1
|
LIMIT 1
|
||||||
`, userID)
|
`, 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 e Event
|
||||||
var wt sql.NullInt64
|
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 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
|
return nil, err
|
||||||
}
|
}
|
||||||
if wt.Valid {
|
if wt.Valid {
|
||||||
|
|||||||
Reference in New Issue
Block a user