lists.go 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214
  1. package main
  2. import (
  3. "fmt"
  4. "net/http"
  5. "strconv"
  6. "github.com/gofrs/uuid"
  7. "github.com/knadh/listmonk/models"
  8. "github.com/lib/pq"
  9. "github.com/labstack/echo"
  10. )
  11. type listsWrap struct {
  12. Results []models.List `json:"results"`
  13. Total int `json:"total"`
  14. PerPage int `json:"per_page"`
  15. Page int `json:"page"`
  16. }
  17. var (
  18. listQuerySortFields = []string{"name", "type", "subscriber_count", "created_at", "updated_at"}
  19. )
  20. // handleGetLists retrieves lists with additional metadata like subscriber counts. This may be slow.
  21. func handleGetLists(c echo.Context) error {
  22. var (
  23. app = c.Get("app").(*App)
  24. out listsWrap
  25. pg = getPagination(c.QueryParams(), 20)
  26. orderBy = c.FormValue("order_by")
  27. order = c.FormValue("order")
  28. minimal, _ = strconv.ParseBool(c.FormValue("minimal"))
  29. listID, _ = strconv.Atoi(c.Param("id"))
  30. single = false
  31. )
  32. // Fetch one list.
  33. if listID > 0 {
  34. single = true
  35. }
  36. if !single && minimal {
  37. // Minimal query simply returns the list of all lists with no additional metadata. This is fast.
  38. if err := app.queries.GetLists.Select(&out.Results, "", "id"); err != nil {
  39. app.log.Printf("error fetching lists: %v", err)
  40. return echo.NewHTTPError(http.StatusInternalServerError,
  41. app.i18n.Ts("globals.messages.errorFetching",
  42. "name", "{globals.terms.lists}", "error", pqErrMsg(err)))
  43. }
  44. if len(out.Results) == 0 {
  45. return c.JSON(http.StatusOK, okResp{[]struct{}{}})
  46. }
  47. // Meta.
  48. out.Total = out.Results[0].Total
  49. out.Page = 1
  50. out.PerPage = out.Total
  51. if out.PerPage == 0 {
  52. out.PerPage = out.Total
  53. }
  54. return c.JSON(http.StatusOK, okResp{out})
  55. }
  56. // Sort params.
  57. if !strSliceContains(orderBy, listQuerySortFields) {
  58. orderBy = "created_at"
  59. }
  60. if order != sortAsc && order != sortDesc {
  61. order = sortAsc
  62. }
  63. if err := db.Select(&out.Results, fmt.Sprintf(app.queries.QueryLists, orderBy, order), listID, pg.Offset, pg.Limit); err != nil {
  64. app.log.Printf("error fetching lists: %v", err)
  65. return echo.NewHTTPError(http.StatusInternalServerError,
  66. app.i18n.Ts("globals.messages.errorFetching",
  67. "name", "{globals.terms.lists}", "error", pqErrMsg(err)))
  68. }
  69. if single && len(out.Results) == 0 {
  70. return echo.NewHTTPError(http.StatusBadRequest,
  71. app.i18n.Ts("globals.messages.notFound", "name", "{globals.terms.list}"))
  72. }
  73. if len(out.Results) == 0 {
  74. return c.JSON(http.StatusOK, okResp{[]struct{}{}})
  75. }
  76. // Replace null tags.
  77. for i, v := range out.Results {
  78. if v.Tags == nil {
  79. out.Results[i].Tags = make(pq.StringArray, 0)
  80. }
  81. }
  82. if single {
  83. return c.JSON(http.StatusOK, okResp{out.Results[0]})
  84. }
  85. // Meta.
  86. out.Total = out.Results[0].Total
  87. out.Page = pg.Page
  88. out.PerPage = pg.PerPage
  89. if out.PerPage == 0 {
  90. out.PerPage = out.Total
  91. }
  92. return c.JSON(http.StatusOK, okResp{out})
  93. }
  94. // handleCreateList handles list creation.
  95. func handleCreateList(c echo.Context) error {
  96. var (
  97. app = c.Get("app").(*App)
  98. o = models.List{}
  99. )
  100. if err := c.Bind(&o); err != nil {
  101. return err
  102. }
  103. // Validate.
  104. if !strHasLen(o.Name, 1, stdInputMaxLen) {
  105. return echo.NewHTTPError(http.StatusBadRequest, app.i18n.T("lists.invalidName"))
  106. }
  107. uu, err := uuid.NewV4()
  108. if err != nil {
  109. app.log.Printf("error generating UUID: %v", err)
  110. return echo.NewHTTPError(http.StatusInternalServerError,
  111. app.i18n.Ts("globals.messages.errorUUID", "error", err.Error()))
  112. }
  113. // Insert and read ID.
  114. var newID int
  115. o.UUID = uu.String()
  116. if err := app.queries.CreateList.Get(&newID,
  117. o.UUID,
  118. o.Name,
  119. o.Type,
  120. o.Optin,
  121. pq.StringArray(normalizeTags(o.Tags))); err != nil {
  122. app.log.Printf("error creating list: %v", err)
  123. return echo.NewHTTPError(http.StatusInternalServerError,
  124. app.i18n.Ts("globals.messages.errorCreating",
  125. "name", "{globals.terms.list}", "error", pqErrMsg(err)))
  126. }
  127. // Hand over to the GET handler to return the last insertion.
  128. return handleGetLists(copyEchoCtx(c, map[string]string{
  129. "id": fmt.Sprintf("%d", newID),
  130. }))
  131. }
  132. // handleUpdateList handles list modification.
  133. func handleUpdateList(c echo.Context) error {
  134. var (
  135. app = c.Get("app").(*App)
  136. id, _ = strconv.Atoi(c.Param("id"))
  137. )
  138. if id < 1 {
  139. return echo.NewHTTPError(http.StatusBadRequest, app.i18n.T("globals.messages.invalidID"))
  140. }
  141. // Incoming params.
  142. var o models.List
  143. if err := c.Bind(&o); err != nil {
  144. return err
  145. }
  146. res, err := app.queries.UpdateList.Exec(id,
  147. o.Name, o.Type, o.Optin, pq.StringArray(normalizeTags(o.Tags)))
  148. if err != nil {
  149. app.log.Printf("error updating list: %v", err)
  150. return echo.NewHTTPError(http.StatusInternalServerError,
  151. app.i18n.Ts("globals.messages.errorUpdating",
  152. "name", "{globals.terms.list}", "error", pqErrMsg(err)))
  153. }
  154. if n, _ := res.RowsAffected(); n == 0 {
  155. return echo.NewHTTPError(http.StatusBadRequest,
  156. app.i18n.Ts("globals.messages.notFound", "name", "{globals.terms.list}"))
  157. }
  158. return handleGetLists(c)
  159. }
  160. // handleDeleteLists handles list deletion, either a single one (ID in the URI), or a list.
  161. func handleDeleteLists(c echo.Context) error {
  162. var (
  163. app = c.Get("app").(*App)
  164. id, _ = strconv.ParseInt(c.Param("id"), 10, 64)
  165. ids pq.Int64Array
  166. )
  167. if id < 1 && len(ids) == 0 {
  168. return echo.NewHTTPError(http.StatusBadRequest, app.i18n.T("globals.messages.invalidID"))
  169. }
  170. if id > 0 {
  171. ids = append(ids, id)
  172. }
  173. if _, err := app.queries.DeleteLists.Exec(ids); err != nil {
  174. app.log.Printf("error deleting lists: %v", err)
  175. return echo.NewHTTPError(http.StatusInternalServerError,
  176. app.i18n.Ts("globals.messages.errorDeleting",
  177. "name", "{globals.terms.list}", "error", pqErrMsg(err)))
  178. }
  179. return c.JSON(http.StatusOK, okResp{true})
  180. }