import.go 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132
  1. package main
  2. import (
  3. "encoding/json"
  4. "io"
  5. "io/ioutil"
  6. "net/http"
  7. "strings"
  8. "github.com/knadh/listmonk/internal/subimporter"
  9. "github.com/knadh/listmonk/models"
  10. "github.com/labstack/echo"
  11. )
  12. // handleImportSubscribers handles the uploading and bulk importing of
  13. // a ZIP file of one or more CSV files.
  14. func handleImportSubscribers(c echo.Context) error {
  15. app := c.Get("app").(*App)
  16. // Is an import already running?
  17. if app.importer.GetStats().Status == subimporter.StatusImporting {
  18. return echo.NewHTTPError(http.StatusBadRequest, app.i18n.T("import.alreadyRunning"))
  19. }
  20. // Unmarsal the JSON params.
  21. var opt subimporter.SessionOpt
  22. if err := json.Unmarshal([]byte(c.FormValue("params")), &opt); err != nil {
  23. return echo.NewHTTPError(http.StatusBadRequest,
  24. app.i18n.Ts("import.invalidParams", "error", err.Error()))
  25. }
  26. // Validate mode.
  27. if opt.Mode != subimporter.ModeSubscribe && opt.Mode != subimporter.ModeBlocklist {
  28. return echo.NewHTTPError(http.StatusBadRequest, app.i18n.T("import.invalidMode"))
  29. }
  30. // If no status is specified, pick a default one.
  31. if opt.SubStatus == "" {
  32. switch opt.Mode {
  33. case subimporter.ModeSubscribe:
  34. opt.SubStatus = models.SubscriptionStatusUnconfirmed
  35. case subimporter.ModeBlocklist:
  36. opt.SubStatus = models.SubscriptionStatusUnsubscribed
  37. }
  38. }
  39. if opt.SubStatus != models.SubscriptionStatusUnconfirmed &&
  40. opt.SubStatus != models.SubscriptionStatusConfirmed &&
  41. opt.SubStatus != models.SubscriptionStatusUnsubscribed {
  42. return echo.NewHTTPError(http.StatusBadRequest, app.i18n.T("import.invalidSubStatus"))
  43. }
  44. if len(opt.Delim) != 1 {
  45. return echo.NewHTTPError(http.StatusBadRequest, app.i18n.T("import.invalidDelim"))
  46. }
  47. file, err := c.FormFile("file")
  48. if err != nil {
  49. return echo.NewHTTPError(http.StatusBadRequest,
  50. app.i18n.Ts("import.invalidFile", "error", err.Error()))
  51. }
  52. src, err := file.Open()
  53. if err != nil {
  54. return err
  55. }
  56. defer src.Close()
  57. out, err := ioutil.TempFile("", "listmonk")
  58. if err != nil {
  59. return echo.NewHTTPError(http.StatusInternalServerError,
  60. app.i18n.Ts("import.errorCopyingFile", "error", err.Error()))
  61. }
  62. defer out.Close()
  63. if _, err = io.Copy(out, src); err != nil {
  64. return echo.NewHTTPError(http.StatusInternalServerError,
  65. app.i18n.Ts("import.errorCopyingFile", "error", err.Error()))
  66. }
  67. // Start the importer session.
  68. opt.Filename = file.Filename
  69. impSess, err := app.importer.NewSession(opt)
  70. if err != nil {
  71. return echo.NewHTTPError(http.StatusInternalServerError,
  72. app.i18n.Ts("import.errorStarting", "error", err.Error()))
  73. }
  74. go impSess.Start()
  75. if strings.HasSuffix(strings.ToLower(file.Filename), ".csv") {
  76. go impSess.LoadCSV(out.Name(), rune(opt.Delim[0]))
  77. } else {
  78. // Only 1 CSV from the ZIP is considered. If multiple files have
  79. // to be processed, counting the net number of lines (to track progress),
  80. // keeping the global import state (failed / successful) etc. across
  81. // multiple files becomes complex. Instead, it's just easier for the
  82. // end user to concat multiple CSVs (if there are multiple in the first)
  83. // place and uploada as one in the first place.
  84. dir, files, err := impSess.ExtractZIP(out.Name(), 1)
  85. if err != nil {
  86. return echo.NewHTTPError(http.StatusInternalServerError,
  87. app.i18n.Ts("import.errorProcessingZIP", "error", err.Error()))
  88. }
  89. go impSess.LoadCSV(dir+"/"+files[0], rune(opt.Delim[0]))
  90. }
  91. return c.JSON(http.StatusOK, okResp{app.importer.GetStats()})
  92. }
  93. // handleGetImportSubscribers returns import statistics.
  94. func handleGetImportSubscribers(c echo.Context) error {
  95. var (
  96. app = c.Get("app").(*App)
  97. s = app.importer.GetStats()
  98. )
  99. return c.JSON(http.StatusOK, okResp{s})
  100. }
  101. // handleGetImportSubscriberStats returns import statistics.
  102. func handleGetImportSubscriberStats(c echo.Context) error {
  103. app := c.Get("app").(*App)
  104. return c.JSON(http.StatusOK, okResp{string(app.importer.GetLogs())})
  105. }
  106. // handleStopImportSubscribers sends a stop signal to the importer.
  107. // If there's an ongoing import, it'll be stopped, and if an import
  108. // is finished, it's state is cleared.
  109. func handleStopImportSubscribers(c echo.Context) error {
  110. app := c.Get("app").(*App)
  111. app.importer.Stop()
  112. return c.JSON(http.StatusOK, okResp{app.importer.GetStats()})
  113. }