2023-07-02 13:56:25 +03:00
|
|
|
package v1
|
2022-02-03 10:32:03 +03:00
|
|
|
|
|
|
|
import (
|
2022-02-04 11:51:48 +03:00
|
|
|
"encoding/json"
|
2022-02-03 10:32:03 +03:00
|
|
|
"fmt"
|
|
|
|
"net/http"
|
|
|
|
"strconv"
|
2022-10-26 18:00:09 +03:00
|
|
|
"time"
|
2022-02-03 10:32:03 +03:00
|
|
|
|
|
|
|
"github.com/labstack/echo/v4"
|
2023-07-02 13:56:25 +03:00
|
|
|
"github.com/pkg/errors"
|
|
|
|
"github.com/usememos/memos/store"
|
2022-02-03 10:32:03 +03:00
|
|
|
)
|
|
|
|
|
2023-07-02 13:56:25 +03:00
|
|
|
type Shortcut struct {
|
|
|
|
ID int `json:"id"`
|
|
|
|
|
|
|
|
// Standard fields
|
|
|
|
RowStatus RowStatus `json:"rowStatus"`
|
|
|
|
CreatorID int `json:"creatorId"`
|
|
|
|
CreatedTs int64 `json:"createdTs"`
|
|
|
|
UpdatedTs int64 `json:"updatedTs"`
|
|
|
|
|
|
|
|
// Domain specific fields
|
|
|
|
Title string `json:"title"`
|
|
|
|
Payload string `json:"payload"`
|
|
|
|
}
|
|
|
|
|
|
|
|
type CreateShortcutRequest struct {
|
|
|
|
Title string `json:"title"`
|
|
|
|
Payload string `json:"payload"`
|
|
|
|
}
|
|
|
|
|
|
|
|
type UpdateShortcutRequest struct {
|
|
|
|
RowStatus *RowStatus `json:"rowStatus"`
|
|
|
|
Title *string `json:"title"`
|
|
|
|
Payload *string `json:"payload"`
|
|
|
|
}
|
|
|
|
|
|
|
|
type ShortcutFind struct {
|
|
|
|
ID *int
|
|
|
|
|
|
|
|
// Standard fields
|
|
|
|
CreatorID *int
|
|
|
|
|
|
|
|
// Domain specific fields
|
|
|
|
Title *string `json:"title"`
|
|
|
|
}
|
|
|
|
|
|
|
|
type ShortcutDelete struct {
|
|
|
|
ID *int
|
|
|
|
|
|
|
|
// Standard fields
|
|
|
|
CreatorID *int
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *APIV1Service) registerShortcutRoutes(g *echo.Group) {
|
2022-02-03 10:32:03 +03:00
|
|
|
g.POST("/shortcut", func(c echo.Context) error {
|
2022-08-07 05:17:12 +03:00
|
|
|
ctx := c.Request().Context()
|
2022-07-28 15:09:25 +03:00
|
|
|
userID, ok := c.Get(getUserIDContextKey()).(int)
|
|
|
|
if !ok {
|
|
|
|
return echo.NewHTTPError(http.StatusUnauthorized, "Missing user in session")
|
|
|
|
}
|
2023-07-02 13:56:25 +03:00
|
|
|
shortcutCreate := &CreateShortcutRequest{}
|
2022-02-04 11:51:48 +03:00
|
|
|
if err := json.NewDecoder(c.Request().Body).Decode(shortcutCreate); err != nil {
|
2022-02-03 10:32:03 +03:00
|
|
|
return echo.NewHTTPError(http.StatusBadRequest, "Malformatted post shortcut request").SetInternal(err)
|
|
|
|
}
|
|
|
|
|
2023-07-02 13:56:25 +03:00
|
|
|
shortcut, err := s.Store.CreateShortcut(ctx, &store.Shortcut{
|
|
|
|
CreatorID: userID,
|
|
|
|
Title: shortcutCreate.Title,
|
|
|
|
Payload: shortcutCreate.Payload,
|
|
|
|
})
|
2022-02-03 10:32:03 +03:00
|
|
|
if err != nil {
|
|
|
|
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to create shortcut").SetInternal(err)
|
|
|
|
}
|
2023-07-02 13:56:25 +03:00
|
|
|
|
|
|
|
shortcutMessage := convertShortcutFromStore(shortcut)
|
|
|
|
if err := s.createShortcutCreateActivity(c, shortcutMessage); err != nil {
|
2023-01-02 18:18:12 +03:00
|
|
|
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to create activity").SetInternal(err)
|
|
|
|
}
|
2023-07-02 13:56:25 +03:00
|
|
|
return c.JSON(http.StatusOK, shortcutMessage)
|
2022-02-03 10:32:03 +03:00
|
|
|
})
|
2022-02-18 17:21:10 +03:00
|
|
|
|
2022-02-03 10:32:03 +03:00
|
|
|
g.PATCH("/shortcut/:shortcutId", func(c echo.Context) error {
|
2022-08-07 05:17:12 +03:00
|
|
|
ctx := c.Request().Context()
|
2022-12-28 15:22:52 +03:00
|
|
|
userID, ok := c.Get(getUserIDContextKey()).(int)
|
|
|
|
if !ok {
|
|
|
|
return echo.NewHTTPError(http.StatusUnauthorized, "Missing user in session")
|
|
|
|
}
|
2022-05-02 21:05:43 +03:00
|
|
|
shortcutID, err := strconv.Atoi(c.Param("shortcutId"))
|
2022-02-03 10:32:03 +03:00
|
|
|
if err != nil {
|
|
|
|
return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("ID is not a number: %s", c.Param("shortcutId"))).SetInternal(err)
|
|
|
|
}
|
|
|
|
|
2023-07-02 13:56:25 +03:00
|
|
|
shortcut, err := s.Store.GetShortcut(ctx, &store.FindShortcut{
|
2022-12-28 15:22:52 +03:00
|
|
|
ID: &shortcutID,
|
2023-07-02 13:56:25 +03:00
|
|
|
})
|
2022-12-28 15:22:52 +03:00
|
|
|
if err != nil {
|
|
|
|
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to find shortcut").SetInternal(err)
|
|
|
|
}
|
|
|
|
if shortcut.CreatorID != userID {
|
|
|
|
return echo.NewHTTPError(http.StatusUnauthorized, "Unauthorized")
|
|
|
|
}
|
|
|
|
|
2023-07-02 13:56:25 +03:00
|
|
|
request := &UpdateShortcutRequest{}
|
|
|
|
if err := json.NewDecoder(c.Request().Body).Decode(request); err != nil {
|
|
|
|
return echo.NewHTTPError(http.StatusBadRequest, "Malformatted patch shortcut request").SetInternal(err)
|
|
|
|
}
|
|
|
|
|
2022-10-26 18:00:09 +03:00
|
|
|
currentTs := time.Now().Unix()
|
2023-07-02 13:56:25 +03:00
|
|
|
shortcutUpdate := &store.UpdateShortcut{
|
|
|
|
ID: shortcutID,
|
2022-10-26 18:00:09 +03:00
|
|
|
UpdatedTs: ¤tTs,
|
2022-02-03 10:32:03 +03:00
|
|
|
}
|
2023-07-02 13:56:25 +03:00
|
|
|
if request.RowStatus != nil {
|
|
|
|
rowStatus := store.RowStatus(*request.RowStatus)
|
|
|
|
shortcutUpdate.RowStatus = &rowStatus
|
|
|
|
}
|
|
|
|
if request.Title != nil {
|
|
|
|
shortcutUpdate.Title = request.Title
|
|
|
|
}
|
|
|
|
if request.Payload != nil {
|
|
|
|
shortcutUpdate.Payload = request.Payload
|
2022-02-03 10:32:03 +03:00
|
|
|
}
|
|
|
|
|
2023-07-02 13:56:25 +03:00
|
|
|
shortcut, err = s.Store.UpdateShortcut(ctx, shortcutUpdate)
|
2022-02-03 10:32:03 +03:00
|
|
|
if err != nil {
|
|
|
|
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to patch shortcut").SetInternal(err)
|
|
|
|
}
|
2023-07-02 13:56:25 +03:00
|
|
|
return c.JSON(http.StatusOK, convertShortcutFromStore(shortcut))
|
2022-02-03 10:32:03 +03:00
|
|
|
})
|
2022-02-18 17:21:10 +03:00
|
|
|
|
2022-02-03 10:32:03 +03:00
|
|
|
g.GET("/shortcut", func(c echo.Context) error {
|
2022-08-07 05:17:12 +03:00
|
|
|
ctx := c.Request().Context()
|
2022-12-28 15:22:52 +03:00
|
|
|
userID, ok := c.Get(getUserIDContextKey()).(int)
|
|
|
|
if !ok {
|
|
|
|
return echo.NewHTTPError(http.StatusBadRequest, "Missing user id to find shortcut")
|
|
|
|
}
|
2023-01-01 18:26:21 +03:00
|
|
|
|
2023-07-02 13:56:25 +03:00
|
|
|
list, err := s.Store.ListShortcuts(ctx, &store.FindShortcut{
|
2022-12-28 15:22:52 +03:00
|
|
|
CreatorID: &userID,
|
2023-07-02 13:56:25 +03:00
|
|
|
})
|
2022-02-03 10:32:03 +03:00
|
|
|
if err != nil {
|
2023-07-02 13:56:25 +03:00
|
|
|
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to get shortcut list").SetInternal(err)
|
2022-02-03 10:32:03 +03:00
|
|
|
}
|
2023-07-02 13:56:25 +03:00
|
|
|
shortcutMessageList := make([]*Shortcut, 0, len(list))
|
|
|
|
for _, shortcut := range list {
|
|
|
|
shortcutMessageList = append(shortcutMessageList, convertShortcutFromStore(shortcut))
|
|
|
|
}
|
|
|
|
return c.JSON(http.StatusOK, shortcutMessageList)
|
2022-02-03 10:32:03 +03:00
|
|
|
})
|
2022-02-18 17:21:10 +03:00
|
|
|
|
2022-02-03 10:32:03 +03:00
|
|
|
g.GET("/shortcut/:shortcutId", func(c echo.Context) error {
|
2022-08-07 05:17:12 +03:00
|
|
|
ctx := c.Request().Context()
|
2022-05-02 21:05:43 +03:00
|
|
|
shortcutID, err := strconv.Atoi(c.Param("shortcutId"))
|
2022-02-03 10:32:03 +03:00
|
|
|
if err != nil {
|
|
|
|
return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("ID is not a number: %s", c.Param("shortcutId"))).SetInternal(err)
|
|
|
|
}
|
|
|
|
|
2023-07-02 13:56:25 +03:00
|
|
|
shortcut, err := s.Store.GetShortcut(ctx, &store.FindShortcut{
|
2022-05-02 21:05:43 +03:00
|
|
|
ID: &shortcutID,
|
2023-07-02 13:56:25 +03:00
|
|
|
})
|
2022-02-03 10:32:03 +03:00
|
|
|
if err != nil {
|
2023-07-02 13:56:25 +03:00
|
|
|
return echo.NewHTTPError(http.StatusInternalServerError, fmt.Sprintf("Failed to fetch shortcut by ID %d", shortcutID)).SetInternal(err)
|
|
|
|
}
|
|
|
|
if shortcut == nil {
|
|
|
|
return echo.NewHTTPError(http.StatusNotFound, fmt.Sprintf("Shortcut by ID %d not found", shortcutID))
|
2022-02-03 10:32:03 +03:00
|
|
|
}
|
2023-07-02 13:56:25 +03:00
|
|
|
return c.JSON(http.StatusOK, convertShortcutFromStore(shortcut))
|
2022-02-03 10:32:03 +03:00
|
|
|
})
|
2022-02-18 17:21:10 +03:00
|
|
|
|
2022-02-03 10:32:03 +03:00
|
|
|
g.DELETE("/shortcut/:shortcutId", func(c echo.Context) error {
|
2022-08-07 05:17:12 +03:00
|
|
|
ctx := c.Request().Context()
|
2022-12-28 15:22:52 +03:00
|
|
|
userID, ok := c.Get(getUserIDContextKey()).(int)
|
|
|
|
if !ok {
|
|
|
|
return echo.NewHTTPError(http.StatusUnauthorized, "Missing user in session")
|
|
|
|
}
|
2022-05-02 21:05:43 +03:00
|
|
|
shortcutID, err := strconv.Atoi(c.Param("shortcutId"))
|
2022-02-03 10:32:03 +03:00
|
|
|
if err != nil {
|
|
|
|
return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("ID is not a number: %s", c.Param("shortcutId"))).SetInternal(err)
|
|
|
|
}
|
|
|
|
|
2023-07-02 13:56:25 +03:00
|
|
|
shortcut, err := s.Store.GetShortcut(ctx, &store.FindShortcut{
|
2022-12-28 15:22:52 +03:00
|
|
|
ID: &shortcutID,
|
2023-07-02 13:56:25 +03:00
|
|
|
})
|
2022-12-28 15:22:52 +03:00
|
|
|
if err != nil {
|
|
|
|
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to find shortcut").SetInternal(err)
|
|
|
|
}
|
|
|
|
if shortcut.CreatorID != userID {
|
|
|
|
return echo.NewHTTPError(http.StatusUnauthorized, "Unauthorized")
|
|
|
|
}
|
|
|
|
|
2023-07-02 13:56:25 +03:00
|
|
|
if err := s.Store.DeleteShortcut(ctx, &store.DeleteShortcut{
|
2022-11-06 07:21:58 +03:00
|
|
|
ID: &shortcutID,
|
2023-07-02 13:56:25 +03:00
|
|
|
}); err != nil {
|
2022-02-03 10:32:03 +03:00
|
|
|
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to delete shortcut").SetInternal(err)
|
|
|
|
}
|
2022-07-02 05:47:16 +03:00
|
|
|
return c.JSON(http.StatusOK, true)
|
2022-02-03 10:32:03 +03:00
|
|
|
})
|
|
|
|
}
|
2023-01-02 18:18:12 +03:00
|
|
|
|
2023-07-02 13:56:25 +03:00
|
|
|
func (s *APIV1Service) createShortcutCreateActivity(c echo.Context, shortcut *Shortcut) error {
|
2023-01-02 18:18:12 +03:00
|
|
|
ctx := c.Request().Context()
|
2023-07-02 13:56:25 +03:00
|
|
|
payload := ActivityShortcutCreatePayload{
|
2023-01-02 18:18:12 +03:00
|
|
|
Title: shortcut.Title,
|
|
|
|
Payload: shortcut.Payload,
|
|
|
|
}
|
2023-02-17 18:55:56 +03:00
|
|
|
payloadBytes, err := json.Marshal(payload)
|
2023-01-02 18:18:12 +03:00
|
|
|
if err != nil {
|
|
|
|
return errors.Wrap(err, "failed to marshal activity payload")
|
|
|
|
}
|
2023-07-06 16:56:42 +03:00
|
|
|
activity, err := s.Store.CreateActivity(ctx, &store.Activity{
|
2023-01-02 18:18:12 +03:00
|
|
|
CreatorID: shortcut.CreatorID,
|
2023-07-02 13:56:25 +03:00
|
|
|
Type: ActivityShortcutCreate.String(),
|
|
|
|
Level: ActivityInfo.String(),
|
2023-02-17 18:55:56 +03:00
|
|
|
Payload: string(payloadBytes),
|
2023-01-02 18:18:12 +03:00
|
|
|
})
|
2023-01-07 06:49:58 +03:00
|
|
|
if err != nil || activity == nil {
|
|
|
|
return errors.Wrap(err, "failed to create activity")
|
|
|
|
}
|
2023-01-02 18:18:12 +03:00
|
|
|
return err
|
|
|
|
}
|
2023-07-02 13:56:25 +03:00
|
|
|
|
|
|
|
func convertShortcutFromStore(shortcut *store.Shortcut) *Shortcut {
|
|
|
|
return &Shortcut{
|
|
|
|
ID: shortcut.ID,
|
|
|
|
RowStatus: RowStatus(shortcut.RowStatus),
|
|
|
|
CreatorID: shortcut.CreatorID,
|
|
|
|
Title: shortcut.Title,
|
|
|
|
Payload: shortcut.Payload,
|
|
|
|
CreatedTs: shortcut.CreatedTs,
|
|
|
|
UpdatedTs: shortcut.UpdatedTs,
|
|
|
|
}
|
|
|
|
}
|