memo_relation.go 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156
  1. package v1
  2. import (
  3. "encoding/json"
  4. "fmt"
  5. "net/http"
  6. "github.com/labstack/echo/v4"
  7. "github.com/usememos/memos/internal/util"
  8. "github.com/usememos/memos/store"
  9. )
  10. type MemoRelationType string
  11. const (
  12. MemoRelationReference MemoRelationType = "REFERENCE"
  13. MemoRelationComment MemoRelationType = "COMMENT"
  14. )
  15. func (t MemoRelationType) String() string {
  16. return string(t)
  17. }
  18. type MemoRelation struct {
  19. MemoID int32 `json:"memoId"`
  20. RelatedMemoID int32 `json:"relatedMemoId"`
  21. Type MemoRelationType `json:"type"`
  22. }
  23. type UpsertMemoRelationRequest struct {
  24. RelatedMemoID int32 `json:"relatedMemoId"`
  25. Type MemoRelationType `json:"type"`
  26. }
  27. func (s *APIV1Service) registerMemoRelationRoutes(g *echo.Group) {
  28. g.GET("/memo/:memoId/relation", s.GetMemoRelationList)
  29. g.POST("/memo/:memoId/relation", s.CreateMemoRelation)
  30. g.DELETE("/memo/:memoId/relation/:relatedMemoId/type/:relationType", s.DeleteMemoRelation)
  31. }
  32. // GetMemoRelationList godoc
  33. //
  34. // @Summary Get a list of Memo Relations
  35. // @Tags memo-relation
  36. // @Accept json
  37. // @Produce json
  38. // @Param memoId path int true "ID of memo to find relations"
  39. // @Success 200 {object} []store.MemoRelation "Memo relation information list"
  40. // @Failure 400 {object} nil "ID is not a number: %s"
  41. // @Failure 500 {object} nil "Failed to list memo relations"
  42. // @Router /api/v1/memo/{memoId}/relation [GET]
  43. func (s *APIV1Service) GetMemoRelationList(c echo.Context) error {
  44. ctx := c.Request().Context()
  45. memoID, err := util.ConvertStringToInt32(c.Param("memoId"))
  46. if err != nil {
  47. return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("ID is not a number: %s", c.Param("memoId"))).SetInternal(err)
  48. }
  49. memoRelationList, err := s.Store.ListMemoRelations(ctx, &store.FindMemoRelation{
  50. MemoID: &memoID,
  51. })
  52. if err != nil {
  53. return echo.NewHTTPError(http.StatusInternalServerError, "Failed to list memo relations").SetInternal(err)
  54. }
  55. return c.JSON(http.StatusOK, memoRelationList)
  56. }
  57. // CreateMemoRelation godoc
  58. //
  59. // @Summary Create Memo Relation
  60. // @Description Create a relation between two memos
  61. // @Tags memo-relation
  62. // @Accept json
  63. // @Produce json
  64. // @Param memoId path int true "ID of memo to relate"
  65. // @Param body body UpsertMemoRelationRequest true "Memo relation object"
  66. // @Success 200 {object} store.MemoRelation "Memo relation information"
  67. // @Failure 400 {object} nil "ID is not a number: %s | Malformatted post memo relation request"
  68. // @Failure 500 {object} nil "Failed to upsert memo relation"
  69. // @Router /api/v1/memo/{memoId}/relation [POST]
  70. //
  71. // NOTES:
  72. // - Currently not secured
  73. // - It's possible to create relations to memos that doesn't exist, which will trigger 404 errors when the frontend tries to load them.
  74. // - It's possible to create multiple relations, though the interface only shows first.
  75. func (s *APIV1Service) CreateMemoRelation(c echo.Context) error {
  76. ctx := c.Request().Context()
  77. memoID, err := util.ConvertStringToInt32(c.Param("memoId"))
  78. if err != nil {
  79. return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("ID is not a number: %s", c.Param("memoId"))).SetInternal(err)
  80. }
  81. request := &UpsertMemoRelationRequest{}
  82. if err := json.NewDecoder(c.Request().Body).Decode(request); err != nil {
  83. return echo.NewHTTPError(http.StatusBadRequest, "Malformatted post memo relation request").SetInternal(err)
  84. }
  85. memoRelation, err := s.Store.UpsertMemoRelation(ctx, &store.MemoRelation{
  86. MemoID: memoID,
  87. RelatedMemoID: request.RelatedMemoID,
  88. Type: store.MemoRelationType(request.Type),
  89. })
  90. if err != nil {
  91. return echo.NewHTTPError(http.StatusInternalServerError, "Failed to upsert memo relation").SetInternal(err)
  92. }
  93. return c.JSON(http.StatusOK, memoRelation)
  94. }
  95. // DeleteMemoRelation godoc
  96. //
  97. // @Summary Delete a Memo Relation
  98. // @Description Removes a relation between two memos
  99. // @Tags memo-relation
  100. // @Accept json
  101. // @Produce json
  102. // @Param memoId path int true "ID of memo to find relations"
  103. // @Param relatedMemoId path int true "ID of memo to remove relation to"
  104. // @Param relationType path MemoRelationType true "Type of relation to remove"
  105. // @Success 200 {boolean} true "Memo relation deleted"
  106. // @Failure 400 {object} nil "Memo ID is not a number: %s | Related memo ID is not a number: %s"
  107. // @Failure 500 {object} nil "Failed to delete memo relation"
  108. // @Router /api/v1/memo/{memoId}/relation/{relatedMemoId}/type/{relationType} [DELETE]
  109. //
  110. // NOTES:
  111. // - Currently not secured.
  112. // - Will always return true, even if the relation doesn't exist.
  113. func (s *APIV1Service) DeleteMemoRelation(c echo.Context) error {
  114. ctx := c.Request().Context()
  115. memoID, err := util.ConvertStringToInt32(c.Param("memoId"))
  116. if err != nil {
  117. return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Memo ID is not a number: %s", c.Param("memoId"))).SetInternal(err)
  118. }
  119. relatedMemoID, err := util.ConvertStringToInt32(c.Param("relatedMemoId"))
  120. if err != nil {
  121. return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Related memo ID is not a number: %s", c.Param("relatedMemoId"))).SetInternal(err)
  122. }
  123. relationType := store.MemoRelationType(c.Param("relationType"))
  124. if err := s.Store.DeleteMemoRelation(ctx, &store.DeleteMemoRelation{
  125. MemoID: &memoID,
  126. RelatedMemoID: &relatedMemoID,
  127. Type: &relationType,
  128. }); err != nil {
  129. return echo.NewHTTPError(http.StatusInternalServerError, "Failed to delete memo relation").SetInternal(err)
  130. }
  131. return c.JSON(http.StatusOK, true)
  132. }
  133. func convertMemoRelationFromStore(memoRelation *store.MemoRelation) *MemoRelation {
  134. return &MemoRelation{
  135. MemoID: memoRelation.MemoID,
  136. RelatedMemoID: memoRelation.RelatedMemoID,
  137. Type: MemoRelationType(memoRelation.Type),
  138. }
  139. }