prism/service/subscription/store.go
2026-02-26 18:35:11 -08:00

255 lines
6.3 KiB
Go

package subscription
import (
"database/sql"
"fmt"
)
type Store struct {
DB *sql.DB
}
func NewStore(db *sql.DB) (*Store, error) {
store := &Store{DB: db}
if err := store.createTables(); err != nil {
return nil, err
}
return store, nil
}
func (s *Store) createTables() error {
queries := []string{
`CREATE TABLE IF NOT EXISTS apps (
appName TEXT PRIMARY KEY
)`,
`CREATE TABLE IF NOT EXISTS subscriptions (
id TEXT PRIMARY KEY DEFAULT (hex(randomblob(16))),
appName TEXT NOT NULL,
channel TEXT NOT NULL,
signalGroupId TEXT,
signalAccount TEXT,
telegramChatId TEXT,
pushEndpoint TEXT,
p256dh TEXT,
auth TEXT,
vapidPrivateKey TEXT,
FOREIGN KEY(appName) REFERENCES apps(appName) ON DELETE CASCADE
)`,
`CREATE INDEX IF NOT EXISTS idx_subscriptions_appName ON subscriptions(appName)`,
}
for _, query := range queries {
if _, err := s.DB.Exec(query); err != nil {
return fmt.Errorf("failed to create tables: %w", err)
}
}
return nil
}
func (s *Store) RegisterApp(appName string) error {
_, err := s.DB.Exec(`INSERT INTO apps (appName) VALUES (?) ON CONFLICT(appName) DO NOTHING`, appName)
return err
}
func (s *Store) AddSubscription(sub Subscription) (string, error) {
if err := s.RegisterApp(sub.AppName); err != nil {
return "", err
}
var signalGroupID, signalAccount, telegramChatID, pushEndpoint, p256dh, auth, vapidPrivateKey *string
if sub.Signal != nil {
signalGroupID = &sub.Signal.GroupID
signalAccount = &sub.Signal.Account
}
if sub.Telegram != nil {
telegramChatID = &sub.Telegram.ChatID
}
if sub.WebPush != nil {
pushEndpoint = &sub.WebPush.Endpoint
p256dh = &sub.WebPush.P256dh
auth = &sub.WebPush.Auth
vapidPrivateKey = &sub.WebPush.VapidPrivateKey
}
var id string
err := s.DB.QueryRow(`
INSERT INTO subscriptions (appName, channel, signalGroupId, signalAccount, telegramChatId, pushEndpoint, p256dh, auth, vapidPrivateKey)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?) RETURNING id
`, sub.AppName, sub.Channel, signalGroupID, signalAccount, telegramChatID, pushEndpoint, p256dh, auth, vapidPrivateKey).Scan(&id)
return id, err
}
func (s *Store) GetApp(appName string) (*App, error) {
row := s.DB.QueryRow(`SELECT appName FROM apps WHERE appName = ?`, appName)
var app App
if err := row.Scan(&app.AppName); err == sql.ErrNoRows {
return nil, nil
} else if err != nil {
return nil, err
}
subs, err := s.GetSubscriptions(appName)
if err != nil {
return nil, err
}
app.Subscriptions = subs
return &app, nil
}
func (s *Store) GetSubscriptions(appName string) ([]Subscription, error) {
rows, err := s.DB.Query(`
SELECT id, appName, channel, signalGroupId, signalAccount, telegramChatId, pushEndpoint, p256dh, auth, vapidPrivateKey
FROM subscriptions
WHERE appName = ?
`, appName)
if err != nil {
return nil, err
}
defer rows.Close()
var subscriptions []Subscription
for rows.Next() {
var sub Subscription
var signalGroupID, signalAccount, telegramChatID, pushEndpoint, p256dh, auth, vapidPrivateKey sql.NullString
if err := rows.Scan(&sub.ID, &sub.AppName, &sub.Channel, &signalGroupID, &signalAccount, &telegramChatID, &pushEndpoint, &p256dh, &auth, &vapidPrivateKey); err != nil {
return nil, err
}
if signalGroupID.Valid && signalAccount.Valid {
sub.Signal = &SignalSubscription{
GroupID: signalGroupID.String,
Account: signalAccount.String,
}
}
if telegramChatID.Valid {
sub.Telegram = &TelegramSubscription{
ChatID: telegramChatID.String,
}
}
if pushEndpoint.Valid {
sub.WebPush = &WebPushSubscription{
Endpoint: pushEndpoint.String,
}
if p256dh.Valid {
sub.WebPush.P256dh = p256dh.String
}
if auth.Valid {
sub.WebPush.Auth = auth.String
}
if vapidPrivateKey.Valid {
sub.WebPush.VapidPrivateKey = vapidPrivateKey.String
}
}
subscriptions = append(subscriptions, sub)
}
return subscriptions, rows.Err()
}
func (s *Store) GetAllApps() ([]App, error) {
rows, err := s.DB.Query(`SELECT appName FROM apps ORDER BY appName`)
if err != nil {
return nil, err
}
appNames := make([]string, 0)
for rows.Next() {
var appName string
if err := rows.Scan(&appName); err != nil {
_ = rows.Close()
return nil, err
}
appNames = append(appNames, appName)
}
if err := rows.Err(); err != nil {
_ = rows.Close()
return nil, err
}
if err := rows.Close(); err != nil {
return nil, err
}
apps := make([]App, 0, len(appNames))
for _, appName := range appNames {
app := App{AppName: appName}
subs, err := s.GetSubscriptions(appName)
if err != nil {
return nil, err
}
app.Subscriptions = subs
apps = append(apps, app)
}
return apps, nil
}
func (s *Store) DeleteSubscription(subscriptionID string) error {
_, err := s.DB.Exec(`DELETE FROM subscriptions WHERE id = ?`, subscriptionID)
return err
}
func (s *Store) DeleteSubscriptionsByChannel(channel Channel) error {
_, err := s.DB.Exec(`DELETE FROM subscriptions WHERE channel = ?`, channel)
return err
}
func (s *Store) GetSubscription(subscriptionID string) (*Subscription, error) {
row := s.DB.QueryRow(`
SELECT id, appName, channel, signalGroupId, signalAccount, telegramChatId, pushEndpoint, p256dh, auth, vapidPrivateKey
FROM subscriptions
WHERE id = ?
`, subscriptionID)
var sub Subscription
var signalGroupID, signalAccount, telegramChatID, pushEndpoint, p256dh, auth, vapidPrivateKey sql.NullString
err := row.Scan(&sub.ID, &sub.AppName, &sub.Channel, &signalGroupID, &signalAccount, &telegramChatID, &pushEndpoint, &p256dh, &auth, &vapidPrivateKey)
if err == sql.ErrNoRows {
return nil, nil
}
if err != nil {
return nil, err
}
if signalGroupID.Valid && signalAccount.Valid {
sub.Signal = &SignalSubscription{
GroupID: signalGroupID.String,
Account: signalAccount.String,
}
}
if telegramChatID.Valid {
sub.Telegram = &TelegramSubscription{
ChatID: telegramChatID.String,
}
}
if pushEndpoint.Valid {
sub.WebPush = &WebPushSubscription{
Endpoint: pushEndpoint.String,
}
if p256dh.Valid {
sub.WebPush.P256dh = p256dh.String
}
if auth.Valid {
sub.WebPush.Auth = auth.String
}
if vapidPrivateKey.Valid {
sub.WebPush.VapidPrivateKey = vapidPrivateKey.String
}
}
return &sub, nil
}
func (s *Store) RemoveApp(appName string) error {
_, err := s.DB.Exec(`DELETE FROM apps WHERE appName = ?`, appName)
return err
}