mirror of https://github.com/usememos/memos
chore: remove shortcut related api (#2072)
parent
d1b0b0da10
commit
1ce82ba0d6
@ -1,242 +0,0 @@
|
|||||||
package v1
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"net/http"
|
|
||||||
"strconv"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/labstack/echo/v4"
|
|
||||||
"github.com/pkg/errors"
|
|
||||||
"github.com/usememos/memos/api/auth"
|
|
||||||
"github.com/usememos/memos/store"
|
|
||||||
)
|
|
||||||
|
|
||||||
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) {
|
|
||||||
g.POST("/shortcut", func(c echo.Context) error {
|
|
||||||
ctx := c.Request().Context()
|
|
||||||
userID, ok := c.Get(auth.UserIDContextKey).(int)
|
|
||||||
if !ok {
|
|
||||||
return echo.NewHTTPError(http.StatusUnauthorized, "Missing user in session")
|
|
||||||
}
|
|
||||||
shortcutCreate := &CreateShortcutRequest{}
|
|
||||||
if err := json.NewDecoder(c.Request().Body).Decode(shortcutCreate); err != nil {
|
|
||||||
return echo.NewHTTPError(http.StatusBadRequest, "Malformatted post shortcut request").SetInternal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
shortcut, err := s.Store.CreateShortcut(ctx, &store.Shortcut{
|
|
||||||
CreatorID: userID,
|
|
||||||
Title: shortcutCreate.Title,
|
|
||||||
Payload: shortcutCreate.Payload,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to create shortcut").SetInternal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
shortcutMessage := convertShortcutFromStore(shortcut)
|
|
||||||
if err := s.createShortcutCreateActivity(c, shortcutMessage); err != nil {
|
|
||||||
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to create activity").SetInternal(err)
|
|
||||||
}
|
|
||||||
return c.JSON(http.StatusOK, shortcutMessage)
|
|
||||||
})
|
|
||||||
|
|
||||||
g.PATCH("/shortcut/:shortcutId", func(c echo.Context) error {
|
|
||||||
ctx := c.Request().Context()
|
|
||||||
userID, ok := c.Get(auth.UserIDContextKey).(int)
|
|
||||||
if !ok {
|
|
||||||
return echo.NewHTTPError(http.StatusUnauthorized, "Missing user in session")
|
|
||||||
}
|
|
||||||
shortcutID, err := strconv.Atoi(c.Param("shortcutId"))
|
|
||||||
if err != nil {
|
|
||||||
return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("ID is not a number: %s", c.Param("shortcutId"))).SetInternal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
shortcut, err := s.Store.GetShortcut(ctx, &store.FindShortcut{
|
|
||||||
ID: &shortcutID,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to find shortcut").SetInternal(err)
|
|
||||||
}
|
|
||||||
if shortcut == nil {
|
|
||||||
return echo.NewHTTPError(http.StatusNotFound, fmt.Sprintf("Shortcut not found: %d", shortcutID))
|
|
||||||
}
|
|
||||||
if shortcut.CreatorID != userID {
|
|
||||||
return echo.NewHTTPError(http.StatusUnauthorized, "Unauthorized")
|
|
||||||
}
|
|
||||||
|
|
||||||
request := &UpdateShortcutRequest{}
|
|
||||||
if err := json.NewDecoder(c.Request().Body).Decode(request); err != nil {
|
|
||||||
return echo.NewHTTPError(http.StatusBadRequest, "Malformatted patch shortcut request").SetInternal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
currentTs := time.Now().Unix()
|
|
||||||
shortcutUpdate := &store.UpdateShortcut{
|
|
||||||
ID: shortcutID,
|
|
||||||
UpdatedTs: ¤tTs,
|
|
||||||
}
|
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
shortcut, err = s.Store.UpdateShortcut(ctx, shortcutUpdate)
|
|
||||||
if err != nil {
|
|
||||||
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to patch shortcut").SetInternal(err)
|
|
||||||
}
|
|
||||||
return c.JSON(http.StatusOK, convertShortcutFromStore(shortcut))
|
|
||||||
})
|
|
||||||
|
|
||||||
g.GET("/shortcut", func(c echo.Context) error {
|
|
||||||
ctx := c.Request().Context()
|
|
||||||
userID, ok := c.Get(auth.UserIDContextKey).(int)
|
|
||||||
if !ok {
|
|
||||||
return echo.NewHTTPError(http.StatusBadRequest, "Missing user id to find shortcut")
|
|
||||||
}
|
|
||||||
|
|
||||||
list, err := s.Store.ListShortcuts(ctx, &store.FindShortcut{
|
|
||||||
CreatorID: &userID,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to get shortcut list").SetInternal(err)
|
|
||||||
}
|
|
||||||
shortcutMessageList := make([]*Shortcut, 0, len(list))
|
|
||||||
for _, shortcut := range list {
|
|
||||||
shortcutMessageList = append(shortcutMessageList, convertShortcutFromStore(shortcut))
|
|
||||||
}
|
|
||||||
return c.JSON(http.StatusOK, shortcutMessageList)
|
|
||||||
})
|
|
||||||
|
|
||||||
g.GET("/shortcut/:shortcutId", func(c echo.Context) error {
|
|
||||||
ctx := c.Request().Context()
|
|
||||||
shortcutID, err := strconv.Atoi(c.Param("shortcutId"))
|
|
||||||
if err != nil {
|
|
||||||
return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("ID is not a number: %s", c.Param("shortcutId"))).SetInternal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
shortcut, err := s.Store.GetShortcut(ctx, &store.FindShortcut{
|
|
||||||
ID: &shortcutID,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
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 not found: %d", shortcutID))
|
|
||||||
}
|
|
||||||
return c.JSON(http.StatusOK, convertShortcutFromStore(shortcut))
|
|
||||||
})
|
|
||||||
|
|
||||||
g.DELETE("/shortcut/:shortcutId", func(c echo.Context) error {
|
|
||||||
ctx := c.Request().Context()
|
|
||||||
userID, ok := c.Get(auth.UserIDContextKey).(int)
|
|
||||||
if !ok {
|
|
||||||
return echo.NewHTTPError(http.StatusUnauthorized, "Missing user in session")
|
|
||||||
}
|
|
||||||
shortcutID, err := strconv.Atoi(c.Param("shortcutId"))
|
|
||||||
if err != nil {
|
|
||||||
return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("ID is not a number: %s", c.Param("shortcutId"))).SetInternal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
shortcut, err := s.Store.GetShortcut(ctx, &store.FindShortcut{
|
|
||||||
ID: &shortcutID,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to find shortcut").SetInternal(err)
|
|
||||||
}
|
|
||||||
if shortcut == nil {
|
|
||||||
return echo.NewHTTPError(http.StatusNotFound, fmt.Sprintf("Shortcut not found: %d", shortcutID))
|
|
||||||
}
|
|
||||||
if shortcut.CreatorID != userID {
|
|
||||||
return echo.NewHTTPError(http.StatusUnauthorized, "Unauthorized")
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := s.Store.DeleteShortcut(ctx, &store.DeleteShortcut{
|
|
||||||
ID: &shortcutID,
|
|
||||||
}); err != nil {
|
|
||||||
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to delete shortcut").SetInternal(err)
|
|
||||||
}
|
|
||||||
return c.JSON(http.StatusOK, true)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *APIV1Service) createShortcutCreateActivity(c echo.Context, shortcut *Shortcut) error {
|
|
||||||
ctx := c.Request().Context()
|
|
||||||
payload := ActivityShortcutCreatePayload{
|
|
||||||
Title: shortcut.Title,
|
|
||||||
Payload: shortcut.Payload,
|
|
||||||
}
|
|
||||||
payloadBytes, err := json.Marshal(payload)
|
|
||||||
if err != nil {
|
|
||||||
return errors.Wrap(err, "failed to marshal activity payload")
|
|
||||||
}
|
|
||||||
activity, err := s.Store.CreateActivity(ctx, &store.Activity{
|
|
||||||
CreatorID: shortcut.CreatorID,
|
|
||||||
Type: ActivityShortcutCreate.String(),
|
|
||||||
Level: ActivityInfo.String(),
|
|
||||||
Payload: string(payloadBytes),
|
|
||||||
})
|
|
||||||
if err != nil || activity == nil {
|
|
||||||
return errors.Wrap(err, "failed to create activity")
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
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,
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,211 +0,0 @@
|
|||||||
package store
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"database/sql"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Shortcut struct {
|
|
||||||
ID int
|
|
||||||
|
|
||||||
// Standard fields
|
|
||||||
RowStatus RowStatus
|
|
||||||
CreatorID int
|
|
||||||
CreatedTs int64
|
|
||||||
UpdatedTs int64
|
|
||||||
|
|
||||||
// Domain specific fields
|
|
||||||
Title string
|
|
||||||
Payload string
|
|
||||||
}
|
|
||||||
|
|
||||||
type UpdateShortcut struct {
|
|
||||||
ID int
|
|
||||||
|
|
||||||
UpdatedTs *int64
|
|
||||||
RowStatus *RowStatus
|
|
||||||
Title *string
|
|
||||||
Payload *string
|
|
||||||
}
|
|
||||||
|
|
||||||
type FindShortcut struct {
|
|
||||||
ID *int
|
|
||||||
CreatorID *int
|
|
||||||
Title *string
|
|
||||||
}
|
|
||||||
|
|
||||||
type DeleteShortcut struct {
|
|
||||||
ID *int
|
|
||||||
CreatorID *int
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Store) CreateShortcut(ctx context.Context, create *Shortcut) (*Shortcut, error) {
|
|
||||||
stmt := `
|
|
||||||
INSERT INTO shortcut (
|
|
||||||
title,
|
|
||||||
payload,
|
|
||||||
creator_id
|
|
||||||
)
|
|
||||||
VALUES (?, ?, ?)
|
|
||||||
RETURNING id, created_ts, updated_ts, row_status
|
|
||||||
`
|
|
||||||
if err := s.db.QueryRowContext(ctx, stmt, create.Title, create.Payload, create.CreatorID).Scan(
|
|
||||||
&create.ID,
|
|
||||||
&create.CreatedTs,
|
|
||||||
&create.UpdatedTs,
|
|
||||||
&create.RowStatus,
|
|
||||||
); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
shortcut := create
|
|
||||||
return shortcut, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Store) ListShortcuts(ctx context.Context, find *FindShortcut) ([]*Shortcut, error) {
|
|
||||||
where, args := []string{"1 = 1"}, []any{}
|
|
||||||
|
|
||||||
if v := find.ID; v != nil {
|
|
||||||
where, args = append(where, "id = ?"), append(args, *v)
|
|
||||||
}
|
|
||||||
if v := find.CreatorID; v != nil {
|
|
||||||
where, args = append(where, "creator_id = ?"), append(args, *v)
|
|
||||||
}
|
|
||||||
if v := find.Title; v != nil {
|
|
||||||
where, args = append(where, "title = ?"), append(args, *v)
|
|
||||||
}
|
|
||||||
|
|
||||||
rows, err := s.db.QueryContext(ctx, `
|
|
||||||
SELECT
|
|
||||||
id,
|
|
||||||
title,
|
|
||||||
payload,
|
|
||||||
creator_id,
|
|
||||||
created_ts,
|
|
||||||
updated_ts,
|
|
||||||
row_status
|
|
||||||
FROM shortcut
|
|
||||||
WHERE `+strings.Join(where, " AND ")+`
|
|
||||||
ORDER BY created_ts DESC`,
|
|
||||||
args...,
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
defer rows.Close()
|
|
||||||
|
|
||||||
list := make([]*Shortcut, 0)
|
|
||||||
for rows.Next() {
|
|
||||||
var shortcut Shortcut
|
|
||||||
if err := rows.Scan(
|
|
||||||
&shortcut.ID,
|
|
||||||
&shortcut.Title,
|
|
||||||
&shortcut.Payload,
|
|
||||||
&shortcut.CreatorID,
|
|
||||||
&shortcut.CreatedTs,
|
|
||||||
&shortcut.UpdatedTs,
|
|
||||||
&shortcut.RowStatus,
|
|
||||||
); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
list = append(list, &shortcut)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := rows.Err(); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return list, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Store) GetShortcut(ctx context.Context, find *FindShortcut) (*Shortcut, error) {
|
|
||||||
list, err := s.ListShortcuts(ctx, find)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(list) == 0 {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
shortcut := list[0]
|
|
||||||
return shortcut, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Store) UpdateShortcut(ctx context.Context, update *UpdateShortcut) (*Shortcut, error) {
|
|
||||||
set, args := []string{}, []any{}
|
|
||||||
if v := update.UpdatedTs; v != nil {
|
|
||||||
set, args = append(set, "updated_ts = ?"), append(args, *v)
|
|
||||||
}
|
|
||||||
if v := update.Title; v != nil {
|
|
||||||
set, args = append(set, "title = ?"), append(args, *v)
|
|
||||||
}
|
|
||||||
if v := update.Payload; v != nil {
|
|
||||||
set, args = append(set, "payload = ?"), append(args, *v)
|
|
||||||
}
|
|
||||||
if v := update.RowStatus; v != nil {
|
|
||||||
set, args = append(set, "row_status = ?"), append(args, *v)
|
|
||||||
}
|
|
||||||
args = append(args, update.ID)
|
|
||||||
|
|
||||||
stmt := `
|
|
||||||
UPDATE shortcut
|
|
||||||
SET ` + strings.Join(set, ", ") + `
|
|
||||||
WHERE id = ?
|
|
||||||
RETURNING id, title, payload, creator_id, created_ts, updated_ts, row_status
|
|
||||||
`
|
|
||||||
shortcut := &Shortcut{}
|
|
||||||
if err := s.db.QueryRowContext(ctx, stmt, args...).Scan(
|
|
||||||
&shortcut.ID,
|
|
||||||
&shortcut.Title,
|
|
||||||
&shortcut.Payload,
|
|
||||||
&shortcut.CreatorID,
|
|
||||||
&shortcut.CreatedTs,
|
|
||||||
&shortcut.UpdatedTs,
|
|
||||||
&shortcut.RowStatus,
|
|
||||||
); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return shortcut, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Store) DeleteShortcut(ctx context.Context, delete *DeleteShortcut) error {
|
|
||||||
where, args := []string{}, []any{}
|
|
||||||
if v := delete.ID; v != nil {
|
|
||||||
where, args = append(where, "id = ?"), append(args, *v)
|
|
||||||
}
|
|
||||||
if v := delete.CreatorID; v != nil {
|
|
||||||
where, args = append(where, "creator_id = ?"), append(args, *v)
|
|
||||||
}
|
|
||||||
stmt := `DELETE FROM shortcut WHERE ` + strings.Join(where, " AND ")
|
|
||||||
result, err := s.db.ExecContext(ctx, stmt, args...)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if _, err := result.RowsAffected(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
s.shortcutCache.Delete(*delete.ID)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func vacuumShortcut(ctx context.Context, tx *sql.Tx) error {
|
|
||||||
stmt := `
|
|
||||||
DELETE FROM
|
|
||||||
shortcut
|
|
||||||
WHERE
|
|
||||||
creator_id NOT IN (
|
|
||||||
SELECT
|
|
||||||
id
|
|
||||||
FROM
|
|
||||||
user
|
|
||||||
)`
|
|
||||||
_, err := tx.ExecContext(ctx, stmt)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
Loading…
Reference in New Issue