main.go 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279
  1. package main
  2. import (
  3. "fmt"
  4. "html/template"
  5. "log"
  6. "os"
  7. "path/filepath"
  8. "strings"
  9. "time"
  10. _ "github.com/jinzhu/gorm/dialects/postgres"
  11. "github.com/jmoiron/sqlx"
  12. "github.com/knadh/goyesql"
  13. "github.com/knadh/listmonk/messenger"
  14. "github.com/knadh/listmonk/runner"
  15. "github.com/knadh/listmonk/subimporter"
  16. "github.com/labstack/echo"
  17. flag "github.com/spf13/pflag"
  18. "github.com/spf13/viper"
  19. )
  20. var logger *log.Logger
  21. type constants struct {
  22. AssetPath string `mapstructure:"asset_path"`
  23. RootURL string `mapstructure:"root"`
  24. UploadPath string `mapstructure:"upload_path"`
  25. UploadURI string `mapstructure:"upload_uri"`
  26. FromEmail string `mapstructure:"from_email"`
  27. }
  28. // App contains the "global" components that are
  29. // passed around, especially through HTTP handlers.
  30. type App struct {
  31. Constants *constants
  32. DB *sqlx.DB
  33. Queries *Queries
  34. Importer *subimporter.Importer
  35. Runner *runner.Runner
  36. Logger *log.Logger
  37. Messenger messenger.Messenger
  38. }
  39. func init() {
  40. logger = log.New(os.Stdout, "SYS: ", log.Ldate|log.Ltime|log.Lshortfile)
  41. // Register --help handler.
  42. flagSet := flag.NewFlagSet("config", flag.ContinueOnError)
  43. flagSet.Usage = func() {
  44. fmt.Println(flagSet.FlagUsages())
  45. os.Exit(0)
  46. }
  47. // Setup the default configuration.
  48. viper.SetConfigName("config")
  49. flagSet.StringSlice("config", []string{"config.toml"},
  50. "Path to one or more config files (will be merged in order)")
  51. flagSet.Bool("install", false, "Run first time installation")
  52. flagSet.Bool("version", false, "Current version of the build")
  53. // Process flags.
  54. flagSet.Parse(os.Args[1:])
  55. viper.BindPFlags(flagSet)
  56. // Read the config files.
  57. cfgs := viper.GetStringSlice("config")
  58. for _, c := range cfgs {
  59. logger.Printf("reading config: %s", c)
  60. viper.SetConfigFile(c)
  61. if err := viper.MergeInConfig(); err != nil {
  62. logger.Fatalf("error reading config: %s", err)
  63. }
  64. }
  65. }
  66. // registerHandlers registers HTTP handlers.
  67. func registerHandlers(e *echo.Echo) {
  68. e.GET("/", handleIndexPage)
  69. e.GET("/api/config.js", handleGetConfigScript)
  70. e.GET("/api/dashboard/stats", handleGetDashboardStats)
  71. e.GET("/api/users", handleGetUsers)
  72. e.POST("/api/users", handleCreateUser)
  73. e.DELETE("/api/users/:id", handleDeleteUser)
  74. e.GET("/api/subscribers/:id", handleGetSubscriber)
  75. e.GET("/api/subscribers", handleQuerySubscribers)
  76. e.POST("/api/subscribers", handleCreateSubscriber)
  77. e.PUT("/api/subscribers/:id", handleUpdateSubscriber)
  78. e.DELETE("/api/subscribers/:id", handleDeleteSubscribers)
  79. e.DELETE("/api/subscribers", handleDeleteSubscribers)
  80. e.POST("/api/subscribers/lists", handleQuerySubscribersIntoLists)
  81. e.GET("/api/import/subscribers", handleGetImportSubscribers)
  82. e.GET("/api/import/subscribers/logs", handleGetImportSubscriberLogs)
  83. e.POST("/api/import/subscribers", handleImportSubscribers)
  84. e.DELETE("/api/import/subscribers", handleStopImportSubscribers)
  85. e.GET("/api/lists", handleGetLists)
  86. e.GET("/api/lists/:id", handleGetLists)
  87. e.POST("/api/lists", handleCreateList)
  88. e.PUT("/api/lists/:id", handleUpdateList)
  89. e.DELETE("/api/lists/:id", handleDeleteLists)
  90. e.GET("/api/campaigns", handleGetCampaigns)
  91. e.GET("/api/campaigns/running/stats", handleGetRunningCampaignStats)
  92. e.GET("/api/campaigns/:id", handleGetCampaigns)
  93. e.GET("/api/campaigns/:id/preview", handlePreviewCampaign)
  94. e.POST("/api/campaigns/:id/preview", handlePreviewCampaign)
  95. e.POST("/api/campaigns/:id/test", handleTestCampaign)
  96. e.POST("/api/campaigns", handleCreateCampaign)
  97. e.PUT("/api/campaigns/:id", handleUpdateCampaign)
  98. e.PUT("/api/campaigns/:id/status", handleUpdateCampaignStatus)
  99. e.DELETE("/api/campaigns/:id", handleDeleteCampaign)
  100. e.GET("/api/media", handleGetMedia)
  101. e.POST("/api/media", handleUploadMedia)
  102. e.DELETE("/api/media/:id", handleDeleteMedia)
  103. e.GET("/api/templates", handleGetTemplates)
  104. e.GET("/api/templates/:id", handleGetTemplates)
  105. e.GET("/api/templates/:id/preview", handlePreviewTemplate)
  106. e.POST("/api/templates/preview", handlePreviewTemplate)
  107. e.POST("/api/templates", handleCreateTemplate)
  108. e.PUT("/api/templates/:id", handleUpdateTemplate)
  109. e.PUT("/api/templates/:id/default", handleTemplateSetDefault)
  110. e.DELETE("/api/templates/:id", handleDeleteTemplate)
  111. // Subscriber facing views.
  112. e.GET("/unsubscribe/:campUUID/:subUUID", handleUnsubscribePage)
  113. e.POST("/unsubscribe/:campUUID/:subUUID", handleUnsubscribePage)
  114. e.GET("/link/:linkUUID/:campUUID/:subUUID", handleLinkRedirect)
  115. e.GET("/campaign/:campUUID/:subUUID/px.png", handleRegisterCampaignView)
  116. // Static views.
  117. e.GET("/lists", handleIndexPage)
  118. e.GET("/subscribers", handleIndexPage)
  119. e.GET("/subscribers/lists/:listID", handleIndexPage)
  120. e.GET("/subscribers/import", handleIndexPage)
  121. e.GET("/campaigns", handleIndexPage)
  122. e.GET("/campaigns/new", handleIndexPage)
  123. e.GET("/campaigns/media", handleIndexPage)
  124. e.GET("/campaigns/templates", handleIndexPage)
  125. e.GET("/campaigns/:campignID", handleIndexPage)
  126. }
  127. // initMessengers initializes various messaging backends.
  128. func initMessengers(r *runner.Runner) messenger.Messenger {
  129. // Load SMTP configurations for the default e-mail Messenger.
  130. var srv []messenger.Server
  131. for name := range viper.GetStringMapString("smtp") {
  132. if !viper.GetBool(fmt.Sprintf("smtp.%s.enabled", name)) {
  133. logger.Printf("skipped SMTP config %s", name)
  134. continue
  135. }
  136. var s messenger.Server
  137. viper.UnmarshalKey("smtp."+name, &s)
  138. s.Name = name
  139. s.SendTimeout = s.SendTimeout * time.Millisecond
  140. srv = append(srv, s)
  141. logger.Printf("loaded SMTP config %s (%s@%s)", s.Name, s.Username, s.Host)
  142. }
  143. msgr, err := messenger.NewEmailer(srv...)
  144. if err != nil {
  145. logger.Fatalf("error loading e-mail messenger: %v", err)
  146. }
  147. if err := r.AddMessenger(msgr); err != nil {
  148. logger.Printf("error registering messenger %s", err)
  149. }
  150. return msgr
  151. }
  152. func main() {
  153. // Connect to the DB.
  154. db, err := connectDB(viper.GetString("db.host"),
  155. viper.GetInt("db.port"),
  156. viper.GetString("db.user"),
  157. viper.GetString("db.password"),
  158. viper.GetString("db.database"))
  159. if err != nil {
  160. logger.Fatalf("error connecting to DB: %v", err)
  161. }
  162. defer db.Close()
  163. var c constants
  164. viper.UnmarshalKey("app", &c)
  165. c.RootURL = strings.TrimRight(c.RootURL, "/")
  166. c.UploadURI = filepath.Clean(c.UploadURI)
  167. c.AssetPath = filepath.Clean(c.AssetPath)
  168. // Initialize the app context that's passed around.
  169. app := &App{
  170. Constants: &c,
  171. DB: db,
  172. Logger: logger,
  173. }
  174. // Load SQL queries.
  175. qMap, err := goyesql.ParseFile("queries.sql")
  176. if err != nil {
  177. logger.Fatalf("error loading SQL queries: %v", err)
  178. }
  179. // First time installation.
  180. if viper.GetBool("install") {
  181. install(app, qMap)
  182. return
  183. }
  184. // Map queries to the query container.
  185. q := &Queries{}
  186. if err := scanQueriesToStruct(q, qMap, db.Unsafe()); err != nil {
  187. logger.Fatalf("no SQL queries loaded: %v", err)
  188. }
  189. app.Queries = q
  190. app.Importer = subimporter.New(q.UpsertSubscriber.Stmt, q.BlacklistSubscriber.Stmt, db.DB)
  191. // Campaign daemon.
  192. r := runner.New(runner.Config{
  193. Concurrency: viper.GetInt("app.concurrency"),
  194. MaxSendErrors: viper.GetInt("app.max_send_errors"),
  195. // url.com/unsubscribe/{campaign_uuid}/{subscriber_uuid}
  196. UnsubscribeURL: fmt.Sprintf("%s/unsubscribe/%%s/%%s", app.Constants.RootURL),
  197. // url.com/link/{campaign_uuid}/{subscriber_uuid}/{link_uuid}
  198. LinkTrackURL: fmt.Sprintf("%s/link/%%s/%%s/%%s", app.Constants.RootURL),
  199. // url.com/campaign/{campaign_uuid}/{subscriber_uuid}/px.png
  200. ViewTrackURL: fmt.Sprintf("%s/campaign/%%s/%%s/px.png", app.Constants.RootURL),
  201. }, newRunnerDB(q), logger)
  202. app.Runner = r
  203. // Add messengers.
  204. app.Messenger = initMessengers(app.Runner)
  205. go r.Run(time.Duration(time.Second * 5))
  206. r.SpawnWorkers()
  207. // Initialize the server.
  208. var srv = echo.New()
  209. srv.Use(func(next echo.HandlerFunc) echo.HandlerFunc {
  210. return func(c echo.Context) error {
  211. c.Set("app", app)
  212. return next(c)
  213. }
  214. })
  215. // User facing templates.
  216. tpl, err := template.ParseGlob("public/templates/*.html")
  217. if err != nil {
  218. logger.Fatalf("error parsing public templates: %v", err)
  219. }
  220. srv.Renderer = &Template{
  221. templates: tpl,
  222. }
  223. srv.HideBanner = true
  224. // Register HTTP middleware.
  225. // e.Use(session.Middleware(sessions.NewCookieStore([]byte("secret"))))
  226. // e.Use(authSession)
  227. srv.Static("/static", filepath.Join(filepath.Clean(viper.GetString("app.asset_path")), "static"))
  228. srv.Static("/static/public", "frontend/my/public")
  229. srv.Static("/public/static", "public/static")
  230. srv.Static(filepath.Clean(viper.GetString("app.upload_uri")),
  231. filepath.Clean(viper.GetString("app.upload_path")))
  232. registerHandlers(srv)
  233. srv.Logger.Fatal(srv.Start(viper.GetString("app.address")))
  234. }