// Package notifysvc // Path: /internal/platform/services/notifications // File: service.go // ToDo: carve out sql package notifysvc import ( "context" "database/sql" "errors" "time" domain "synlotto-website/internal/domain/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) ([]domain.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 []domain.Notification for rows.Next() { var n domain.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) (*domain.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 domain.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 }