- go.mod module name
- All 714 import references across 289 Go files
- VERSION: 0.1.0
- CI DB names: switchboard_core_{ci,dev,test}
- Docker image: gobha/switchboard-core
- Test fixtures: JWT issuer, repo names
- .env.example, docker-compose container name
- Compiles clean (go build exit 0)
147 lines
3.6 KiB
Go
147 lines
3.6 KiB
Go
package sqlite
|
|
|
|
import (
|
|
"context"
|
|
"database/sql"
|
|
"time"
|
|
|
|
"switchboard-core/models"
|
|
"switchboard-core/store"
|
|
)
|
|
|
|
type NotificationStore struct{}
|
|
|
|
func NewNotificationStore() *NotificationStore { return &NotificationStore{} }
|
|
|
|
func (s *NotificationStore) Create(ctx context.Context, n *models.Notification) error {
|
|
if n.ID == "" {
|
|
n.ID = store.NewID()
|
|
}
|
|
now := time.Now().UTC().Format(timeFmt)
|
|
|
|
_, err := DB.ExecContext(ctx, `
|
|
INSERT INTO notifications (id, user_id, type, title, body, resource_type, resource_id, is_read, created_at)
|
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
n.ID, n.UserID, n.Type, n.Title, n.Body,
|
|
nullStr(n.ResourceType), nullStr(n.ResourceID), boolToInt(n.IsRead), now,
|
|
)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
n.CreatedAt, _ = time.Parse(timeFmt, now)
|
|
return nil
|
|
}
|
|
|
|
func (s *NotificationStore) ListByUser(ctx context.Context, userID string, limit, offset int, unreadOnly bool) ([]models.Notification, int, error) {
|
|
if limit <= 0 {
|
|
limit = 20
|
|
}
|
|
|
|
// Count
|
|
countQ := `SELECT COUNT(*) FROM notifications WHERE user_id = ?`
|
|
countArgs := []interface{}{userID}
|
|
if unreadOnly {
|
|
countQ += ` AND is_read = 0`
|
|
}
|
|
|
|
var total int
|
|
if err := DB.QueryRowContext(ctx, countQ, countArgs...).Scan(&total); err != nil {
|
|
return nil, 0, err
|
|
}
|
|
|
|
// Fetch
|
|
q := `SELECT id, user_id, type, title, body, resource_type, resource_id, is_read, created_at
|
|
FROM notifications WHERE user_id = ?`
|
|
if unreadOnly {
|
|
q += ` AND is_read = 0`
|
|
}
|
|
q += ` ORDER BY created_at DESC LIMIT ? OFFSET ?`
|
|
|
|
rows, err := DB.QueryContext(ctx, q, userID, limit, offset)
|
|
if err != nil {
|
|
return nil, 0, err
|
|
}
|
|
defer rows.Close()
|
|
|
|
var items []models.Notification
|
|
for rows.Next() {
|
|
var n models.Notification
|
|
var resType, resID sql.NullString
|
|
var isRead int
|
|
if err := rows.Scan(
|
|
&n.ID, &n.UserID, &n.Type, &n.Title, &n.Body,
|
|
&resType, &resID, &isRead, st(&n.CreatedAt),
|
|
); err != nil {
|
|
return nil, 0, err
|
|
}
|
|
n.ResourceType = NullableString(resType)
|
|
n.ResourceID = NullableString(resID)
|
|
n.IsRead = isRead != 0
|
|
items = append(items, n)
|
|
}
|
|
if items == nil {
|
|
items = []models.Notification{}
|
|
}
|
|
return items, total, rows.Err()
|
|
}
|
|
|
|
func (s *NotificationStore) MarkRead(ctx context.Context, id, userID string) error {
|
|
res, err := DB.ExecContext(ctx,
|
|
`UPDATE notifications SET is_read = 1 WHERE id = ? AND user_id = ?`,
|
|
id, userID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
n, _ := res.RowsAffected()
|
|
if n == 0 {
|
|
return sql.ErrNoRows
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (s *NotificationStore) MarkAllRead(ctx context.Context, userID string) error {
|
|
_, err := DB.ExecContext(ctx,
|
|
`UPDATE notifications SET is_read = 1 WHERE user_id = ? AND is_read = 0`,
|
|
userID)
|
|
return err
|
|
}
|
|
|
|
func (s *NotificationStore) Delete(ctx context.Context, id, userID string) error {
|
|
res, err := DB.ExecContext(ctx,
|
|
`DELETE FROM notifications WHERE id = ? AND user_id = ?`, id, userID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
n, _ := res.RowsAffected()
|
|
if n == 0 {
|
|
return sql.ErrNoRows
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (s *NotificationStore) UnreadCount(ctx context.Context, userID string) (int, error) {
|
|
var count int
|
|
err := DB.QueryRowContext(ctx,
|
|
`SELECT COUNT(*) FROM notifications WHERE user_id = ? AND is_read = 0`,
|
|
userID).Scan(&count)
|
|
return count, err
|
|
}
|
|
|
|
func (s *NotificationStore) DeleteOlderThan(ctx context.Context, before time.Time) (int64, error) {
|
|
res, err := DB.ExecContext(ctx,
|
|
`DELETE FROM notifications WHERE created_at < ?`,
|
|
before.UTC().Format(timeFmt))
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
return res.RowsAffected()
|
|
}
|
|
|
|
// nullStr returns sql.NullString for optional string fields.
|
|
func nullStr(s string) sql.NullString {
|
|
if s == "" {
|
|
return sql.NullString{}
|
|
}
|
|
return sql.NullString{String: s, Valid: true}
|
|
}
|