mirror of
				https://github.com/usememos/memos.git
				synced 2025-10-31 16:59:30 +08:00 
			
		
		
		
	
		
			
				
	
	
		
			152 lines
		
	
	
	
		
			5.7 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			152 lines
		
	
	
	
		
			5.7 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| package v1
 | |
| 
 | |
| import (
 | |
| 	"encoding/json"
 | |
| 	"fmt"
 | |
| 	"net/http"
 | |
| 
 | |
| 	"github.com/labstack/echo/v4"
 | |
| 
 | |
| 	"github.com/usememos/memos/internal/util"
 | |
| 	"github.com/usememos/memos/store"
 | |
| )
 | |
| 
 | |
| type MemoRelationType string
 | |
| 
 | |
| const (
 | |
| 	MemoRelationReference MemoRelationType = "REFERENCE"
 | |
| 	MemoRelationComment   MemoRelationType = "COMMENT"
 | |
| )
 | |
| 
 | |
| type MemoRelation struct {
 | |
| 	MemoID        int32            `json:"memoId"`
 | |
| 	RelatedMemoID int32            `json:"relatedMemoId"`
 | |
| 	Type          MemoRelationType `json:"type"`
 | |
| }
 | |
| 
 | |
| type UpsertMemoRelationRequest struct {
 | |
| 	RelatedMemoID int32            `json:"relatedMemoId"`
 | |
| 	Type          MemoRelationType `json:"type"`
 | |
| }
 | |
| 
 | |
| func (s *APIV1Service) registerMemoRelationRoutes(g *echo.Group) {
 | |
| 	g.GET("/memo/:memoId/relation", s.GetMemoRelationList)
 | |
| 	g.POST("/memo/:memoId/relation", s.CreateMemoRelation)
 | |
| 	g.DELETE("/memo/:memoId/relation/:relatedMemoId/type/:relationType", s.DeleteMemoRelation)
 | |
| }
 | |
| 
 | |
| // GetMemoRelationList godoc
 | |
| //
 | |
| //	@Summary	Get a list of Memo Relations
 | |
| //	@Tags		memo-relation
 | |
| //	@Accept		json
 | |
| //	@Produce	json
 | |
| //	@Param		memoId	path		int						true	"ID of memo to find relations"
 | |
| //	@Success	200		{object}	[]store.MemoRelation	"Memo relation information list"
 | |
| //	@Failure	400		{object}	nil						"ID is not a number: %s"
 | |
| //	@Failure	500		{object}	nil						"Failed to list memo relations"
 | |
| //	@Router		/api/v1/memo/{memoId}/relation [GET]
 | |
| func (s *APIV1Service) GetMemoRelationList(c echo.Context) error {
 | |
| 	ctx := c.Request().Context()
 | |
| 	memoID, err := util.ConvertStringToInt32(c.Param("memoId"))
 | |
| 	if err != nil {
 | |
| 		return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("ID is not a number: %s", c.Param("memoId"))).SetInternal(err)
 | |
| 	}
 | |
| 
 | |
| 	memoRelationList, err := s.Store.ListMemoRelations(ctx, &store.FindMemoRelation{
 | |
| 		MemoID: &memoID,
 | |
| 	})
 | |
| 	if err != nil {
 | |
| 		return echo.NewHTTPError(http.StatusInternalServerError, "Failed to list memo relations").SetInternal(err)
 | |
| 	}
 | |
| 	return c.JSON(http.StatusOK, memoRelationList)
 | |
| }
 | |
| 
 | |
| // CreateMemoRelation godoc
 | |
| //
 | |
| //	@Summary		Create Memo Relation
 | |
| //	@Description	Create a relation between two memos
 | |
| //	@Tags			memo-relation
 | |
| //	@Accept			json
 | |
| //	@Produce		json
 | |
| //	@Param			memoId	path		int							true	"ID of memo to relate"
 | |
| //	@Param			body	body		UpsertMemoRelationRequest	true	"Memo relation object"
 | |
| //	@Success		200		{object}	store.MemoRelation			"Memo relation information"
 | |
| //	@Failure		400		{object}	nil							"ID is not a number: %s | Malformatted post memo relation request"
 | |
| //	@Failure		500		{object}	nil							"Failed to upsert memo relation"
 | |
| //	@Router			/api/v1/memo/{memoId}/relation [POST]
 | |
| //
 | |
| // NOTES:
 | |
| // - Currently not secured
 | |
