web_push.go 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196
  1. package server
  2. import (
  3. "database/sql"
  4. "fmt"
  5. "time"
  6. "github.com/SherClockHolmes/webpush-go"
  7. _ "github.com/mattn/go-sqlite3" // SQLite driver
  8. )
  9. const (
  10. createWebPushSubscriptionsTableQuery = `
  11. BEGIN;
  12. CREATE TABLE IF NOT EXISTS subscriptions (
  13. id INTEGER PRIMARY KEY AUTOINCREMENT,
  14. topic TEXT NOT NULL,
  15. user_id TEXT,
  16. endpoint TEXT NOT NULL,
  17. key_auth TEXT NOT NULL,
  18. key_p256dh TEXT NOT NULL,
  19. updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
  20. warning_sent BOOLEAN DEFAULT FALSE
  21. );
  22. CREATE INDEX IF NOT EXISTS idx_topic ON subscriptions (topic);
  23. CREATE INDEX IF NOT EXISTS idx_endpoint ON subscriptions (endpoint);
  24. CREATE UNIQUE INDEX IF NOT EXISTS idx_topic_endpoint ON subscriptions (topic, endpoint);
  25. COMMIT;
  26. `
  27. insertWebPushSubscriptionQuery = `
  28. INSERT OR REPLACE INTO subscriptions (topic, user_id, endpoint, key_auth, key_p256dh)
  29. VALUES (?, ?, ?, ?, ?)
  30. `
  31. deleteWebPushSubscriptionByEndpointQuery = `DELETE FROM subscriptions WHERE endpoint = ?`
  32. deleteWebPushSubscriptionByUserIDQuery = `DELETE FROM subscriptions WHERE user_id = ?`
  33. deleteWebPushSubscriptionsByAgeQuery = `DELETE FROM subscriptions WHERE warning_sent = 1 AND updated_at <= datetime('now', ?)`
  34. selectWebPushSubscriptionsForTopicQuery = `SELECT endpoint, key_auth, key_p256dh, user_id FROM subscriptions WHERE topic = ?`
  35. selectWebPushSubscriptionsExpiringSoonQuery = `SELECT DISTINCT endpoint, key_auth, key_p256dh FROM subscriptions WHERE warning_sent = 0 AND updated_at <= datetime('now', ?)`
  36. updateWarningSentQuery = `UPDATE subscriptions SET warning_sent = true WHERE warning_sent = 0 AND updated_at <= datetime('now', ?)`
  37. selectWebPushSubscriptionsCountQuery = `SELECT COUNT(*) FROM subscriptions`
  38. )
  39. type webPushStore struct {
  40. db *sql.DB
  41. }
  42. func newWebPushStore(filename string) (*webPushStore, error) {
  43. db, err := sql.Open("sqlite3", filename)
  44. if err != nil {
  45. return nil, err
  46. }
  47. if err := setupSubscriptionsDB(db); err != nil {
  48. return nil, err
  49. }
  50. return &webPushStore{
  51. db: db,
  52. }, nil
  53. }
  54. func setupSubscriptionsDB(db *sql.DB) error {
  55. // If 'subscriptions' table does not exist, this must be a new database
  56. rows, err := db.Query(selectWebPushSubscriptionsCountQuery)
  57. if err != nil {
  58. return setupNewSubscriptionsDB(db)
  59. }
  60. return rows.Close()
  61. }
  62. func setupNewSubscriptionsDB(db *sql.DB) error {
  63. if _, err := db.Exec(createWebPushSubscriptionsTableQuery); err != nil {
  64. return err
  65. }
  66. return nil
  67. }
  68. func (c *webPushStore) UpdateSubscriptions(topics []string, userID string, subscription webpush.Subscription) error {
  69. tx, err := c.db.Begin()
  70. if err != nil {
  71. return err
  72. }
  73. defer tx.Rollback()
  74. if err = c.RemoveByEndpoint(subscription.Endpoint); err != nil {
  75. return err
  76. }
  77. for _, topic := range topics {
  78. if err := c.AddSubscription(topic, userID, subscription); err != nil {
  79. return err
  80. }
  81. }
  82. return tx.Commit()
  83. }
  84. func (c *webPushStore) AddSubscription(topic string, userID string, subscription webpush.Subscription) error {
  85. _, err := c.db.Exec(
  86. insertWebPushSubscriptionQuery,
  87. topic,
  88. userID,
  89. subscription.Endpoint,
  90. subscription.Keys.Auth,
  91. subscription.Keys.P256dh,
  92. )
  93. return err
  94. }
  95. func (c *webPushStore) SubscriptionsForTopic(topic string) (subscriptions []*webPushSubscription, err error) {
  96. rows, err := c.db.Query(selectWebPushSubscriptionsForTopicQuery, topic)
  97. if err != nil {
  98. return nil, err
  99. }
  100. defer rows.Close()
  101. var data []*webPushSubscription
  102. for rows.Next() {
  103. var userID, endpoint, auth, p256dh string
  104. if err = rows.Scan(&endpoint, &auth, &p256dh, &userID); err != nil {
  105. return nil, err
  106. }
  107. data = append(data, &webPushSubscription{
  108. UserID: userID,
  109. BrowserSubscription: webpush.Subscription{
  110. Endpoint: endpoint,
  111. Keys: webpush.Keys{
  112. Auth: auth,
  113. P256dh: p256dh,
  114. },
  115. },
  116. })
  117. }
  118. return data, nil
  119. }
  120. func (c *webPushStore) ExpireAndGetExpiringSubscriptions(warningDuration time.Duration, expiryDuration time.Duration) (subscriptions []webPushSubscription, err error) {
  121. // TODO this should be two functions
  122. tx, err := c.db.Begin()
  123. if err != nil {
  124. return nil, err
  125. }
  126. defer tx.Rollback()
  127. _, err = tx.Exec(deleteWebPushSubscriptionsByAgeQuery, fmt.Sprintf("-%.2f seconds", expiryDuration.Seconds()))
  128. if err != nil {
  129. return nil, err
  130. }
  131. rows, err := tx.Query(selectWebPushSubscriptionsExpiringSoonQuery, fmt.Sprintf("-%.2f seconds", warningDuration.Seconds()))
  132. if err != nil {
  133. return nil, err
  134. }
  135. defer rows.Close()
  136. var data []webPushSubscription
  137. for rows.Next() {
  138. i := webPushSubscription{}
  139. err = rows.Scan(&i.BrowserSubscription.Endpoint, &i.BrowserSubscription.Keys.Auth, &i.BrowserSubscription.Keys.P256dh)
  140. fmt.Printf("%v+", i)
  141. if err != nil {
  142. return nil, err
  143. }
  144. data = append(data, i)
  145. }
  146. // also set warning as sent
  147. _, err = tx.Exec(updateWarningSentQuery, fmt.Sprintf("-%.2f seconds", warningDuration.Seconds()))
  148. if err != nil {
  149. return nil, err
  150. }
  151. if err = tx.Commit(); err != nil {
  152. return nil, err
  153. }
  154. return data, nil
  155. }
  156. func (c *webPushStore) RemoveByEndpoint(endpoint string) error {
  157. _, err := c.db.Exec(
  158. deleteWebPushSubscriptionByEndpointQuery,
  159. endpoint,
  160. )
  161. return err
  162. }
  163. func (c *webPushStore) RemoveByUserID(userID string) error {
  164. _, err := c.db.Exec(
  165. deleteWebPushSubscriptionByUserIDQuery,
  166. userID,
  167. )
  168. return err
  169. }
  170. func (c *webPushStore) Close() error {
  171. return c.db.Close()
  172. }