feat(backend): add domain store, learning services, and seed data
Persist LMS entities and implement hearts/watch rules plus curriculum bootstrap.
This commit is contained in:
@@ -0,0 +1,61 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"pargar/backend/internal/models"
|
||||
"pargar/backend/internal/store"
|
||||
)
|
||||
|
||||
// Pusher delivers realtime messages to a user's connected websockets.
|
||||
type Pusher interface {
|
||||
Push(userID int64, msg any)
|
||||
}
|
||||
|
||||
// Notifier persists in-app notifications and pushes them live.
|
||||
type Notifier struct {
|
||||
store *store.Store
|
||||
pusher Pusher
|
||||
}
|
||||
|
||||
func NewNotifier(st *store.Store, pusher Pusher) *Notifier {
|
||||
return &Notifier{store: st, pusher: pusher}
|
||||
}
|
||||
|
||||
func (n *Notifier) Notify(ctx context.Context, userID int64, category, typ, title, body, route string, data any) error {
|
||||
enabled, err := n.store.PrefEnabled(ctx, userID, category)
|
||||
if err != nil || !enabled {
|
||||
return nil
|
||||
}
|
||||
notif := &models.Notification{
|
||||
UserID: userID,
|
||||
Category: category,
|
||||
Type: typ,
|
||||
Title: title,
|
||||
Body: body,
|
||||
Route: route,
|
||||
}
|
||||
if data != nil {
|
||||
notif.Data = mustJSON(data)
|
||||
}
|
||||
if err := n.store.CreateNotification(ctx, notif); err != nil {
|
||||
return err
|
||||
}
|
||||
if n.pusher != nil {
|
||||
n.pusher.Push(userID, map[string]any{
|
||||
"type": "notification",
|
||||
"unread": true,
|
||||
"item": notif,
|
||||
})
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (n *Notifier) NotifyMany(ctx context.Context, userIDs []int64, category, typ, title, body, route string, data any) error {
|
||||
for _, uid := range userIDs {
|
||||
if err := n.Notify(ctx, uid, category, typ, title, body, route, data); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user