queries.go 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168
  1. package main
  2. import (
  3. "context"
  4. "database/sql"
  5. "fmt"
  6. "time"
  7. "github.com/jmoiron/sqlx"
  8. "github.com/lib/pq"
  9. )
  10. // Queries contains all prepared SQL queries.
  11. type Queries struct {
  12. GetDashboardCharts *sqlx.Stmt `query:"get-dashboard-charts"`
  13. GetDashboardCounts *sqlx.Stmt `query:"get-dashboard-counts"`
  14. InsertSubscriber *sqlx.Stmt `query:"insert-subscriber"`
  15. UpsertSubscriber *sqlx.Stmt `query:"upsert-subscriber"`
  16. UpsertBlocklistSubscriber *sqlx.Stmt `query:"upsert-blocklist-subscriber"`
  17. GetSubscriber *sqlx.Stmt `query:"get-subscriber"`
  18. GetSubscribersByEmails *sqlx.Stmt `query:"get-subscribers-by-emails"`
  19. GetSubscriberLists *sqlx.Stmt `query:"get-subscriber-lists"`
  20. GetSubscriberListsLazy *sqlx.Stmt `query:"get-subscriber-lists-lazy"`
  21. SubscriberExists *sqlx.Stmt `query:"subscriber-exists"`
  22. UpdateSubscriber *sqlx.Stmt `query:"update-subscriber"`
  23. BlocklistSubscribers *sqlx.Stmt `query:"blocklist-subscribers"`
  24. AddSubscribersToLists *sqlx.Stmt `query:"add-subscribers-to-lists"`
  25. DeleteSubscriptions *sqlx.Stmt `query:"delete-subscriptions"`
  26. ConfirmSubscriptionOptin *sqlx.Stmt `query:"confirm-subscription-optin"`
  27. UnsubscribeSubscribersFromLists *sqlx.Stmt `query:"unsubscribe-subscribers-from-lists"`
  28. DeleteSubscribers *sqlx.Stmt `query:"delete-subscribers"`
  29. Unsubscribe *sqlx.Stmt `query:"unsubscribe"`
  30. ExportSubscriberData *sqlx.Stmt `query:"export-subscriber-data"`
  31. // Non-prepared arbitrary subscriber queries.
  32. QuerySubscribers string `query:"query-subscribers"`
  33. QuerySubscribersCount string `query:"query-subscribers-count"`
  34. QuerySubscribersForExport string `query:"query-subscribers-for-export"`
  35. QuerySubscribersTpl string `query:"query-subscribers-template"`
  36. DeleteSubscribersByQuery string `query:"delete-subscribers-by-query"`
  37. AddSubscribersToListsByQuery string `query:"add-subscribers-to-lists-by-query"`
  38. BlocklistSubscribersByQuery string `query:"blocklist-subscribers-by-query"`
  39. DeleteSubscriptionsByQuery string `query:"delete-subscriptions-by-query"`
  40. UnsubscribeSubscribersFromListsByQuery string `query:"unsubscribe-subscribers-from-lists-by-query"`
  41. CreateList *sqlx.Stmt `query:"create-list"`
  42. QueryLists string `query:"query-lists"`
  43. GetLists *sqlx.Stmt `query:"get-lists"`
  44. GetListsByOptin *sqlx.Stmt `query:"get-lists-by-optin"`
  45. UpdateList *sqlx.Stmt `query:"update-list"`
  46. UpdateListsDate *sqlx.Stmt `query:"update-lists-date"`
  47. DeleteLists *sqlx.Stmt `query:"delete-lists"`
  48. CreateCampaign *sqlx.Stmt `query:"create-campaign"`
  49. QueryCampaigns string `query:"query-campaigns"`
  50. GetCampaign *sqlx.Stmt `query:"get-campaign"`
  51. GetCampaignForPreview *sqlx.Stmt `query:"get-campaign-for-preview"`
  52. GetCampaignStats *sqlx.Stmt `query:"get-campaign-stats"`
  53. GetCampaignStatus *sqlx.Stmt `query:"get-campaign-status"`
  54. GetCampaignViewCounts *sqlx.Stmt `query:"get-campaign-view-counts"`
  55. GetCampaignClickCounts *sqlx.Stmt `query:"get-campaign-click-counts"`
  56. GetCampaignBounceCounts *sqlx.Stmt `query:"get-campaign-bounce-counts"`
  57. GetCampaignLinkCounts *sqlx.Stmt `query:"get-campaign-link-counts"`
  58. NextCampaigns *sqlx.Stmt `query:"next-campaigns"`
  59. NextCampaignSubscribers *sqlx.Stmt `query:"next-campaign-subscribers"`
  60. GetOneCampaignSubscriber *sqlx.Stmt `query:"get-one-campaign-subscriber"`
  61. UpdateCampaign *sqlx.Stmt `query:"update-campaign"`
  62. UpdateCampaignStatus *sqlx.Stmt `query:"update-campaign-status"`
  63. UpdateCampaignCounts *sqlx.Stmt `query:"update-campaign-counts"`
  64. RegisterCampaignView *sqlx.Stmt `query:"register-campaign-view"`
  65. DeleteCampaign *sqlx.Stmt `query:"delete-campaign"`
  66. InsertMedia *sqlx.Stmt `query:"insert-media"`
  67. GetMedia *sqlx.Stmt `query:"get-media"`
  68. DeleteMedia *sqlx.Stmt `query:"delete-media"`
  69. CreateTemplate *sqlx.Stmt `query:"create-template"`
  70. GetTemplates *sqlx.Stmt `query:"get-templates"`
  71. UpdateTemplate *sqlx.Stmt `query:"update-template"`
  72. SetDefaultTemplate *sqlx.Stmt `query:"set-default-template"`
  73. DeleteTemplate *sqlx.Stmt `query:"delete-template"`
  74. CreateLink *sqlx.Stmt `query:"create-link"`
  75. RegisterLinkClick *sqlx.Stmt `query:"register-link-click"`
  76. GetSettings *sqlx.Stmt `query:"get-settings"`
  77. UpdateSettings *sqlx.Stmt `query:"update-settings"`
  78. // GetStats *sqlx.Stmt `query:"get-stats"`
  79. RecordBounce *sqlx.Stmt `query:"record-bounce"`
  80. QueryBounces string `query:"query-bounces"`
  81. DeleteBounces *sqlx.Stmt `query:"delete-bounces"`
  82. DeleteBouncesBySubscriber *sqlx.Stmt `query:"delete-bounces-by-subscriber"`
  83. }
  84. // dbConf contains database config required for connecting to a DB.
  85. type dbConf struct {
  86. Host string `koanf:"host"`
  87. Port int `koanf:"port"`
  88. User string `koanf:"user"`
  89. Password string `koanf:"password"`
  90. DBName string `koanf:"database"`
  91. SSLMode string `koanf:"ssl_mode"`
  92. MaxOpen int `koanf:"max_open"`
  93. MaxIdle int `koanf:"max_idle"`
  94. MaxLifetime time.Duration `koanf:"max_lifetime"`
  95. }
  96. // connectDB initializes a database connection.
  97. func connectDB(c dbConf) (*sqlx.DB, error) {
  98. db, err := sqlx.Connect("postgres",
  99. fmt.Sprintf("host=%s port=%d user=%s password=%s dbname=%s sslmode=%s",
  100. c.Host, c.Port, c.User, c.Password, c.DBName, c.SSLMode))
  101. if err != nil {
  102. return nil, err
  103. }
  104. db.SetMaxOpenConns(c.MaxOpen)
  105. db.SetMaxIdleConns(c.MaxIdle)
  106. db.SetConnMaxLifetime(c.MaxLifetime)
  107. return db, nil
  108. }
  109. // compileSubscriberQueryTpl takes a arbitrary WHERE expressions
  110. // to filter subscribers from the subscribers table and prepares a query
  111. // out of it using the raw `query-subscribers-template` query template.
  112. // While doing this, a readonly transaction is created and the query is
  113. // dry run on it to ensure that it is indeed readonly.
  114. func (q *Queries) compileSubscriberQueryTpl(exp string, db *sqlx.DB) (string, error) {
  115. tx, err := db.BeginTxx(context.Background(), &sql.TxOptions{ReadOnly: true})
  116. if err != nil {
  117. return "", err
  118. }
  119. defer tx.Rollback()
  120. // Perform the dry run.
  121. if exp != "" {
  122. exp = " AND " + exp
  123. }
  124. stmt := fmt.Sprintf(q.QuerySubscribersTpl, exp)
  125. if _, err := tx.Exec(stmt, true, pq.Int64Array{}); err != nil {
  126. return "", err
  127. }
  128. return stmt, nil
  129. }
  130. // compileSubscriberQueryTpl takes a arbitrary WHERE expressions and a subscriber
  131. // query template that depends on the filter (eg: delete by query, blocklist by query etc.)
  132. // combines and executes them.
  133. func (q *Queries) execSubscriberQueryTpl(exp, tpl string, listIDs []int64, db *sqlx.DB, args ...interface{}) error {
  134. // Perform a dry run.
  135. filterExp, err := q.compileSubscriberQueryTpl(exp, db)
  136. if err != nil {
  137. return err
  138. }
  139. if len(listIDs) == 0 {
  140. listIDs = pq.Int64Array{}
  141. }
  142. // First argument is the boolean indicating if the query is a dry run.
  143. a := append([]interface{}{false, pq.Int64Array(listIDs)}, args...)
  144. if _, err := db.Exec(fmt.Sprintf(tpl, filterExp), a...); err != nil {
  145. return err
  146. }
  147. return nil
  148. }