| // - It's possible to create relations to memos that doesn't exist, which will trigger 404 errors when the frontend tries to load them.
 | |
| // - It's possible to create multiple relations, though the interface only shows first.
 | |
| func (s *APIV1Service) CreateMemoRelation(c echo.Context) error {
 | |
| 	ctx := c.Request().Context()
 | |
| 	memoID, err := util.ConvertStringToInt32(c.Param("memoId"))
 | |
| 	if err != nil {
 | |
| 		return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("ID is not a number: %s", c.Param("memoId"))).SetInternal(err)
 | |
| 	}
 | |
| 
 | |
| 	request := &UpsertMemoRelationRequest{}
 | |
| 	if err := json.NewDecoder(c.Request().Body).Decode(request); err != nil {
 | |
| 		return echo.NewHTTPError(http.StatusBadRequest, "Malformatted post memo relation request").SetInternal(err)
 | |
| 	}
 | |
| 
 | |
| 	memoRelation, err := s.Store.UpsertMemoRelation(ctx, &store.MemoRelation{
 | |
| 		MemoID:        memoID,
 | |
| 		RelatedMemoID: request.RelatedMemoID,
 | |
| 		Type:          store.MemoRelationType(request.Type),
 | |
| 	})
 | |
| 	if err != nil {
 | |
| 		return echo.NewHTTPError(http.StatusInternalServerError, "Failed to upsert memo relation").SetInternal(err)
 | |
| 	}
 | |
| 	return c.JSON(http.StatusOK, memoRelation)
 | |
| }
 | |
| 
 | |
| // DeleteMemoRelation godoc
 | |
| //
 | |
| //	@Summary		Delete a Memo Relation
 | |
| //	@Description	Removes a relation between two memos
 | |
| //	@Tags			memo-relation
 | |
| //	@Accept			json
 | |
| //	@Produce		json
 | |
| //	@Param			memoId			path		int					true	"ID of memo to find relations"
 | |
| //	@Param			relatedMemoId	path		int					true	"ID of memo to remove relation to"
 | |
| //	@Param			relationType	path		MemoRelationType	true	"Type of relation to remove"
 | |
| //	@Success		200				{boolean}	true				"Memo relation deleted"
 | |
| //	@Failure		400				{object}	nil					"Memo ID is not a number: %s | Related memo ID is not a number: %s"
 | |
| //	@Failure		500				{object}	nil					"Failed to delete memo relation"
 | |
| //	@Router			/api/v1/memo/{memoId}/relation/{relatedMemoId}/type/{relationType} [DELETE]
 | |
| //
 | |
| // NOTES:
 | |
| // - Currently not secured.
 | |
| // - Will always return true, even if the relation doesn't exist.
 | |
| func (s *APIV1Service) DeleteMemoRelation(c echo.Context) error {
 | |
| 	ctx := c.Request().Context()
 | |
| 	memoID, err := util.ConvertStringToInt32(c.Param("memoId"))
 | |
| 	if err != nil {
 | |
| 		return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Memo ID is not a number: %s", c.Param("memoId"))).SetInternal(err)
 | |
| 	}
 | |
| 	relatedMemoID, err := util.ConvertStringToInt32(c.Param("relatedMemoId"))
 | |
| 	if err != nil {
 | |
| 		return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Related memo ID is not a number: %s", c.Param("relatedMemoId"))).SetInternal(err)
 | |
| 	}
 | |
| 	relationType := store.MemoRelationType(c.Param("relationType"))
 | |
| 
 | |
| 	if err := s.Store.DeleteMemoRelation(ctx, &store.DeleteMemoRelation{
 | |
| 		MemoID:        &memoID,
 | |
| 		RelatedMemoID: &relatedMemoID,
 | |
| 		Type:          &relationType,
 | |
| 	}); err != nil {
 | |
| 		return echo.NewHTTPError(http.StatusInternalServerError, "Failed to delete memo relation").SetInternal(err)
 | |
| 	}
 | |
| 	return c.JSON(http.StatusOK, true)
 | |
| }
 | |
| 
 | |
| func convertMemoRelationFromStore(memoRelation *store.MemoRelation) *MemoRelation {
 | |
| 	return &MemoRelation{
 | |
| 		MemoID:        memoRelation.MemoID,
 | |
| 		RelatedMemoID: memoRelation.RelatedMemoID,
 | |
| 		Type:          MemoRelationType(memoRelation.Type),
 | |
| 	}
 | |
| }
 |