tag.go 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177
  1. package server
  2. import (
  3. "encoding/json"
  4. "fmt"
  5. "net/http"
  6. "regexp"
  7. "sort"
  8. "github.com/pkg/errors"
  9. "github.com/usememos/memos/api"
  10. "github.com/usememos/memos/common"
  11. "golang.org/x/exp/slices"
  12. "github.com/labstack/echo/v4"
  13. )
  14. func (s *Server) registerTagRoutes(g *echo.Group) {
  15. g.POST("/tag", func(c echo.Context) error {
  16. ctx := c.Request().Context()
  17. userID, ok := c.Get(getUserIDContextKey()).(int)
  18. if !ok {
  19. return echo.NewHTTPError(http.StatusUnauthorized, "Missing user in session")
  20. }
  21. tagUpsert := &api.TagUpsert{}
  22. if err := json.NewDecoder(c.Request().Body).Decode(tagUpsert); err != nil {
  23. return echo.NewHTTPError(http.StatusBadRequest, "Malformatted post tag request").SetInternal(err)
  24. }
  25. if tagUpsert.Name == "" {
  26. return echo.NewHTTPError(http.StatusBadRequest, "Tag name shouldn't be empty")
  27. }
  28. tagUpsert.CreatorID = userID
  29. tag, err := s.Store.UpsertTag(ctx, tagUpsert)
  30. if err != nil {
  31. return echo.NewHTTPError(http.StatusInternalServerError, "Failed to upsert tag").SetInternal(err)
  32. }
  33. if err := s.createTagCreateActivity(c, tag); err != nil {
  34. return echo.NewHTTPError(http.StatusInternalServerError, "Failed to create activity").SetInternal(err)
  35. }
  36. return c.JSON(http.StatusOK, composeResponse(tag.Name))
  37. })
  38. g.GET("/tag", func(c echo.Context) error {
  39. ctx := c.Request().Context()
  40. userID, ok := c.Get(getUserIDContextKey()).(int)
  41. if !ok {
  42. return echo.NewHTTPError(http.StatusBadRequest, "Missing user id to find tag")
  43. }
  44. tagFind := &api.TagFind{
  45. CreatorID: userID,
  46. }
  47. tagList, err := s.Store.FindTagList(ctx, tagFind)
  48. if err != nil {
  49. return echo.NewHTTPError(http.StatusInternalServerError, "Failed to find tag list").SetInternal(err)
  50. }
  51. tagNameList := []string{}
  52. for _, tag := range tagList {
  53. tagNameList = append(tagNameList, tag.Name)
  54. }
  55. return c.JSON(http.StatusOK, composeResponse(tagNameList))
  56. })
  57. g.GET("/tag/suggestion", func(c echo.Context) error {
  58. ctx := c.Request().Context()
  59. userID, ok := c.Get(getUserIDContextKey()).(int)
  60. if !ok {
  61. return echo.NewHTTPError(http.StatusBadRequest, "Missing user session")
  62. }
  63. contentSearch := "#"
  64. normalRowStatus := api.Normal
  65. memoFind := api.MemoFind{
  66. CreatorID: &userID,
  67. ContentSearch: &contentSearch,
  68. RowStatus: &normalRowStatus,
  69. }
  70. memoList, err := s.Store.FindMemoList(ctx, &memoFind)
  71. if err != nil {
  72. return echo.NewHTTPError(http.StatusInternalServerError, "Failed to find memo list").SetInternal(err)
  73. }
  74. tagFind := &api.TagFind{
  75. CreatorID: userID,
  76. }
  77. existTagList, err := s.Store.FindTagList(ctx, tagFind)
  78. if err != nil {
  79. return echo.NewHTTPError(http.StatusInternalServerError, "Failed to find tag list").SetInternal(err)
  80. }
  81. tagNameList := []string{}
  82. for _, tag := range existTagList {
  83. tagNameList = append(tagNameList, tag.Name)
  84. }
  85. tagMapSet := make(map[string]bool)
  86. for _, memo := range memoList {
  87. for _, tag := range findTagListFromMemoContent(memo.Content) {
  88. if !slices.Contains(tagNameList, tag) {
  89. tagMapSet[tag] = true
  90. }
  91. }
  92. }
  93. tagList := []string{}
  94. for tag := range tagMapSet {
  95. tagList = append(tagList, tag)
  96. }
  97. sort.Strings(tagList)
  98. return c.JSON(http.StatusOK, composeResponse(tagList))
  99. })
  100. g.POST("/tag/delete", func(c echo.Context) error {
  101. ctx := c.Request().Context()
  102. userID, ok := c.Get(getUserIDContextKey()).(int)
  103. if !ok {
  104. return echo.NewHTTPError(http.StatusUnauthorized, "Missing user in session")
  105. }
  106. tagDelete := &api.TagDelete{}
  107. if err := json.NewDecoder(c.Request().Body).Decode(tagDelete); err != nil {
  108. return echo.NewHTTPError(http.StatusBadRequest, "Malformatted post tag request").SetInternal(err)
  109. }
  110. if tagDelete.Name == "" {
  111. return echo.NewHTTPError(http.StatusBadRequest, "Tag name shouldn't be empty")
  112. }
  113. tagDelete.CreatorID = userID
  114. if err := s.Store.DeleteTag(ctx, tagDelete); err != nil {
  115. if common.ErrorCode(err) == common.NotFound {
  116. return echo.NewHTTPError(http.StatusNotFound, fmt.Sprintf("Tag name not found: %s", tagDelete.Name))
  117. }
  118. return echo.NewHTTPError(http.StatusInternalServerError, fmt.Sprintf("Failed to delete tag name: %v", tagDelete.Name)).SetInternal(err)
  119. }
  120. return c.JSON(http.StatusOK, true)
  121. })
  122. }
  123. var tagRegexp = regexp.MustCompile(`#([^\s#]+)`)
  124. func findTagListFromMemoContent(memoContent string) []string {
  125. tagMapSet := make(map[string]bool)
  126. matches := tagRegexp.FindAllStringSubmatch(memoContent, -1)
  127. for _, v := range matches {
  128. tagName := v[1]
  129. tagMapSet[tagName] = true
  130. }
  131. tagList := []string{}
  132. for tag := range tagMapSet {
  133. tagList = append(tagList, tag)
  134. }
  135. sort.Strings(tagList)
  136. return tagList
  137. }
  138. func (s *Server) createTagCreateActivity(c echo.Context, tag *api.Tag) error {
  139. ctx := c.Request().Context()
  140. payload := api.ActivityTagCreatePayload{
  141. TagName: tag.Name,
  142. }
  143. payloadBytes, err := json.Marshal(payload)
  144. if err != nil {
  145. return errors.Wrap(err, "failed to marshal activity payload")
  146. }
  147. activity, err := s.Store.CreateActivity(ctx, &api.ActivityCreate{
  148. CreatorID: tag.CreatorID,
  149. Type: api.ActivityTagCreate,
  150. Level: api.ActivityInfo,
  151. Payload: string(payloadBytes),
  152. })
  153. if err != nil || activity == nil {
  154. return errors.Wrap(err, "failed to create activity")
  155. }
  156. return err
  157. }