- N1.1: POST /notifications/push/subscribe, PushService, migration 090 - N1.2: Send Web Push on follow/like/comment/message via CreateNotification - N1.3: GET/PUT /notifications/preferences, migration 093 - Shared NotificationService with PushService for profile, track, comment handlers - Fix MockSocialService GetGlobalFeed, GetTrendingHashtags for tests
93 lines
2.7 KiB
Go
93 lines
2.7 KiB
Go
package services
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
|
|
"github.com/SherClockHolmes/webpush-go"
|
|
"github.com/google/uuid"
|
|
"go.uber.org/zap"
|
|
"gorm.io/gorm"
|
|
)
|
|
|
|
// PushSubscription represents a Web Push subscription (v0.302 N1)
|
|
type PushSubscription struct {
|
|
ID uuid.UUID `gorm:"type:uuid;primaryKey;default:gen_random_uuid()" json:"id"`
|
|
UserID uuid.UUID `gorm:"type:uuid;not null;index" json:"user_id"`
|
|
Endpoint string `gorm:"type:text;not null" json:"endpoint"`
|
|
P256dh string `gorm:"type:text;not null" json:"p256dh"`
|
|
Auth string `gorm:"type:text;not null" json:"auth"`
|
|
CreatedAt string `gorm:"default:CURRENT_TIMESTAMP" json:"created_at"`
|
|
}
|
|
|
|
func (PushSubscription) TableName() string {
|
|
return "push_subscriptions"
|
|
}
|
|
|
|
// PushService handles Web Push subscriptions and sending
|
|
type PushService struct {
|
|
db *gorm.DB
|
|
logger *zap.Logger
|
|
vapidPublicKey string
|
|
vapidPrivateKey string
|
|
}
|
|
|
|
// NewPushService creates a new PushService
|
|
func NewPushService(db *gorm.DB, logger *zap.Logger, vapidPublic, vapidPrivate string) *PushService {
|
|
return &PushService{
|
|
db: db,
|
|
logger: logger,
|
|
vapidPublicKey: vapidPublic,
|
|
vapidPrivateKey: vapidPrivate,
|
|
}
|
|
}
|
|
|
|
// SubscribePush stores a Web Push subscription for a user
|
|
func (s *PushService) SubscribePush(ctx context.Context, userID uuid.UUID, endpoint, p256dh, auth string) error {
|
|
sub := &PushSubscription{
|
|
UserID: userID,
|
|
Endpoint: endpoint,
|
|
P256dh: p256dh,
|
|
Auth: auth,
|
|
}
|
|
if err := s.db.WithContext(ctx).Create(sub).Error; err != nil {
|
|
return fmt.Errorf("failed to store subscription: %w", err)
|
|
}
|
|
s.logger.Info("Push subscription stored", zap.String("user_id", userID.String()))
|
|
return nil
|
|
}
|
|
|
|
// SendPushToUser sends a Web Push notification to all subscriptions of a user
|
|
func (s *PushService) SendPushToUser(ctx context.Context, userID uuid.UUID, title, body, link string) error {
|
|
if s.vapidPrivateKey == "" {
|
|
return nil
|
|
}
|
|
var subs []PushSubscription
|
|
if err := s.db.WithContext(ctx).Where("user_id = ?", userID).Find(&subs).Error; err != nil {
|
|
return err
|
|
}
|
|
payload, _ := json.Marshal(map[string]string{
|
|
"title": title,
|
|
"body": body,
|
|
"link": link,
|
|
})
|
|
for _, ps := range subs {
|
|
subscription := webpush.Subscription{
|
|
Endpoint: ps.Endpoint,
|
|
Keys: webpush.Keys{
|
|
P256dh: ps.P256dh,
|
|
Auth: ps.Auth,
|
|
},
|
|
}
|
|
_, err := webpush.SendNotification(payload, &subscription, &webpush.Options{
|
|
VAPIDPublicKey: s.vapidPublicKey,
|
|
VAPIDPrivateKey: s.vapidPrivateKey,
|
|
TTL: 30,
|
|
})
|
|
if err != nil {
|
|
s.logger.Warn("Failed to send push", zap.Error(err), zap.String("user_id", userID.String()))
|
|
}
|
|
}
|
|
return nil
|
|
}
|