// ToDo: carve out sql package notifysvc import ( "context" "database/sql" "errors" "time" accountNotificationHandler "synlotto-website/internal/handlers/account/notifications" ) type Service struct { DB *sql.DB Now func() time.Time Timeout time.Duration } func New(db *sql.DB, opts ...func(*Service)) *Service { s := &Service{ DB: db, Now: time.Now, Timeout: 5 * time.Second, } for _, opt := range opts { opt(s) } return s } func WithTimeout(d time.Duration) func(*Service) { return func(s *Service) { s.Timeout = d } } // List returns newest-first notifications for a user. func (s *Service) List(userID int64) ([]accountNotificationHandler.Notification, error) { ctx, cancel := context.WithTimeout(context.Background(), s.Timeout) defer cancel() const q = ` SELECT id, title, body, is_read, created_at FROM notifications WHERE user_id = ? ORDER BY created_at DESC` rows, err := s.DB.QueryContext(ctx, q, userID) if err != nil { return nil, err } defer rows.Close() var out []accountNotificationHandler.Notification for rows.Next() { var n accountNotificationHandler.Notification if err := rows.Scan(&n.ID, &n.Title, &n.Body, &n.IsRead, &n.CreatedAt); err != nil { return nil, err } out = append(out, n) } return out, rows.Err() } func (s *Service) GetByID(userID, id int64) (*accountNotificationHandler.Notification, error) { ctx, cancel := context.WithTimeout(context.Background(), s.Timeout) defer cancel() const q = ` SELECT id, title, body, is_read, created_at FROM notifications WHERE user_id = ? AND id = ?` var n accountNotificationHandler.Notification err := s.DB.QueryRowContext(ctx, q, userID, id). Scan(&n.ID, &n.Title, &n.Body, &n.IsRead, &n.CreatedAt) if errors.Is(err, sql.ErrNoRows) { return nil, nil } if err != nil { return nil, err } return &n, nil }