Unverified Commit a7573d57 authored by boojack's avatar boojack Committed by GitHub

refactor: migrate memo to apiv1 (#1907)

* refactor: migrate memo to apiv1

* chore: update

* chore: update

* chore: update

* chore: upate

* chore: update

* chore: update
parent 1fa9f162
package api
// UnknownID is the ID for unknowns.
const UnknownID = -1
// RowStatus is the status for a row.
type RowStatus string
const (
// Normal is the status for a normal row.
Normal RowStatus = "NORMAL"
// Archived is the status for an archived row.
Archived RowStatus = "ARCHIVED"
)
func (e RowStatus) String() string {
switch e {
case Normal:
return "NORMAL"
case Archived:
return "ARCHIVED"
}
return ""
}
package api
// Visibility is the type of a visibility.
type Visibility string
const (
// Public is the PUBLIC visibility.
Public Visibility = "PUBLIC"
// Protected is the PROTECTED visibility.
Protected Visibility = "PROTECTED"
// Private is the PRIVATE visibility.
Private Visibility = "PRIVATE"
)
func (v Visibility) String() string {
switch v {
case Public:
return "PUBLIC"
case Protected:
return "PROTECTED"
case Private:
return "PRIVATE"
}
return "PRIVATE"
}
type MemoResponse 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
DisplayTs int64 `json:"displayTs"`
Content string `json:"content"`
Visibility Visibility `json:"visibility"`
Pinned bool `json:"pinned"`
// Related fields
CreatorName string `json:"creatorName"`
ResourceList []*Resource `json:"resourceList"`
RelationList []*MemoRelation `json:"relationList"`
}
type CreateMemoRequest struct {
// Standard fields
CreatorID int `json:"-"`
CreatedTs *int64 `json:"createdTs"`
// Domain specific fields
Visibility Visibility `json:"visibility"`
Content string `json:"content"`
// Related fields
ResourceIDList []int `json:"resourceIdList"`
RelationList []*MemoRelationUpsert `json:"relationList"`
}
type PatchMemoRequest struct {
ID int `json:"-"`
// Standard fields
CreatedTs *int64 `json:"createdTs"`
UpdatedTs *int64
RowStatus *RowStatus `json:"rowStatus"`
// Domain specific fields
Content *string `json:"content"`
Visibility *Visibility `json:"visibility"`
// Related fields
ResourceIDList []int `json:"resourceIdList"`
RelationList []*MemoRelationUpsert `json:"relationList"`
}
type FindMemoRequest struct {
ID *int
// Standard fields
RowStatus *RowStatus
CreatorID *int
// Domain specific fields
Pinned *bool
ContentSearch []string
VisibilityList []Visibility
// Pagination
Limit *int
Offset *int
}
package api
type MemoOrganizer struct {
// Domain specific fields
MemoID int
UserID int
Pinned bool
}
type MemoOrganizerUpsert struct {
MemoID int `json:"-"`
UserID int `json:"-"`
Pinned bool `json:"pinned"`
}
type MemoOrganizerFind struct {
MemoID int
UserID int
}
type MemoOrganizerDelete struct {
MemoID *int
UserID *int
}
package api
type MemoRelationType string
const (
MemoRelationReference MemoRelationType = "REFERENCE"
MemoRelationAdditional MemoRelationType = "ADDITIONAL"
)
type MemoRelation struct {
MemoID int `json:"memoId"`
RelatedMemoID int `json:"relatedMemoId"`
Type MemoRelationType `json:"type"`
}
type MemoRelationUpsert struct {
RelatedMemoID int `json:"relatedMemoId"`
Type MemoRelationType `json:"type"`
}
package api
type MemoResource struct {
MemoID int
ResourceID int
CreatedTs int64
UpdatedTs int64
}
type MemoResourceUpsert struct {
MemoID int `json:"-"`
ResourceID int
UpdatedTs *int64
}
type MemoResourceFind struct {
MemoID *int
ResourceID *int
}
type MemoResourceDelete struct {
MemoID *int
ResourceID *int
}
package api
type Resource struct {
ID int `json:"id"`
// Standard fields
CreatorID int `json:"creatorId"`
CreatedTs int64 `json:"createdTs"`
UpdatedTs int64 `json:"updatedTs"`
// Domain specific fields
Filename string `json:"filename"`
Blob []byte `json:"-"`
InternalPath string `json:"-"`
ExternalLink string `json:"externalLink"`
Type string `json:"type"`
Size int64 `json:"size"`
PublicID string `json:"publicId"`
// Related fields
LinkedMemoAmount int `json:"linkedMemoAmount"`
}
...@@ -234,7 +234,7 @@ func (s *APIV1Service) createAuthSignInActivity(c echo.Context, user *store.User ...@@ -234,7 +234,7 @@ func (s *APIV1Service) createAuthSignInActivity(c echo.Context, user *store.User
if err != nil { if err != nil {
return errors.Wrap(err, "failed to marshal activity payload") return errors.Wrap(err, "failed to marshal activity payload")
} }
activity, err := s.Store.CreateActivity(ctx, &store.ActivityMessage{ activity, err := s.Store.CreateActivity(ctx, &store.Activity{
CreatorID: user.ID, CreatorID: user.ID,
Type: string(ActivityUserAuthSignIn), Type: string(ActivityUserAuthSignIn),
Level: string(ActivityInfo), Level: string(ActivityInfo),
...@@ -256,7 +256,7 @@ func (s *APIV1Service) createAuthSignUpActivity(c echo.Context, user *store.User ...@@ -256,7 +256,7 @@ func (s *APIV1Service) createAuthSignUpActivity(c echo.Context, user *store.User
if err != nil { if err != nil {
return errors.Wrap(err, "failed to marshal activity payload") return errors.Wrap(err, "failed to marshal activity payload")
} }
activity, err := s.Store.CreateActivity(ctx, &store.ActivityMessage{ activity, err := s.Store.CreateActivity(ctx, &store.Activity{
CreatorID: user.ID, CreatorID: user.ID,
Type: string(ActivityUserAuthSignUp), Type: string(ActivityUserAuthSignUp),
Level: string(ActivityInfo), Level: string(ActivityInfo),
......
package server package v1
import ( import (
"fmt" "fmt"
...@@ -9,7 +9,7 @@ import ( ...@@ -9,7 +9,7 @@ import (
getter "github.com/usememos/memos/plugin/http-getter" getter "github.com/usememos/memos/plugin/http-getter"
) )
func registerGetterPublicRoutes(g *echo.Group) { func (*APIV1Service) registerGetterPublicRoutes(g *echo.Group) {
g.GET("/get/httpmeta", func(c echo.Context) error { g.GET("/get/httpmeta", func(c echo.Context) error {
urlStr := c.QueryParam("url") urlStr := c.QueryParam("url")
if urlStr == "" { if urlStr == "" {
...@@ -23,7 +23,7 @@ func registerGetterPublicRoutes(g *echo.Group) { ...@@ -23,7 +23,7 @@ func registerGetterPublicRoutes(g *echo.Group) {
if err != nil { if err != nil {
return echo.NewHTTPError(http.StatusNotAcceptable, fmt.Sprintf("Failed to get website meta with url: %s", urlStr)).SetInternal(err) return echo.NewHTTPError(http.StatusNotAcceptable, fmt.Sprintf("Failed to get website meta with url: %s", urlStr)).SetInternal(err)
} }
return c.JSON(http.StatusOK, composeResponse(htmlMeta)) return c.JSON(http.StatusOK, htmlMeta)
}) })
g.GET("/get/image", func(c echo.Context) error { g.GET("/get/image", func(c echo.Context) error {
......
...@@ -82,7 +82,7 @@ func JWTMiddleware(server *APIV1Service, next echo.HandlerFunc, secret string) e ...@@ -82,7 +82,7 @@ func JWTMiddleware(server *APIV1Service, next echo.HandlerFunc, secret string) e
} }
// Skip validation for server status endpoints. // Skip validation for server status endpoints.
if common.HasPrefixes(path, "/api/v1/ping", "/api/v1/idp", "/api/v1/status", "/api/user/:id") && method == http.MethodGet { if common.HasPrefixes(path, "/api/v1/ping", "/api/v1/idp", "/api/v1/status", "/api/v1/user/:id") && method == http.MethodGet {
return next(c) return next(c)
} }
...@@ -93,7 +93,7 @@ func JWTMiddleware(server *APIV1Service, next echo.HandlerFunc, secret string) e ...@@ -93,7 +93,7 @@ func JWTMiddleware(server *APIV1Service, next echo.HandlerFunc, secret string) e
return next(c) return next(c)
} }
// When the request is not authenticated, we allow the user to access the memo endpoints for those public memos. // When the request is not authenticated, we allow the user to access the memo endpoints for those public memos.
if common.HasPrefixes(path, "/api/memo") && method == http.MethodGet { if common.HasPrefixes(path, "/api/v1/memo") && method == http.MethodGet {
return next(c) return next(c)
} }
return echo.NewHTTPError(http.StatusUnauthorized, "Missing access token") return echo.NewHTTPError(http.StatusUnauthorized, "Missing access token")
......
This diff is collapsed.
package v1
import (
"encoding/json"
"fmt"
"net/http"
"strconv"
"github.com/labstack/echo/v4"
"github.com/usememos/memos/store"
)
type MemoOrganizer struct {
MemoID int `json:"memoId"`
UserID int `json:"userId"`
Pinned bool `json:"pinned"`
}
type UpsertMemoOrganizerRequest struct {
Pinned bool `json:"pinned"`
}
func (s *APIV1Service) registerMemoOrganizerRoutes(g *echo.Group) {
g.POST("/memo/:memoId/organizer", func(c echo.Context) error {
ctx := c.Request().Context()
memoID, err := strconv.Atoi(c.Param("memoId"))
if err != nil {
return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("ID is not a number: %s", c.Param("memoId"))).SetInternal(err)
}
userID, ok := c.Get(getUserIDContextKey()).(int)
if !ok {
return echo.NewHTTPError(http.StatusUnauthorized, "Missing user in session")
}
memo, err := s.Store.GetMemo(ctx, &store.FindMemo{
ID: &memoID,
})
if err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to find memo").SetInternal(err)
}
if memo.CreatorID != userID {
return echo.NewHTTPError(http.StatusUnauthorized, "Unauthorized")
}
request := &UpsertMemoOrganizerRequest{}
if err := json.NewDecoder(c.Request().Body).Decode(request); err != nil {
return echo.NewHTTPError(http.StatusBadRequest, "Malformatted post memo organizer request").SetInternal(err)
}
upsert := &store.MemoOrganizer{
MemoID: memoID,
UserID: userID,
Pinned: request.Pinned,
}
_, err = s.Store.UpsertMemoOrganizerV1(ctx, upsert)
if err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to upsert memo organizer").SetInternal(err)
}
memo, err = s.Store.GetMemo(ctx, &store.FindMemo{
ID: &memoID,
})
if err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, fmt.Sprintf("Failed to find memo by ID: %v", memoID)).SetInternal(err)
}
memoResponse, err := s.convertMemoFromStore(ctx, memo)
if err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to compose memo response").SetInternal(err)
}
return c.JSON(http.StatusOK, memoResponse)
})
}
package server package v1
import ( import (
"encoding/json" "encoding/json"
...@@ -6,13 +6,29 @@ import ( ...@@ -6,13 +6,29 @@ import (
"net/http" "net/http"
"strconv" "strconv"
"github.com/usememos/memos/api" "github.com/labstack/echo/v4"
"github.com/usememos/memos/store" "github.com/usememos/memos/store"
)
"github.com/labstack/echo/v4" type MemoRelationType string
const (
MemoRelationReference MemoRelationType = "REFERENCE"
MemoRelationAdditional MemoRelationType = "ADDITIONAL"
) )
func (s *Server) registerMemoRelationRoutes(g *echo.Group) { type MemoRelation struct {
MemoID int `json:"memoId"`
RelatedMemoID int `json:"relatedMemoId"`
Type MemoRelationType `json:"type"`
}
type UpsertMemoRelationRequest struct {
RelatedMemoID int `json:"relatedMemoId"`
Type MemoRelationType `json:"type"`
}
func (s *APIV1Service) registerMemoRelationRoutes(g *echo.Group) {
g.POST("/memo/:memoId/relation", func(c echo.Context) error { g.POST("/memo/:memoId/relation", func(c echo.Context) error {
ctx := c.Request().Context() ctx := c.Request().Context()
memoID, err := strconv.Atoi(c.Param("memoId")) memoID, err := strconv.Atoi(c.Param("memoId"))
...@@ -20,20 +36,20 @@ func (s *Server) registerMemoRelationRoutes(g *echo.Group) { ...@@ -20,20 +36,20 @@ func (s *Server) registerMemoRelationRoutes(g *echo.Group) {
return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("ID is not a number: %s", c.Param("memoId"))).SetInternal(err) return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("ID is not a number: %s", c.Param("memoId"))).SetInternal(err)
} }
memoRelationUpsert := &api.MemoRelationUpsert{} request := &UpsertMemoRelationRequest{}
if err := json.NewDecoder(c.Request().Body).Decode(memoRelationUpsert); err != nil { if err := json.NewDecoder(c.Request().Body).Decode(request); err != nil {
return echo.NewHTTPError(http.StatusBadRequest, "Malformatted post memo relation request").SetInternal(err) return echo.NewHTTPError(http.StatusBadRequest, "Malformatted post memo relation request").SetInternal(err)
} }
memoRelation, err := s.Store.UpsertMemoRelation(ctx, &store.MemoRelationMessage{ memoRelation, err := s.Store.UpsertMemoRelation(ctx, &store.MemoRelation{
MemoID: memoID, MemoID: memoID,
RelatedMemoID: memoRelationUpsert.RelatedMemoID, RelatedMemoID: request.RelatedMemoID,
Type: store.MemoRelationType(memoRelationUpsert.Type), Type: store.MemoRelationType(request.Type),
}) })
if err != nil { if err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to upsert memo relation").SetInternal(err) return echo.NewHTTPError(http.StatusInternalServerError, "Failed to upsert memo relation").SetInternal(err)
} }
return c.JSON(http.StatusOK, composeResponse(memoRelation)) return c.JSON(http.StatusOK, memoRelation)
}) })
g.GET("/memo/:memoId/relation", func(c echo.Context) error { g.GET("/memo/:memoId/relation", func(c echo.Context) error {
...@@ -43,13 +59,13 @@ func (s *Server) registerMemoRelationRoutes(g *echo.Group) { ...@@ -43,13 +59,13 @@ func (s *Server) registerMemoRelationRoutes(g *echo.Group) {
return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("ID is not a number: %s", c.Param("memoId"))).SetInternal(err) 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.FindMemoRelationMessage{ memoRelationList, err := s.Store.ListMemoRelations(ctx, &store.FindMemoRelation{
MemoID: &memoID, MemoID: &memoID,
}) })
if err != nil { if err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to list memo relations").SetInternal(err) return echo.NewHTTPError(http.StatusInternalServerError, "Failed to list memo relations").SetInternal(err)
} }
return c.JSON(http.StatusOK, composeResponse(memoRelationList)) return c.JSON(http.StatusOK, memoRelationList)
}) })
g.DELETE("/memo/:memoId/relation/:relatedMemoId/type/:relationType", func(c echo.Context) error { g.DELETE("/memo/:memoId/relation/:relatedMemoId/type/:relationType", func(c echo.Context) error {
...@@ -64,7 +80,7 @@ func (s *Server) registerMemoRelationRoutes(g *echo.Group) { ...@@ -64,7 +80,7 @@ func (s *Server) registerMemoRelationRoutes(g *echo.Group) {
} }
relationType := store.MemoRelationType(c.Param("relationType")) relationType := store.MemoRelationType(c.Param("relationType"))
if err := s.Store.DeleteMemoRelation(ctx, &store.DeleteMemoRelationMessage{ if err := s.Store.DeleteMemoRelation(ctx, &store.DeleteMemoRelation{
MemoID: &memoID, MemoID: &memoID,
RelatedMemoID: &relatedMemoID, RelatedMemoID: &relatedMemoID,
Type: &relationType, Type: &relationType,
...@@ -75,10 +91,10 @@ func (s *Server) registerMemoRelationRoutes(g *echo.Group) { ...@@ -75,10 +91,10 @@ func (s *Server) registerMemoRelationRoutes(g *echo.Group) {
}) })
} }
func convertMemoRelationMessageToMemoRelation(memoRelation *store.MemoRelationMessage) *api.MemoRelation { func convertMemoRelationFromStore(memoRelation *store.MemoRelation) *MemoRelation {
return &api.MemoRelation{ return &MemoRelation{
MemoID: memoRelation.MemoID, MemoID: memoRelation.MemoID,
RelatedMemoID: memoRelation.RelatedMemoID, RelatedMemoID: memoRelation.RelatedMemoID,
Type: api.MemoRelationType(memoRelation.Type), Type: MemoRelationType(memoRelation.Type),
} }
} }
package v1 package v1
import (
"encoding/json"
"fmt"
"net/http"
"strconv"
"time"
"github.com/labstack/echo/v4"
"github.com/usememos/memos/store"
)
type MemoResource struct { type MemoResource struct {
MemoID int MemoID int `json:"memoId"`
ResourceID int ResourceID int `json:"resourceId"`
CreatedTs int64 CreatedTs int64 `json:"createdTs"`
UpdatedTs int64 UpdatedTs int64 `json:"updatedTs"`
} }
type MemoResourceUpsert struct { type UpsertMemoResourceRequest struct {
MemoID int `json:"-"` ResourceID int `json:"resourceId"`
ResourceID int UpdatedTs *int64 `json:"updatedTs"`
UpdatedTs *int64
} }
type MemoResourceFind struct { type MemoResourceFind struct {
...@@ -22,3 +32,100 @@ type MemoResourceDelete struct { ...@@ -22,3 +32,100 @@ type MemoResourceDelete struct {
MemoID *int MemoID *int
ResourceID *int ResourceID *int
} }
func (s *APIV1Service) registerMemoResourceRoutes(g *echo.Group) {
g.POST("/memo/:memoId/resource", func(c echo.Context) error {
ctx := c.Request().Context()
memoID, err := strconv.Atoi(c.Param("memoId"))
if err != nil {
return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("ID is not a number: %s", c.Param("memoId"))).SetInternal(err)
}
userID, ok := c.Get(getUserIDContextKey()).(int)
if !ok {
return echo.NewHTTPError(http.StatusUnauthorized, "Missing user in session")
}
request := &UpsertMemoResourceRequest{}
if err := json.NewDecoder(c.Request().Body).Decode(request); err != nil {
return echo.NewHTTPError(http.StatusBadRequest, "Malformatted post memo resource request").SetInternal(err)
}
resource, err := s.Store.GetResource(ctx, &store.FindResource{
ID: &request.ResourceID,
})
if err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to fetch resource").SetInternal(err)
}
if resource == nil {
return echo.NewHTTPError(http.StatusBadRequest, "Resource not found").SetInternal(err)
} else if resource.CreatorID != userID {
return echo.NewHTTPError(http.StatusUnauthorized, "Unauthorized to bind this resource").SetInternal(err)
}
upsert := &store.UpsertMemoResource{
MemoID: memoID,
ResourceID: request.ResourceID,
CreatedTs: time.Now().Unix(),
}
if request.UpdatedTs != nil {
upsert.UpdatedTs = request.UpdatedTs
}
if _, err := s.Store.UpsertMemoResource(ctx, upsert); err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to upsert memo resource").SetInternal(err)
}
return c.JSON(http.StatusOK, true)
})
g.GET("/memo/:memoId/resource", func(c echo.Context) error {
ctx := c.Request().Context()
memoID, err := strconv.Atoi(c.Param("memoId"))
if err != nil {
return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("ID is not a number: %s", c.Param("memoId"))).SetInternal(err)
}
list, err := s.Store.ListResources(ctx, &store.FindResource{
MemoID: &memoID,
})
if err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to fetch resource list").SetInternal(err)
}
resourceList := []*Resource{}
for _, resource := range list {
resourceList = append(resourceList, convertResourceFromStore(resource))
}
return c.JSON(http.StatusOK, resourceList)
})
g.DELETE("/memo/:memoId/resource/:resourceId", func(c echo.Context) error {
ctx := c.Request().Context()
userID, ok := c.Get(getUserIDContextKey()).(int)
if !ok {
return echo.NewHTTPError(http.StatusUnauthorized, "Missing user in session")
}
memoID, err := strconv.Atoi(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)
}
resourceID, err := strconv.Atoi(c.Param("resourceId"))
if err != nil {
return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Resource ID is not a number: %s", c.Param("resourceId"))).SetInternal(err)
}
memo, err := s.Store.GetMemo(ctx, &store.FindMemo{
ID: &memoID,
})
if err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to find memo").SetInternal(err)
}
if memo.CreatorID != userID {
return echo.NewHTTPError(http.StatusUnauthorized, "Unauthorized")
}
if err := s.Store.DeleteMemoResource(ctx, &store.DeleteMemoResource{
MemoID: &memoID,
ResourceID: &resourceID,
}); err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to fetch resource list").SetInternal(err)
}
return c.JSON(http.StatusOK, true)
})
}
...@@ -144,7 +144,7 @@ func (s *APIV1Service) registerResourceRoutes(g *echo.Group) { ...@@ -144,7 +144,7 @@ func (s *APIV1Service) registerResourceRoutes(g *echo.Group) {
} }
} }
resource, err := s.Store.CreateResourceV1(ctx, create) resource, err := s.Store.CreateResource(ctx, create)
if err != nil { if err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to create resource").SetInternal(err) return echo.NewHTTPError(http.StatusInternalServerError, "Failed to create resource").SetInternal(err)
} }
...@@ -311,7 +311,7 @@ func (s *APIV1Service) registerResourceRoutes(g *echo.Group) { ...@@ -311,7 +311,7 @@ func (s *APIV1Service) registerResourceRoutes(g *echo.Group) {
} }
create.PublicID = publicID create.PublicID = publicID
resource, err := s.Store.CreateResourceV1(ctx, create) resource, err := s.Store.CreateResource(ctx, create)
if err != nil { if err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to create resource").SetInternal(err) return echo.NewHTTPError(http.StatusInternalServerError, "Failed to create resource").SetInternal(err)
} }
...@@ -430,7 +430,7 @@ func (s *APIV1Service) registerResourceRoutes(g *echo.Group) { ...@@ -430,7 +430,7 @@ func (s *APIV1Service) registerResourceRoutes(g *echo.Group) {
log.Warn(fmt.Sprintf("failed to delete local thumbnail with path %s", thumbnailPath), zap.Error(err)) log.Warn(fmt.Sprintf("failed to delete local thumbnail with path %s", thumbnailPath), zap.Error(err))
} }
if err := s.Store.DeleteResourceV1(ctx, &store.DeleteResource{ if err := s.Store.DeleteResource(ctx, &store.DeleteResource{
ID: resourceID, ID: resourceID,
}); err != nil { }); err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to delete resource").SetInternal(err) return echo.NewHTTPError(http.StatusInternalServerError, "Failed to delete resource").SetInternal(err)
...@@ -522,7 +522,7 @@ func (s *APIV1Service) createResourceCreateActivity(ctx context.Context, resourc ...@@ -522,7 +522,7 @@ func (s *APIV1Service) createResourceCreateActivity(ctx context.Context, resourc
if err != nil { if err != nil {
return errors.Wrap(err, "failed to marshal activity payload") return errors.Wrap(err, "failed to marshal activity payload")
} }
activity, err := s.Store.CreateActivity(ctx, &store.ActivityMessage{ activity, err := s.Store.CreateActivity(ctx, &store.Activity{
CreatorID: resource.CreatorID, CreatorID: resource.CreatorID,
Type: ActivityResourceCreate.String(), Type: ActivityResourceCreate.String(),
Level: ActivityInfo.String(), Level: ActivityInfo.String(),
......
...@@ -210,7 +210,7 @@ func (s *APIV1Service) createShortcutCreateActivity(c echo.Context, shortcut *Sh ...@@ -210,7 +210,7 @@ func (s *APIV1Service) createShortcutCreateActivity(c echo.Context, shortcut *Sh
if err != nil { if err != nil {
return errors.Wrap(err, "failed to marshal activity payload") return errors.Wrap(err, "failed to marshal activity payload")
} }
activity, err := s.Store.CreateActivity(ctx, &store.ActivityMessage{ activity, err := s.Store.CreateActivity(ctx, &store.Activity{
CreatorID: shortcut.CreatorID, CreatorID: shortcut.CreatorID,
Type: ActivityShortcutCreate.String(), Type: ActivityShortcutCreate.String(),
Level: ActivityInfo.String(), Level: ActivityInfo.String(),
......
...@@ -84,7 +84,7 @@ func (s *APIV1Service) registerTagRoutes(g *echo.Group) { ...@@ -84,7 +84,7 @@ func (s *APIV1Service) registerTagRoutes(g *echo.Group) {
return echo.NewHTTPError(http.StatusBadRequest, "Missing user session") return echo.NewHTTPError(http.StatusBadRequest, "Missing user session")
} }
normalRowStatus := store.Normal normalRowStatus := store.Normal
memoFind := &store.FindMemoMessage{ memoFind := &store.FindMemo{
CreatorID: &userID, CreatorID: &userID,
ContentSearch: []string{"#"}, ContentSearch: []string{"#"},
RowStatus: &normalRowStatus, RowStatus: &normalRowStatus,
...@@ -157,7 +157,7 @@ func (s *APIV1Service) createTagCreateActivity(c echo.Context, tag *Tag) error { ...@@ -157,7 +157,7 @@ func (s *APIV1Service) createTagCreateActivity(c echo.Context, tag *Tag) error {
if err != nil { if err != nil {
return errors.Wrap(err, "failed to marshal activity payload") return errors.Wrap(err, "failed to marshal activity payload")
} }
activity, err := s.Store.CreateActivity(ctx, &store.ActivityMessage{ activity, err := s.Store.CreateActivity(ctx, &store.Activity{
CreatorID: tag.CreatorID, CreatorID: tag.CreatorID,
Type: ActivityTagCreate.String(), Type: ActivityTagCreate.String(),
Level: ActivityInfo.String(), Level: ActivityInfo.String(),
......
...@@ -377,7 +377,7 @@ func (s *APIV1Service) createUserCreateActivity(c echo.Context, user *User) erro ...@@ -377,7 +377,7 @@ func (s *APIV1Service) createUserCreateActivity(c echo.Context, user *User) erro
if err != nil { if err != nil {
return errors.Wrap(err, "failed to marshal activity payload") return errors.Wrap(err, "failed to marshal activity payload")
} }
activity, err := s.Store.CreateActivity(ctx, &store.ActivityMessage{ activity, err := s.Store.CreateActivity(ctx, &store.Activity{
CreatorID: user.ID, CreatorID: user.ID,
Type: ActivityUserCreate.String(), Type: ActivityUserCreate.String(),
Level: ActivityInfo.String(), Level: ActivityInfo.String(),
......
...@@ -35,10 +35,15 @@ func (s *APIV1Service) Register(rootGroup *echo.Group) { ...@@ -35,10 +35,15 @@ func (s *APIV1Service) Register(rootGroup *echo.Group) {
s.registerShortcutRoutes(apiV1Group) s.registerShortcutRoutes(apiV1Group)
s.registerStorageRoutes(apiV1Group) s.registerStorageRoutes(apiV1Group)
s.registerResourceRoutes(apiV1Group) s.registerResourceRoutes(apiV1Group)
s.registerMemoRoutes(apiV1Group)
s.registerMemoOrganizerRoutes(apiV1Group)
s.registerMemoResourceRoutes(apiV1Group)
s.registerMemoRelationRoutes(apiV1Group)
publicGroup := rootGroup.Group("/o") publicGroup := rootGroup.Group("/o")
publicGroup.Use(func(next echo.HandlerFunc) echo.HandlerFunc { publicGroup.Use(func(next echo.HandlerFunc) echo.HandlerFunc {
return JWTMiddleware(s, next, s.Secret) return JWTMiddleware(s, next, s.Secret)
}) })
s.registerGetterPublicRoutes(publicGroup)
s.registerResourcePublicRoutes(publicGroup) s.registerResourcePublicRoutes(publicGroup)
} }
package server
import (
"net/http"
"github.com/labstack/echo/v4"
"github.com/usememos/memos/common"
"github.com/usememos/memos/store"
)
type response struct {
Data any `json:"data"`
}
func composeResponse(data any) response {
return response{
Data: data,
}
}
func defaultGetRequestSkipper(c echo.Context) bool {
return c.Request().Method == http.MethodGet
}
func defaultAPIRequestSkipper(c echo.Context) bool {
path := c.Path()
return common.HasPrefixes(path, "/api")
}
func (s *Server) defaultAuthSkipper(c echo.Context) bool {
ctx := c.Request().Context()
path := c.Path()
// Skip auth.
if common.HasPrefixes(path, "/api/v1/auth") {
return true
}
// If there is openId in query string and related user is found, then skip auth.
openID := c.QueryParam("openId")
if openID != "" {
user, err := s.Store.GetUser(ctx, &store.FindUser{
OpenID: &openID,
})
if err != nil && common.ErrorCode(err) != common.NotFound {
return false
}
if user != nil {
// Stores userID into context.
c.Set(getUserIDContextKey(), user.ID)
return true
}
}
return false
}
package server
import (
"fmt"
"net/http"
"strconv"
"strings"
"time"
"github.com/golang-jwt/jwt/v4"
"github.com/labstack/echo/v4"
"github.com/pkg/errors"
"github.com/usememos/memos/common"
"github.com/usememos/memos/server/auth"
"github.com/usememos/memos/store"
)
const (
// Context section
// The key name used to store user id in the context
// user id is extracted from the jwt token subject field.
userIDContextKey = "user-id"
)
func getUserIDContextKey() string {
return userIDContextKey
}
// Claims creates a struct that will be encoded to a JWT.
// We add jwt.RegisteredClaims as an embedded type, to provide fields such as name.
type Claims struct {
Name string `json:"name"`
jwt.RegisteredClaims
}
func extractTokenFromHeader(c echo.Context) (string, error) {
authHeader := c.Request().Header.Get("Authorization")
if authHeader == "" {
return "", nil
}
authHeaderParts := strings.Fields(authHeader)
if len(authHeaderParts) != 2 || strings.ToLower(authHeaderParts[0]) != "bearer" {
return "", errors.New("Authorization header format must be Bearer {token}")
}
return authHeaderParts[1], nil
}
func findAccessToken(c echo.Context) string {
accessToken := ""
cookie, _ := c.Cookie(auth.AccessTokenCookieName)
if cookie != nil {
accessToken = cookie.Value
}
if accessToken == "" {
accessToken, _ = extractTokenFromHeader(c)
}
return accessToken
}
func audienceContains(audience jwt.ClaimStrings, token string) bool {
for _, v := range audience {
if v == token {
return true
}
}
return false
}
// JWTMiddleware validates the access token.
// If the access token is about to expire or has expired and the request has a valid refresh token, it
// will try to generate new access token and refresh token.
func JWTMiddleware(server *Server, next echo.HandlerFunc, secret string) echo.HandlerFunc {
return func(c echo.Context) error {
path := c.Request().URL.Path
method := c.Request().Method
if server.defaultAuthSkipper(c) {
return next(c)
}
token := findAccessToken(c)
if token == "" {
// Allow the user to access the public endpoints.
if common.HasPrefixes(path, "/o") {
return next(c)
}
// When the request is not authenticated, we allow the user to access the memo endpoints for those public memos.
if common.HasPrefixes(path, "/api/memo") && method == http.MethodGet {
return next(c)
}
return echo.NewHTTPError(http.StatusUnauthorized, "Missing access token")
}
claims := &Claims{}
accessToken, err := jwt.ParseWithClaims(token, claims, func(t *jwt.Token) (any, error) {
if t.Method.Alg() != jwt.SigningMethodHS256.Name {
return nil, errors.Errorf("unexpected access token signing method=%v, expect %v", t.Header["alg"], jwt.SigningMethodHS256)
}
if kid, ok := t.Header["kid"].(string); ok {
if kid == "v1" {
return []byte(secret), nil
}
}
return nil, errors.Errorf("unexpected access token kid=%v", t.Header["kid"])
})
if !accessToken.Valid {
return echo.NewHTTPError(http.StatusUnauthorized, "Invalid access token.")
}
if !audienceContains(claims.Audience, auth.AccessTokenAudienceName) {
return echo.NewHTTPError(http.StatusUnauthorized, fmt.Sprintf("Invalid access token, audience mismatch, got %q, expected %q.", claims.Audience, auth.AccessTokenAudienceName))
}
generateToken := time.Until(claims.ExpiresAt.Time) < auth.RefreshThresholdDuration
if err != nil {
var ve *jwt.ValidationError
if errors.As(err, &ve) {
// If expiration error is the only error, we will clear the err
// and generate new access token and refresh token
if ve.Errors == jwt.ValidationErrorExpired {
generateToken = true
}
} else {
return echo.NewHTTPError(http.StatusUnauthorized, errors.Wrap(err, "Invalid or expired access token"))
}
}
// We either have a valid access token or we will attempt to generate new access token and refresh token
ctx := c.Request().Context()
userID, err := strconv.Atoi(claims.Subject)
if err != nil {
return echo.NewHTTPError(http.StatusUnauthorized, "Malformed ID in the token.")
}
// Even if there is no error, we still need to make sure the user still exists.
user, err := server.Store.GetUser(ctx, &store.FindUser{
ID: &userID,
})
if err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, fmt.Sprintf("Server error to find user ID: %d", userID)).SetInternal(err)
}
if user == nil {
return echo.NewHTTPError(http.StatusUnauthorized, fmt.Sprintf("Failed to find user ID: %d", userID))
}
if generateToken {
generateTokenFunc := func() error {
rc, err := c.Cookie(auth.RefreshTokenCookieName)
if err != nil {
return echo.NewHTTPError(http.StatusUnauthorized, "Failed to generate access token. Missing refresh token.")
}
// Parses token and checks if it's valid.
refreshTokenClaims := &Claims{}
refreshToken, err := jwt.ParseWithClaims(rc.Value, refreshTokenClaims, func(t *jwt.Token) (any, error) {
if t.Method.Alg() != jwt.SigningMethodHS256.Name {
return nil, errors.Errorf("unexpected refresh token signing method=%v, expected %v", t.Header["alg"], jwt.SigningMethodHS256)
}
if kid, ok := t.Header["kid"].(string); ok {
if kid == "v1" {
return []byte(secret), nil
}
}
return nil, errors.Errorf("unexpected refresh token kid=%v", t.Header["kid"])
})
if err != nil {
if err == jwt.ErrSignatureInvalid {
return echo.NewHTTPError(http.StatusUnauthorized, "Failed to generate access token. Invalid refresh token signature.")
}
return echo.NewHTTPError(http.StatusInternalServerError, fmt.Sprintf("Server error to refresh expired token. User Id %d", userID)).SetInternal(err)
}
if !audienceContains(refreshTokenClaims.Audience, auth.RefreshTokenAudienceName) {
return echo.NewHTTPError(http.StatusUnauthorized,
fmt.Sprintf("Invalid refresh token, audience mismatch, got %q, expected %q. you may send request to the wrong environment",
refreshTokenClaims.Audience,
auth.RefreshTokenAudienceName,
))
}
// If we have a valid refresh token, we will generate new access token and refresh token
if refreshToken != nil && refreshToken.Valid {
if err := auth.GenerateTokensAndSetCookies(c, user, secret); err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, fmt.Sprintf("Server error to refresh expired token. User Id %d", userID)).SetInternal(err)
}
}
return nil
}
// It may happen that we still have a valid access token, but we encounter issue when trying to generate new token
// In such case, we won't return the error.
if err := generateTokenFunc(); err != nil && !accessToken.Valid {
return err
}
}
// Stores userID into context.
c.Set(getUserIDContextKey(), userID)
return next(c)
}
}
This diff is collapsed.
package server
import (
"encoding/json"
"fmt"
"net/http"
"strconv"
"time"
"github.com/usememos/memos/api"
"github.com/usememos/memos/store"
"github.com/labstack/echo/v4"
)
func (s *Server) registerMemoResourceRoutes(g *echo.Group) {
g.POST("/memo/:memoId/resource", func(c echo.Context) error {
ctx := c.Request().Context()
memoID, err := strconv.Atoi(c.Param("memoId"))
if err != nil {
return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("ID is not a number: %s", c.Param("memoId"))).SetInternal(err)
}
userID, ok := c.Get(getUserIDContextKey()).(int)
if !ok {
return echo.NewHTTPError(http.StatusUnauthorized, "Missing user in session")
}
memoResourceUpsert := &api.MemoResourceUpsert{}
if err := json.NewDecoder(c.Request().Body).Decode(memoResourceUpsert); err != nil {
return echo.NewHTTPError(http.StatusBadRequest, "Malformatted post memo resource request").SetInternal(err)
}
resource, err := s.Store.GetResource(ctx, &store.FindResource{
ID: &memoResourceUpsert.ResourceID,
})
if err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to fetch resource").SetInternal(err)
}
if resource == nil {
return echo.NewHTTPError(http.StatusBadRequest, "Resource not found").SetInternal(err)
} else if resource.CreatorID != userID {
return echo.NewHTTPError(http.StatusUnauthorized, "Unauthorized to bind this resource").SetInternal(err)
}
memoResourceUpsert.MemoID = memoID
currentTs := time.Now().Unix()
memoResourceUpsert.UpdatedTs = &currentTs
if _, err := s.Store.UpsertMemoResource(ctx, memoResourceUpsert); err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to upsert memo resource").SetInternal(err)
}
return c.JSON(http.StatusOK, true)
})
g.GET("/memo/:memoId/resource", func(c echo.Context) error {
ctx := c.Request().Context()
memoID, err := strconv.Atoi(c.Param("memoId"))
if err != nil {
return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("ID is not a number: %s", c.Param("memoId"))).SetInternal(err)
}
list, err := s.Store.ListResources(ctx, &store.FindResource{
MemoID: &memoID,
})
if err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to fetch resource list").SetInternal(err)
}
resourceList := []*api.Resource{}
for _, resource := range list {
resourceList = append(resourceList, convertResourceFromStore(resource))
}
return c.JSON(http.StatusOK, composeResponse(resourceList))
})
g.DELETE("/memo/:memoId/resource/:resourceId", func(c echo.Context) error {
ctx := c.Request().Context()
userID, ok := c.Get(getUserIDContextKey()).(int)
if !ok {
return echo.NewHTTPError(http.StatusUnauthorized, "Missing user in session")
}
memoID, err := strconv.Atoi(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)
}
resourceID, err := strconv.Atoi(c.Param("resourceId"))
if err != nil {
return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Resource ID is not a number: %s", c.Param("resourceId"))).SetInternal(err)
}
memo, err := s.Store.GetMemo(ctx, &store.FindMemoMessage{
ID: &memoID,
})
if err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to find memo").SetInternal(err)
}
if memo.CreatorID != userID {
return echo.NewHTTPError(http.StatusUnauthorized, "Unauthorized")
}
memoResourceDelete := &api.MemoResourceDelete{
MemoID: &memoID,
ResourceID: &resourceID,
}
if err := s.Store.DeleteMemoResource(ctx, memoResourceDelete); err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to fetch resource list").SetInternal(err)
}
return c.JSON(http.StatusOK, true)
})
}
...@@ -26,7 +26,7 @@ func (s *Server) registerRSSRoutes(g *echo.Group) { ...@@ -26,7 +26,7 @@ func (s *Server) registerRSSRoutes(g *echo.Group) {
} }
normalStatus := store.Normal normalStatus := store.Normal
memoFind := store.FindMemoMessage{ memoFind := store.FindMemo{
RowStatus: &normalStatus, RowStatus: &normalStatus,
VisibilityList: []store.Visibility{store.Public}, VisibilityList: []store.Visibility{store.Public},
} }
...@@ -57,7 +57,7 @@ func (s *Server) registerRSSRoutes(g *echo.Group) { ...@@ -57,7 +57,7 @@ func (s *Server) registerRSSRoutes(g *echo.Group) {
} }
normalStatus := store.Normal normalStatus := store.Normal
memoFind := store.FindMemoMessage{ memoFind := store.FindMemo{
CreatorID: &id, CreatorID: &id,
RowStatus: &normalStatus, RowStatus: &normalStatus,
VisibilityList: []store.Visibility{store.Public}, VisibilityList: []store.Visibility{store.Public},
...@@ -80,7 +80,7 @@ func (s *Server) registerRSSRoutes(g *echo.Group) { ...@@ -80,7 +80,7 @@ func (s *Server) registerRSSRoutes(g *echo.Group) {
const MaxRSSItemCount = 100 const MaxRSSItemCount = 100
const MaxRSSItemTitleLength = 100 const MaxRSSItemTitleLength = 100
func (s *Server) generateRSSFromMemoList(ctx context.Context, memoList []*store.MemoMessage, baseURL string, profile *apiv1.CustomizedProfile) (string, error) { func (s *Server) generateRSSFromMemoList(ctx context.Context, memoList []*store.Memo, baseURL string, profile *apiv1.CustomizedProfile) (string, error) {
feed := &feeds.Feed{ feed := &feeds.Feed{
Title: profile.Name, Title: profile.Name,
Link: &feeds.Link{Href: baseURL}, Link: &feeds.Link{Href: baseURL},
......
...@@ -4,6 +4,7 @@ import ( ...@@ -4,6 +4,7 @@ import (
"context" "context"
"encoding/json" "encoding/json"
"fmt" "fmt"
"net/http"
"time" "time"
"github.com/google/uuid" "github.com/google/uuid"
...@@ -87,20 +88,6 @@ func NewServer(ctx context.Context, profile *profile.Profile, store *store.Store ...@@ -87,20 +88,6 @@ func NewServer(ctx context.Context, profile *profile.Profile, store *store.Store
rootGroup := e.Group("") rootGroup := e.Group("")
s.registerRSSRoutes(rootGroup) s.registerRSSRoutes(rootGroup)
publicGroup := e.Group("/o")
publicGroup.Use(func(next echo.HandlerFunc) echo.HandlerFunc {
return JWTMiddleware(s, next, s.Secret)
})
registerGetterPublicRoutes(publicGroup)
apiGroup := e.Group("/api")
apiGroup.Use(func(next echo.HandlerFunc) echo.HandlerFunc {
return JWTMiddleware(s, next, s.Secret)
})
s.registerMemoRoutes(apiGroup)
s.registerMemoResourceRoutes(apiGroup)
s.registerMemoRelationRoutes(apiGroup)
apiV1Service := apiv1.NewAPIV1Service(s.Secret, profile, store) apiV1Service := apiv1.NewAPIV1Service(s.Secret, profile, store)
apiV1Service.Register(rootGroup) apiV1Service.Register(rootGroup)
...@@ -185,7 +172,7 @@ func (s *Server) createServerStartActivity(ctx context.Context) error { ...@@ -185,7 +172,7 @@ func (s *Server) createServerStartActivity(ctx context.Context) error {
if err != nil { if err != nil {
return errors.Wrap(err, "failed to marshal activity payload") return errors.Wrap(err, "failed to marshal activity payload")
} }
activity, err := s.Store.CreateActivity(ctx, &store.ActivityMessage{ activity, err := s.Store.CreateActivity(ctx, &store.Activity{
CreatorID: apiv1.UnknownID, CreatorID: apiv1.UnknownID,
Type: apiv1.ActivityServerStart.String(), Type: apiv1.ActivityServerStart.String(),
Level: apiv1.ActivityInfo.String(), Level: apiv1.ActivityInfo.String(),
...@@ -196,3 +183,12 @@ func (s *Server) createServerStartActivity(ctx context.Context) error { ...@@ -196,3 +183,12 @@ func (s *Server) createServerStartActivity(ctx context.Context) error {
} }
return err return err
} }
func defaultGetRequestSkipper(c echo.Context) bool {
return c.Request().Method == http.MethodGet
}
func defaultAPIRequestSkipper(c echo.Context) bool {
path := c.Path()
return common.HasPrefixes(path, "/api", "/api/v1")
}
...@@ -8,7 +8,6 @@ import ( ...@@ -8,7 +8,6 @@ import (
"strconv" "strconv"
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/usememos/memos/api"
apiv1 "github.com/usememos/memos/api/v1" apiv1 "github.com/usememos/memos/api/v1"
"github.com/usememos/memos/common" "github.com/usememos/memos/common"
"github.com/usememos/memos/plugin/telegram" "github.com/usememos/memos/plugin/telegram"
...@@ -61,20 +60,19 @@ func (t *telegramHandler) MessageHandle(ctx context.Context, bot *telegram.Bot, ...@@ -61,20 +60,19 @@ func (t *telegramHandler) MessageHandle(ctx context.Context, bot *telegram.Bot,
return err return err
} }
// create memo create := &store.Memo{
memoCreate := api.CreateMemoRequest{
CreatorID: creatorID, CreatorID: creatorID,
Visibility: api.Private, Visibility: store.Private,
} }
if message.Text != nil { if message.Text != nil {
memoCreate.Content = *message.Text create.Content = *message.Text
} }
if blobs != nil && message.Caption != nil { if blobs != nil && message.Caption != nil {
memoCreate.Content = *message.Caption create.Content = *message.Caption
} }
memoMessage, err := t.store.CreateMemo(ctx, convertCreateMemoRequestToMemoMessage(&memoCreate)) memoMessage, err := t.store.CreateMemo(ctx, create)
if err != nil { if err != nil {
_, err := bot.EditMessage(ctx, message.Chat.ID, reply.MessageID, fmt.Sprintf("failed to CreateMemo: %s", err), nil) _, err := bot.EditMessage(ctx, message.Chat.ID, reply.MessageID, fmt.Sprintf("failed to CreateMemo: %s", err), nil)
return err return err
...@@ -90,7 +88,7 @@ func (t *telegramHandler) MessageHandle(ctx context.Context, bot *telegram.Bot, ...@@ -90,7 +88,7 @@ func (t *telegramHandler) MessageHandle(ctx context.Context, bot *telegram.Bot,
case ".png": case ".png":
mime = "image/png" mime = "image/png"
} }
resource, err := t.store.CreateResourceV1(ctx, &store.Resource{ resource, err := t.store.CreateResource(ctx, &store.Resource{
CreatorID: creatorID, CreatorID: creatorID,
Filename: filename, Filename: filename,
Type: mime, Type: mime,
...@@ -103,7 +101,7 @@ func (t *telegramHandler) MessageHandle(ctx context.Context, bot *telegram.Bot, ...@@ -103,7 +101,7 @@ func (t *telegramHandler) MessageHandle(ctx context.Context, bot *telegram.Bot,
return err return err
} }
_, err = t.store.UpsertMemoResource(ctx, &api.MemoResourceUpsert{ _, err = t.store.UpsertMemoResource(ctx, &store.UpsertMemoResource{
MemoID: memoMessage.ID, MemoID: memoMessage.ID,
ResourceID: resource.ID, ResourceID: resource.ID,
}) })
...@@ -126,7 +124,7 @@ func (t *telegramHandler) CallbackQueryHandle(ctx context.Context, bot *telegram ...@@ -126,7 +124,7 @@ func (t *telegramHandler) CallbackQueryHandle(ctx context.Context, bot *telegram
return bot.AnswerCallbackQuery(ctx, callbackQuery.ID, fmt.Sprintf("fail to parse callbackQuery.Data %s", callbackQuery.Data)) return bot.AnswerCallbackQuery(ctx, callbackQuery.ID, fmt.Sprintf("fail to parse callbackQuery.Data %s", callbackQuery.Data))
} }
update := store.UpdateMemoMessage{ update := store.UpdateMemo{
ID: memoID, ID: memoID,
Visibility: &visibility, Visibility: &visibility,
} }
......
...@@ -4,7 +4,7 @@ import ( ...@@ -4,7 +4,7 @@ import (
"context" "context"
) )
type ActivityMessage struct { type Activity struct {
ID int ID int
// Standard fields // Standard fields
...@@ -18,7 +18,7 @@ type ActivityMessage struct { ...@@ -18,7 +18,7 @@ type ActivityMessage struct {
} }
// CreateActivity creates an instance of Activity. // CreateActivity creates an instance of Activity.
func (s *Store) CreateActivity(ctx context.Context, create *ActivityMessage) (*ActivityMessage, error) { func (s *Store) CreateActivity(ctx context.Context, create *Activity) (*Activity, error) {
tx, err := s.db.BeginTx(ctx, nil) tx, err := s.db.BeginTx(ctx, nil)
if err != nil { if err != nil {
return nil, err return nil, err
...@@ -45,6 +45,7 @@ func (s *Store) CreateActivity(ctx context.Context, create *ActivityMessage) (*A ...@@ -45,6 +45,7 @@ func (s *Store) CreateActivity(ctx context.Context, create *ActivityMessage) (*A
if err := tx.Commit(); err != nil { if err := tx.Commit(); err != nil {
return nil, err return nil, err
} }
activityMessage := create
return activityMessage, nil activity := create
return activity, nil
} }
...@@ -123,6 +123,10 @@ func (s *Store) ListIdentityProviders(ctx context.Context, find *FindIdentityPro ...@@ -123,6 +123,10 @@ func (s *Store) ListIdentityProviders(ctx context.Context, find *FindIdentityPro
return nil, err return nil, err
} }
if err := tx.Commit(); err != nil {
return nil, err
}
for _, item := range list { for _, item := range list {
s.idpCache.Store(item.ID, item) s.idpCache.Store(item.ID, item)
} }
...@@ -150,6 +154,10 @@ func (s *Store) GetIdentityProvider(ctx context.Context, find *FindIdentityProvi ...@@ -150,6 +154,10 @@ func (s *Store) GetIdentityProvider(ctx context.Context, find *FindIdentityProvi
return nil, nil return nil, nil
} }
if err := tx.Commit(); err != nil {
return nil, err
}
identityProvider := list[0] identityProvider := list[0]
s.idpCache.Store(identityProvider.ID, identityProvider) s.idpCache.Store(identityProvider.ID, identityProvider)
return identityProvider, nil return identityProvider, nil
......
...@@ -7,8 +7,6 @@ import ( ...@@ -7,8 +7,6 @@ import (
"strconv" "strconv"
"strings" "strings"
"time" "time"
"github.com/usememos/memos/common"
) )
// Visibility is the type of a visibility. // Visibility is the type of a visibility.
...@@ -35,7 +33,7 @@ func (v Visibility) String() string { ...@@ -35,7 +33,7 @@ func (v Visibility) String() string {
return "PRIVATE" return "PRIVATE"
} }
type MemoMessage struct { type Memo struct {
ID int ID int
// Standard fields // Standard fields
...@@ -51,10 +49,10 @@ type MemoMessage struct { ...@@ -51,10 +49,10 @@ type MemoMessage struct {
// Composed fields // Composed fields
Pinned bool Pinned bool
ResourceIDList []int ResourceIDList []int
RelationList []*MemoRelationMessage RelationList []*MemoRelation
} }
type FindMemoMessage struct { type FindMemo struct {
ID *int ID *int
// Standard fields // Standard fields
...@@ -72,7 +70,7 @@ type FindMemoMessage struct { ...@@ -72,7 +70,7 @@ type FindMemoMessage struct {
OrderByUpdatedTs bool OrderByUpdatedTs bool
} }
type UpdateMemoMessage struct { type UpdateMemo struct {
ID int ID int
CreatedTs *int64 CreatedTs *int64
UpdatedTs *int64 UpdatedTs *int64
...@@ -81,14 +79,14 @@ type UpdateMemoMessage struct { ...@@ -81,14 +79,14 @@ type UpdateMemoMessage struct {
Visibility *Visibility Visibility *Visibility
} }
type DeleteMemoMessage struct { type DeleteMemo struct {
ID int ID int
} }
func (s *Store) CreateMemo(ctx context.Context, create *MemoMessage) (*MemoMessage, error) { func (s *Store) CreateMemo(ctx context.Context, create *Memo) (*Memo, error) {
tx, err := s.db.BeginTx(ctx, nil) tx, err := s.db.BeginTx(ctx, nil)
if err != nil { if err != nil {
return nil, FormatError(err) return nil, err
} }
defer tx.Rollback() defer tx.Rollback()
...@@ -119,19 +117,20 @@ func (s *Store) CreateMemo(ctx context.Context, create *MemoMessage) (*MemoMessa ...@@ -119,19 +117,20 @@ func (s *Store) CreateMemo(ctx context.Context, create *MemoMessage) (*MemoMessa
&create.UpdatedTs, &create.UpdatedTs,
&create.RowStatus, &create.RowStatus,
); err != nil { ); err != nil {
return nil, FormatError(err) return nil, err
} }
if err := tx.Commit(); err != nil { if err := tx.Commit(); err != nil {
return nil, FormatError(err) return nil, err
} }
memoMessage := create
return memoMessage, nil memo := create
return memo, nil
} }
func (s *Store) ListMemos(ctx context.Context, find *FindMemoMessage) ([]*MemoMessage, error) { func (s *Store) ListMemos(ctx context.Context, find *FindMemo) ([]*Memo, error) {
tx, err := s.db.BeginTx(ctx, nil) tx, err := s.db.BeginTx(ctx, nil)
if err != nil { if err != nil {
return nil, FormatError(err) return nil, err
} }
defer tx.Rollback() defer tx.Rollback()
...@@ -140,13 +139,17 @@ func (s *Store) ListMemos(ctx context.Context, find *FindMemoMessage) ([]*MemoMe ...@@ -140,13 +139,17 @@ func (s *Store) ListMemos(ctx context.Context, find *FindMemoMessage) ([]*MemoMe
return nil, err return nil, err
} }
if err := tx.Commit(); err != nil {
return nil, err
}
return list, nil return list, nil
} }
func (s *Store) GetMemo(ctx context.Context, find *FindMemoMessage) (*MemoMessage, error) { func (s *Store) GetMemo(ctx context.Context, find *FindMemo) (*Memo, error) {
tx, err := s.db.BeginTx(ctx, nil) tx, err := s.db.BeginTx(ctx, nil)
if err != nil { if err != nil {
return nil, FormatError(err) return nil, err
} }
defer tx.Rollback() defer tx.Rollback()
...@@ -155,14 +158,18 @@ func (s *Store) GetMemo(ctx context.Context, find *FindMemoMessage) (*MemoMessag ...@@ -155,14 +158,18 @@ func (s *Store) GetMemo(ctx context.Context, find *FindMemoMessage) (*MemoMessag
return nil, err return nil, err
} }
if len(list) == 0 { if len(list) == 0 {
return nil, &common.Error{Code: common.NotFound, Err: fmt.Errorf("memo not found")} return nil, nil
}
if err := tx.Commit(); err != nil {
return nil, err
} }
memoMessage := list[0] memo := list[0]
return memoMessage, nil return memo, nil
} }
func (s *Store) UpdateMemo(ctx context.Context, update *UpdateMemoMessage) error { func (s *Store) UpdateMemo(ctx context.Context, update *UpdateMemo) error {
tx, err := s.db.BeginTx(ctx, nil) tx, err := s.db.BeginTx(ctx, nil)
if err != nil { if err != nil {
return err return err
...@@ -199,27 +206,20 @@ func (s *Store) UpdateMemo(ctx context.Context, update *UpdateMemoMessage) error ...@@ -199,27 +206,20 @@ func (s *Store) UpdateMemo(ctx context.Context, update *UpdateMemoMessage) error
return err return err
} }
func (s *Store) DeleteMemo(ctx context.Context, delete *DeleteMemoMessage) error { func (s *Store) DeleteMemo(ctx context.Context, delete *DeleteMemo) error {
tx, err := s.db.BeginTx(ctx, nil) tx, err := s.db.BeginTx(ctx, nil)
if err != nil { if err != nil {
return FormatError(err) return err
} }
defer tx.Rollback() defer tx.Rollback()
where, args := []string{"id = ?"}, []any{delete.ID} where, args := []string{"id = ?"}, []any{delete.ID}
stmt := `DELETE FROM memo WHERE ` + strings.Join(where, " AND ") stmt := `DELETE FROM memo WHERE ` + strings.Join(where, " AND ")
result, err := tx.ExecContext(ctx, stmt, args...) _, err = tx.ExecContext(ctx, stmt, args...)
if err != nil {
return FormatError(err)
}
rows, err := result.RowsAffected()
if err != nil { if err != nil {
return err return err
} }
if rows == 0 {
return &common.Error{Code: common.NotFound, Err: fmt.Errorf("idp not found")}
}
if err := s.vacuumImpl(ctx, tx); err != nil { if err := s.vacuumImpl(ctx, tx); err != nil {
return err return err
} }
...@@ -230,7 +230,7 @@ func (s *Store) DeleteMemo(ctx context.Context, delete *DeleteMemoMessage) error ...@@ -230,7 +230,7 @@ func (s *Store) DeleteMemo(ctx context.Context, delete *DeleteMemoMessage) error
func (s *Store) FindMemosVisibilityList(ctx context.Context, memoIDs []int) ([]Visibility, error) { func (s *Store) FindMemosVisibilityList(ctx context.Context, memoIDs []int) ([]Visibility, error) {
tx, err := s.db.BeginTx(ctx, nil) tx, err := s.db.BeginTx(ctx, nil)
if err != nil { if err != nil {
return nil, FormatError(err) return nil, err
} }
defer tx.Rollback() defer tx.Rollback()
...@@ -247,7 +247,7 @@ func (s *Store) FindMemosVisibilityList(ctx context.Context, memoIDs []int) ([]V ...@@ -247,7 +247,7 @@ func (s *Store) FindMemosVisibilityList(ctx context.Context, memoIDs []int) ([]V
rows, err := tx.QueryContext(ctx, query, args...) rows, err := tx.QueryContext(ctx, query, args...)
if err != nil { if err != nil {
return nil, FormatError(err) return nil, err
} }
defer rows.Close() defer rows.Close()
...@@ -255,19 +255,19 @@ func (s *Store) FindMemosVisibilityList(ctx context.Context, memoIDs []int) ([]V ...@@ -255,19 +255,19 @@ func (s *Store) FindMemosVisibilityList(ctx context.Context, memoIDs []int) ([]V
for rows.Next() { for rows.Next() {
var visibility Visibility var visibility Visibility
if err := rows.Scan(&visibility); err != nil { if err := rows.Scan(&visibility); err != nil {
return nil, FormatError(err) return nil, err
} }
visibilityList = append(visibilityList, visibility) visibilityList = append(visibilityList, visibility)
} }
if err := rows.Err(); err != nil { if err := rows.Err(); err != nil {
return nil, FormatError(err) return nil, err
} }
return visibilityList, nil return visibilityList, nil
} }
func listMemos(ctx context.Context, tx *sql.Tx, find *FindMemoMessage) ([]*MemoMessage, error) { func listMemos(ctx context.Context, tx *sql.Tx, find *FindMemo) ([]*Memo, error) {
where, args := []string{"1 = 1"}, []any{} where, args := []string{"1 = 1"}, []any{}
if v := find.ID; v != nil { if v := find.ID; v != nil {
...@@ -343,68 +343,68 @@ func listMemos(ctx context.Context, tx *sql.Tx, find *FindMemoMessage) ([]*MemoM ...@@ -343,68 +343,68 @@ func listMemos(ctx context.Context, tx *sql.Tx, find *FindMemoMessage) ([]*MemoM
rows, err := tx.QueryContext(ctx, query, args...) rows, err := tx.QueryContext(ctx, query, args...)
if err != nil { if err != nil {
return nil, FormatError(err) return nil, err
} }
defer rows.Close() defer rows.Close()
memoMessageList := make([]*MemoMessage, 0) list := make([]*Memo, 0)
for rows.Next() { for rows.Next() {
var memoMessage MemoMessage var memo Memo
var memoResourceIDList sql.NullString var memoResourceIDList sql.NullString
var memoRelationList sql.NullString var memoRelationList sql.NullString
if err := rows.Scan( if err := rows.Scan(
&memoMessage.ID, &memo.ID,
&memoMessage.CreatorID, &memo.CreatorID,
&memoMessage.CreatedTs, &memo.CreatedTs,
&memoMessage.UpdatedTs, &memo.UpdatedTs,
&memoMessage.RowStatus, &memo.RowStatus,
&memoMessage.Content, &memo.Content,
&memoMessage.Visibility, &memo.Visibility,
&memoMessage.Pinned, &memo.Pinned,
&memoResourceIDList, &memoResourceIDList,
&memoRelationList, &memoRelationList,
); err != nil { ); err != nil {
return nil, FormatError(err) return nil, err
} }
if memoResourceIDList.Valid { if memoResourceIDList.Valid {
idStringList := strings.Split(memoResourceIDList.String, ",") idStringList := strings.Split(memoResourceIDList.String, ",")
memoMessage.ResourceIDList = make([]int, 0, len(idStringList)) memo.ResourceIDList = make([]int, 0, len(idStringList))
for _, idString := range idStringList { for _, idString := range idStringList {
id, err := strconv.Atoi(idString) id, err := strconv.Atoi(idString)
if err != nil { if err != nil {
return nil, FormatError(err) return nil, err
} }
memoMessage.ResourceIDList = append(memoMessage.ResourceIDList, id) memo.ResourceIDList = append(memo.ResourceIDList, id)
} }
} }
if memoRelationList.Valid { if memoRelationList.Valid {
memoMessage.RelationList = make([]*MemoRelationMessage, 0) memo.RelationList = make([]*MemoRelation, 0)
relatedMemoTypeList := strings.Split(memoRelationList.String, ",") relatedMemoTypeList := strings.Split(memoRelationList.String, ",")
for _, relatedMemoType := range relatedMemoTypeList { for _, relatedMemoType := range relatedMemoTypeList {
relatedMemoTypeList := strings.Split(relatedMemoType, ":") relatedMemoTypeList := strings.Split(relatedMemoType, ":")
if len(relatedMemoTypeList) != 2 { if len(relatedMemoTypeList) != 2 {
return nil, &common.Error{Code: common.Invalid, Err: fmt.Errorf("invalid relation format")} return nil, fmt.Errorf("invalid relation format")
} }
relatedMemoID, err := strconv.Atoi(relatedMemoTypeList[0]) relatedMemoID, err := strconv.Atoi(relatedMemoTypeList[0])
if err != nil { if err != nil {
return nil, FormatError(err) return nil, err
} }
memoMessage.RelationList = append(memoMessage.RelationList, &MemoRelationMessage{ memo.RelationList = append(memo.RelationList, &MemoRelation{
MemoID: memoMessage.ID, MemoID: memo.ID,
RelatedMemoID: relatedMemoID, RelatedMemoID: relatedMemoID,
Type: MemoRelationType(relatedMemoTypeList[1]), Type: MemoRelationType(relatedMemoTypeList[1]),
}) })
} }
} }
memoMessageList = append(memoMessageList, &memoMessage) list = append(list, &memo)
} }
if err := rows.Err(); err != nil { if err := rows.Err(); err != nil {
return nil, FormatError(err) return nil, err
} }
return memoMessageList, nil return list, nil
} }
func vacuumMemo(ctx context.Context, tx *sql.Tx) error { func vacuumMemo(ctx context.Context, tx *sql.Tx) error {
...@@ -420,7 +420,7 @@ func vacuumMemo(ctx context.Context, tx *sql.Tx) error { ...@@ -420,7 +420,7 @@ func vacuumMemo(ctx context.Context, tx *sql.Tx) error {
)` )`
_, err := tx.ExecContext(ctx, stmt) _, err := tx.ExecContext(ctx, stmt)
if err != nil { if err != nil {
return FormatError(err) return err
} }
return nil return nil
......
...@@ -5,142 +5,99 @@ import ( ...@@ -5,142 +5,99 @@ import (
"database/sql" "database/sql"
"fmt" "fmt"
"strings" "strings"
"github.com/usememos/memos/api"
"github.com/usememos/memos/common"
) )
// memoOrganizerRaw is the store model for an MemoOrganizer. type MemoOrganizer struct {
// Fields have exactly the same meanings as MemoOrganizer.
type memoOrganizerRaw struct {
// Domain specific fields
MemoID int MemoID int
UserID int UserID int
Pinned bool Pinned bool
} }
func (raw *memoOrganizerRaw) toMemoOrganizer() *api.MemoOrganizer { type FindMemoOrganizer struct {
return &api.MemoOrganizer{ MemoID int
MemoID: raw.MemoID, UserID int
UserID: raw.UserID,
Pinned: raw.Pinned,
}
} }
func (s *Store) FindMemoOrganizer(ctx context.Context, find *api.MemoOrganizerFind) (*api.MemoOrganizer, error) { type DeleteMemoOrganizer struct {
tx, err := s.db.BeginTx(ctx, nil) MemoID *int
if err != nil { UserID *int
return nil, FormatError(err)
}
defer tx.Rollback()
memoOrganizerRaw, err := findMemoOrganizer(ctx, tx, find)
if err != nil {
return nil, err
}
memoOrganizer := memoOrganizerRaw.toMemoOrganizer()
return memoOrganizer, nil
} }
func (s *Store) UpsertMemoOrganizer(ctx context.Context, upsert *api.MemoOrganizerUpsert) error { func (s *Store) UpsertMemoOrganizerV1(ctx context.Context, upsert *MemoOrganizer) (*MemoOrganizer, error) {
tx, err := s.db.BeginTx(ctx, nil) tx, err := s.db.BeginTx(ctx, nil)
if err != nil { if err != nil {
return FormatError(err) return nil, err
} }
defer tx.Rollback() defer tx.Rollback()
if err := upsertMemoOrganizer(ctx, tx, upsert); err != nil { query := `
return err INSERT INTO memo_organizer (
memo_id,
user_id,
pinned
)
VALUES (?, ?, ?)
ON CONFLICT(memo_id, user_id) DO UPDATE
SET
pinned = EXCLUDED.pinned
`
if _, err := tx.ExecContext(ctx, query, upsert.MemoID, upsert.UserID, upsert.Pinned); err != nil {
return nil, err
} }
if err := tx.Commit(); err != nil { if err := tx.Commit(); err != nil {
return FormatError(err) return nil, err
} }
return nil memoOrganizer := upsert
return memoOrganizer, nil
} }
func (s *Store) DeleteMemoOrganizer(ctx context.Context, delete *api.MemoOrganizerDelete) error { func (s *Store) GetMemoOrganizerV1(ctx context.Context, find *FindMemoOrganizer) (*MemoOrganizer, error) {
tx, err := s.db.BeginTx(ctx, nil) tx, err := s.db.BeginTx(ctx, nil)
if err != nil { if err != nil {
return FormatError(err) return nil, err
} }
defer tx.Rollback() defer tx.Rollback()
if err := deleteMemoOrganizer(ctx, tx, delete); err != nil { where, args := []string{}, []any{}
return err if find.MemoID != 0 {
where = append(where, "memo_id = ?")
args = append(args, find.MemoID)
} }
if find.UserID != 0 {
if err := tx.Commit(); err != nil { where = append(where, "user_id = ?")
return FormatError(err) args = append(args, find.UserID)
} }
query := fmt.Sprintf(`
return nil
}
func findMemoOrganizer(ctx context.Context, tx *sql.Tx, find *api.MemoOrganizerFind) (*memoOrganizerRaw, error) {
query := `
SELECT SELECT
memo_id, memo_id,
user_id, user_id,
pinned pinned
FROM memo_organizer FROM memo_organizer
WHERE memo_id = ? AND user_id = ? WHERE %s
` `, strings.Join(where, " AND "))
row, err := tx.QueryContext(ctx, query, find.MemoID, find.UserID) row := tx.QueryRowContext(ctx, query, args...)
if err != nil {
return nil, FormatError(err)
}
defer row.Close()
if !row.Next() {
return nil, &common.Error{Code: common.NotFound, Err: fmt.Errorf("not found")}
}
var memoOrganizerRaw memoOrganizerRaw memoOrganizer := &MemoOrganizer{}
if err := row.Scan( if err := row.Scan(
&memoOrganizerRaw.MemoID, &memoOrganizer.MemoID,
&memoOrganizerRaw.UserID, &memoOrganizer.UserID,
&memoOrganizerRaw.Pinned, &memoOrganizer.Pinned,
); err != nil { ); err != nil {
return nil, FormatError(err)
}
if err := row.Err(); err != nil {
return nil, err return nil, err
} }
return &memoOrganizerRaw, nil return memoOrganizer, nil
} }
func upsertMemoOrganizer(ctx context.Context, tx *sql.Tx, upsert *api.MemoOrganizerUpsert) error { func (s *Store) DeleteMemoOrganizerV1(ctx context.Context, delete *DeleteMemoOrganizer) error {
query := ` tx, err := s.db.BeginTx(ctx, nil)
INSERT INTO memo_organizer ( if err != nil {
memo_id,
user_id,
pinned
)
VALUES (?, ?, ?)
ON CONFLICT(memo_id, user_id) DO UPDATE
SET
pinned = EXCLUDED.pinned
RETURNING memo_id, user_id, pinned
`
var memoOrganizer api.MemoOrganizer
if err := tx.QueryRowContext(ctx, query, upsert.MemoID, upsert.UserID, upsert.Pinned).Scan(
&memoOrganizer.MemoID,
&memoOrganizer.UserID,
&memoOrganizer.Pinned,
); err != nil {
return FormatError(err) return FormatError(err)
} }
defer tx.Rollback()
return nil
}
func deleteMemoOrganizer(ctx context.Context, tx *sql.Tx, delete *api.MemoOrganizerDelete) error {
where, args := []string{}, []any{} where, args := []string{}, []any{}
if v := delete.MemoID; v != nil { if v := delete.MemoID; v != nil {
...@@ -151,14 +108,13 @@ func deleteMemoOrganizer(ctx context.Context, tx *sql.Tx, delete *api.MemoOrgani ...@@ -151,14 +108,13 @@ func deleteMemoOrganizer(ctx context.Context, tx *sql.Tx, delete *api.MemoOrgani
} }
stmt := `DELETE FROM memo_organizer WHERE ` + strings.Join(where, " AND ") stmt := `DELETE FROM memo_organizer WHERE ` + strings.Join(where, " AND ")
result, err := tx.ExecContext(ctx, stmt, args...) _, err = tx.ExecContext(ctx, stmt, args...)
if err != nil { if err != nil {
return FormatError(err) return FormatError(err)
} }
rows, _ := result.RowsAffected() if err := tx.Commit(); err != nil {
if rows == 0 { return FormatError(err)
return &common.Error{Code: common.NotFound, Err: fmt.Errorf("memo organizer not found")}
} }
return nil return nil
......
...@@ -3,10 +3,7 @@ package store ...@@ -3,10 +3,7 @@ package store
import ( import (
"context" "context"
"database/sql" "database/sql"
"fmt"
"strings" "strings"
"github.com/usememos/memos/common"
) )
type MemoRelationType string type MemoRelationType string
...@@ -16,28 +13,28 @@ const ( ...@@ -16,28 +13,28 @@ const (
MemoRelationAdditional MemoRelationType = "ADDITIONAL" MemoRelationAdditional MemoRelationType = "ADDITIONAL"
) )
type MemoRelationMessage struct { type MemoRelation struct {
MemoID int MemoID int
RelatedMemoID int RelatedMemoID int
Type MemoRelationType Type MemoRelationType
} }
type FindMemoRelationMessage struct { type FindMemoRelation struct {
MemoID *int MemoID *int
RelatedMemoID *int RelatedMemoID *int
Type *MemoRelationType Type *MemoRelationType
} }
type DeleteMemoRelationMessage struct { type DeleteMemoRelation struct {
MemoID *int MemoID *int
RelatedMemoID *int RelatedMemoID *int
Type *MemoRelationType Type *MemoRelationType
} }
func (s *Store) UpsertMemoRelation(ctx context.Context, create *MemoRelationMessage) (*MemoRelationMessage, error) { func (s *Store) UpsertMemoRelation(ctx context.Context, create *MemoRelation) (*MemoRelation, error) {
tx, err := s.db.BeginTx(ctx, nil) tx, err := s.db.BeginTx(ctx, nil)
if err != nil { if err != nil {
return nil, FormatError(err) return nil, err
} }
defer tx.Rollback() defer tx.Rollback()
...@@ -52,7 +49,7 @@ func (s *Store) UpsertMemoRelation(ctx context.Context, create *MemoRelationMess ...@@ -52,7 +49,7 @@ func (s *Store) UpsertMemoRelation(ctx context.Context, create *MemoRelationMess
type = EXCLUDED.type type = EXCLUDED.type
RETURNING memo_id, related_memo_id, type RETURNING memo_id, related_memo_id, type
` `
memoRelationMessage := &MemoRelationMessage{} memoRelationMessage := &MemoRelation{}
if err := tx.QueryRowContext( if err := tx.QueryRowContext(
ctx, ctx,
query, query,
...@@ -64,18 +61,18 @@ func (s *Store) UpsertMemoRelation(ctx context.Context, create *MemoRelationMess ...@@ -64,18 +61,18 @@ func (s *Store) UpsertMemoRelation(ctx context.Context, create *MemoRelationMess
&memoRelationMessage.RelatedMemoID, &memoRelationMessage.RelatedMemoID,
&memoRelationMessage.Type, &memoRelationMessage.Type,
); err != nil { ); err != nil {
return nil, FormatError(err) return nil, err
} }
if err := tx.Commit(); err != nil { if err := tx.Commit(); err != nil {
return nil, FormatError(err) return nil, err
} }
return memoRelationMessage, nil return memoRelationMessage, nil
} }
func (s *Store) ListMemoRelations(ctx context.Context, find *FindMemoRelationMessage) ([]*MemoRelationMessage, error) { func (s *Store) ListMemoRelations(ctx context.Context, find *FindMemoRelation) ([]*MemoRelation, error) {
tx, err := s.db.BeginTx(ctx, nil) tx, err := s.db.BeginTx(ctx, nil)
if err != nil { if err != nil {
return nil, FormatError(err) return nil, err
} }
defer tx.Rollback() defer tx.Rollback()
...@@ -84,13 +81,17 @@ func (s *Store) ListMemoRelations(ctx context.Context, find *FindMemoRelationMes ...@@ -84,13 +81,17 @@ func (s *Store) ListMemoRelations(ctx context.Context, find *FindMemoRelationMes
return nil, err return nil, err
} }
if err := tx.Commit(); err != nil {
return nil, err
}
return list, nil return list, nil
} }
func (s *Store) GetMemoRelation(ctx context.Context, find *FindMemoRelationMessage) (*MemoRelationMessage, error) { func (s *Store) GetMemoRelation(ctx context.Context, find *FindMemoRelation) (*MemoRelation, error) {
tx, err := s.db.BeginTx(ctx, nil) tx, err := s.db.BeginTx(ctx, nil)
if err != nil { if err != nil {
return nil, FormatError(err) return nil, err
} }
defer tx.Rollback() defer tx.Rollback()
...@@ -100,15 +101,20 @@ func (s *Store) GetMemoRelation(ctx context.Context, find *FindMemoRelationMessa ...@@ -100,15 +101,20 @@ func (s *Store) GetMemoRelation(ctx context.Context, find *FindMemoRelationMessa
} }
if len(list) == 0 { if len(list) == 0 {
return nil, &common.Error{Code: common.NotFound, Err: fmt.Errorf("not found")} return nil, nil
}
if err := tx.Commit(); err != nil {
return nil, err
} }
return list[0], nil return list[0], nil
} }
func (s *Store) DeleteMemoRelation(ctx context.Context, delete *DeleteMemoRelationMessage) error { func (s *Store) DeleteMemoRelation(ctx context.Context, delete *DeleteMemoRelation) error {
tx, err := s.db.BeginTx(ctx, nil) tx, err := s.db.BeginTx(ctx, nil)
if err != nil { if err != nil {
return FormatError(err) return err
} }
defer tx.Rollback() defer tx.Rollback()
...@@ -127,16 +133,17 @@ func (s *Store) DeleteMemoRelation(ctx context.Context, delete *DeleteMemoRelati ...@@ -127,16 +133,17 @@ func (s *Store) DeleteMemoRelation(ctx context.Context, delete *DeleteMemoRelati
DELETE FROM memo_relation DELETE FROM memo_relation
WHERE ` + strings.Join(where, " AND ") WHERE ` + strings.Join(where, " AND ")
if _, err := tx.ExecContext(ctx, query, args...); err != nil { if _, err := tx.ExecContext(ctx, query, args...); err != nil {
return FormatError(err) return err
} }
if err := tx.Commit(); err != nil { if err := tx.Commit(); err != nil {
return FormatError(err) // Prevent lint warning.
return err
} }
return nil return nil
} }
func listMemoRelations(ctx context.Context, tx *sql.Tx, find *FindMemoRelationMessage) ([]*MemoRelationMessage, error) { func listMemoRelations(ctx context.Context, tx *sql.Tx, find *FindMemoRelation) ([]*MemoRelation, error) {
where, args := []string{"TRUE"}, []any{} where, args := []string{"TRUE"}, []any{}
if find.MemoID != nil { if find.MemoID != nil {
where, args = append(where, "memo_id = ?"), append(args, find.MemoID) where, args = append(where, "memo_id = ?"), append(args, find.MemoID)
...@@ -156,24 +163,24 @@ func listMemoRelations(ctx context.Context, tx *sql.Tx, find *FindMemoRelationMe ...@@ -156,24 +163,24 @@ func listMemoRelations(ctx context.Context, tx *sql.Tx, find *FindMemoRelationMe
FROM memo_relation FROM memo_relation
WHERE `+strings.Join(where, " AND "), args...) WHERE `+strings.Join(where, " AND "), args...)
if err != nil { if err != nil {
return nil, FormatError(err) return nil, err
} }
defer rows.Close() defer rows.Close()
memoRelationMessages := []*MemoRelationMessage{} memoRelationMessages := []*MemoRelation{}
for rows.Next() { for rows.Next() {
memoRelationMessage := &MemoRelationMessage{} memoRelationMessage := &MemoRelation{}
if err := rows.Scan( if err := rows.Scan(
&memoRelationMessage.MemoID, &memoRelationMessage.MemoID,
&memoRelationMessage.RelatedMemoID, &memoRelationMessage.RelatedMemoID,
&memoRelationMessage.Type, &memoRelationMessage.Type,
); err != nil { ); err != nil {
return nil, FormatError(err) return nil, err
} }
memoRelationMessages = append(memoRelationMessages, memoRelationMessage) memoRelationMessages = append(memoRelationMessages, memoRelationMessage)
} }
if err := rows.Err(); err != nil { if err := rows.Err(); err != nil {
return nil, FormatError(err) return nil, err
} }
return memoRelationMessages, nil return memoRelationMessages, nil
} }
...@@ -183,7 +190,7 @@ func vacuumMemoRelations(ctx context.Context, tx *sql.Tx) error { ...@@ -183,7 +190,7 @@ func vacuumMemoRelations(ctx context.Context, tx *sql.Tx) error {
DELETE FROM memo_relation DELETE FROM memo_relation
WHERE memo_id NOT IN (SELECT id FROM memo) OR related_memo_id NOT IN (SELECT id FROM memo) WHERE memo_id NOT IN (SELECT id FROM memo) OR related_memo_id NOT IN (SELECT id FROM memo)
`); err != nil { `); err != nil {
return FormatError(err) return err
} }
return nil return nil
} }
...@@ -3,11 +3,7 @@ package store ...@@ -3,11 +3,7 @@ package store
import ( import (
"context" "context"
"database/sql" "database/sql"
"fmt"
"strings" "strings"
"github.com/usememos/memos/api"
"github.com/usememos/memos/common"
) )
type MemoResource struct { type MemoResource struct {
...@@ -17,60 +13,50 @@ type MemoResource struct { ...@@ -17,60 +13,50 @@ type MemoResource struct {
UpdatedTs int64 UpdatedTs int64
} }
type UpsertMemoResource struct {
MemoID int
ResourceID int
CreatedTs int64
UpdatedTs *int64
}
type FindMemoResource struct { type FindMemoResource struct {
MemoID *int MemoID *int
ResourceID *int ResourceID *int
} }
func (s *Store) ListMemoResources(ctx context.Context, find *FindMemoResource) ([]*MemoResource, error) { type DeleteMemoResource struct {
MemoID *int
ResourceID *int
}
func (s *Store) UpsertMemoResource(ctx context.Context, upsert *UpsertMemoResource) (*MemoResource, error) {
tx, err := s.db.BeginTx(ctx, nil) tx, err := s.db.BeginTx(ctx, nil)
if err != nil { if err != nil {
return nil, err return nil, FormatError(err)
} }
defer tx.Rollback() defer tx.Rollback()
list, err := listMemoResources(ctx, tx, find) set := []string{"memo_id", "resource_id"}
if err != nil { args := []any{upsert.MemoID, upsert.ResourceID}
return nil, err placeholder := []string{"?", "?"}
}
if err := tx.Commit(); err != nil {
return nil, err
}
return list, nil
}
func listMemoResources(ctx context.Context, tx *sql.Tx, find *FindMemoResource) ([]*MemoResource, error) {
where, args := []string{"1 = 1"}, []any{}
if v := find.MemoID; v != nil { if v := upsert.UpdatedTs; v != nil {
where, args = append(where, "memo_id = ?"), append(args, *v) set, args, placeholder = append(set, "updated_ts"), append(args, v), append(placeholder, "?")
}
if v := find.ResourceID; v != nil {
where, args = append(where, "resource_id = ?"), append(args, *v)
} }
query := ` query := `
SELECT INSERT INTO memo_resource (
memo_id, ` + strings.Join(set, ", ") + `
resource_id, )
created_ts, VALUES (` + strings.Join(placeholder, ",") + `)
updated_ts ON CONFLICT(memo_id, resource_id) DO UPDATE
FROM memo_resource SET
WHERE ` + strings.Join(where, " AND ") + ` updated_ts = EXCLUDED.updated_ts
ORDER BY updated_ts DESC RETURNING memo_id, resource_id, created_ts, updated_ts
` `
rows, err := tx.QueryContext(ctx, query, args...) memoResource := &MemoResource{}
if err != nil { if err := tx.QueryRowContext(ctx, query, args...).Scan(
return nil, FormatError(err)
}
defer rows.Close()
list := make([]*MemoResource, 0)
for rows.Next() {
var memoResource MemoResource
if err := rows.Scan(
&memoResource.MemoID, &memoResource.MemoID,
&memoResource.ResourceID, &memoResource.ResourceID,
&memoResource.CreatedTs, &memoResource.CreatedTs,
...@@ -79,103 +65,74 @@ func listMemoResources(ctx context.Context, tx *sql.Tx, find *FindMemoResource) ...@@ -79,103 +65,74 @@ func listMemoResources(ctx context.Context, tx *sql.Tx, find *FindMemoResource)
return nil, FormatError(err) return nil, FormatError(err)
} }
list = append(list, &memoResource) if err := tx.Commit(); err != nil {
} return nil, FormatError(err)
if err := rows.Err(); err != nil {
return nil, err
} }
return list, nil return memoResource, nil
} }
// memoResourceRaw is the store model for an MemoResource. func (s *Store) ListMemoResources(ctx context.Context, find *FindMemoResource) ([]*MemoResource, error) {
// Fields have exactly the same meanings as MemoResource.
type memoResourceRaw struct {
MemoID int
ResourceID int
CreatedTs int64
UpdatedTs int64
}
func (raw *memoResourceRaw) toMemoResource() *api.MemoResource {
return &api.MemoResource{
MemoID: raw.MemoID,
ResourceID: raw.ResourceID,
CreatedTs: raw.CreatedTs,
UpdatedTs: raw.UpdatedTs,
}
}
func (s *Store) FindMemoResourceList(ctx context.Context, find *api.MemoResourceFind) ([]*api.MemoResource, error) {
tx, err := s.db.BeginTx(ctx, nil) tx, err := s.db.BeginTx(ctx, nil)
if err != nil { if err != nil {
return nil, FormatError(err) return nil, err
} }
defer tx.Rollback() defer tx.Rollback()
memoResourceRawList, err := findMemoResourceList(ctx, tx, find) list, err := listMemoResources(ctx, tx, find)
if err != nil { if err != nil {
return nil, err return nil, err
} }
list := []*api.MemoResource{} if err := tx.Commit(); err != nil {
for _, raw := range memoResourceRawList { return nil, err
memoResource := raw.toMemoResource()
list = append(list, memoResource)
} }
return list, nil return list, nil
} }
func (s *Store) FindMemoResource(ctx context.Context, find *api.MemoResourceFind) (*api.MemoResource, error) { func (s *Store) GetMemoResource(ctx context.Context, find *FindMemoResource) (*MemoResource, error) {
tx, err := s.db.BeginTx(ctx, nil) tx, err := s.db.BeginTx(ctx, nil)
if err != nil { if err != nil {
return nil, FormatError(err) return nil, err
} }
defer tx.Rollback() defer tx.Rollback()
list, err := findMemoResourceList(ctx, tx, find) list, err := listMemoResources(ctx, tx, find)
if err != nil { if err != nil {
return nil, err return nil, err
} }
if len(list) == 0 { if len(list) == 0 {
return nil, &common.Error{Code: common.NotFound, Err: fmt.Errorf("not found")} return nil, nil
} }
memoResourceRaw := list[0] if err := tx.Commit(); err != nil {
return nil, err
}
return memoResourceRaw.toMemoResource(), nil memoResource := list[0]
return memoResource, nil
} }
func (s *Store) UpsertMemoResource(ctx context.Context, upsert *api.MemoResourceUpsert) (*api.MemoResource, error) { func (s *Store) DeleteMemoResource(ctx context.Context, delete *DeleteMemoResource) error {
tx, err := s.db.BeginTx(ctx, nil) tx, err := s.db.BeginTx(ctx, nil)
if err != nil { if err != nil {
return nil, FormatError(err) return FormatError(err)
} }
defer tx.Rollback() defer tx.Rollback()
memoResourceRaw, err := upsertMemoResource(ctx, tx, upsert) where, args := []string{}, []any{}
if err != nil {
return nil, err
}
if err := tx.Commit(); err != nil { if v := delete.MemoID; v != nil {
return nil, FormatError(err) where, args = append(where, "memo_id = ?"), append(args, *v)
} }
if v := delete.ResourceID; v != nil {
return memoResourceRaw.toMemoResource(), nil where, args = append(where, "resource_id = ?"), append(args, *v)
}
func (s *Store) DeleteMemoResource(ctx context.Context, delete *api.MemoResourceDelete) error {
tx, err := s.db.BeginTx(ctx, nil)
if err != nil {
return FormatError(err)
} }
defer tx.Rollback()
if err := deleteMemoResource(ctx, tx, delete); err != nil { stmt := `DELETE FROM memo_resource WHERE ` + strings.Join(where, " AND ")
_, err = tx.ExecContext(ctx, stmt, args...)
if err != nil {
return FormatError(err) return FormatError(err)
} }
...@@ -186,7 +143,7 @@ func (s *Store) DeleteMemoResource(ctx context.Context, delete *api.MemoResource ...@@ -186,7 +143,7 @@ func (s *Store) DeleteMemoResource(ctx context.Context, delete *api.MemoResource
return nil return nil
} }
func findMemoResourceList(ctx context.Context, tx *sql.Tx, find *api.MemoResourceFind) ([]*memoResourceRaw, error) { func listMemoResources(ctx context.Context, tx *sql.Tx, find *FindMemoResource) ([]*MemoResource, error) {
where, args := []string{"1 = 1"}, []any{} where, args := []string{"1 = 1"}, []any{}
if v := find.MemoID; v != nil { if v := find.MemoID; v != nil {
...@@ -212,82 +169,26 @@ func findMemoResourceList(ctx context.Context, tx *sql.Tx, find *api.MemoResourc ...@@ -212,82 +169,26 @@ func findMemoResourceList(ctx context.Context, tx *sql.Tx, find *api.MemoResourc
} }
defer rows.Close() defer rows.Close()
memoResourceRawList := make([]*memoResourceRaw, 0) list := make([]*MemoResource, 0)
for rows.Next() { for rows.Next() {
var memoResourceRaw memoResourceRaw var memoResource MemoResource
if err := rows.Scan( if err := rows.Scan(
&memoResourceRaw.MemoID, &memoResource.MemoID,
&memoResourceRaw.ResourceID, &memoResource.ResourceID,
&memoResourceRaw.CreatedTs, &memoResource.CreatedTs,
&memoResourceRaw.UpdatedTs, &memoResource.UpdatedTs,
); err != nil { ); err != nil {
return nil, FormatError(err) return nil, FormatError(err)
} }
memoResourceRawList = append(memoResourceRawList, &memoResourceRaw) list = append(list, &memoResource)
} }
if err := rows.Err(); err != nil { if err := rows.Err(); err != nil {
return nil, err return nil, err
} }
return memoResourceRawList, nil return list, nil
}
func upsertMemoResource(ctx context.Context, tx *sql.Tx, upsert *api.MemoResourceUpsert) (*memoResourceRaw, error) {
set := []string{"memo_id", "resource_id"}
args := []any{upsert.MemoID, upsert.ResourceID}
placeholder := []string{"?", "?"}
if v := upsert.UpdatedTs; v != nil {
set, args, placeholder = append(set, "updated_ts"), append(args, v), append(placeholder, "?")
}
query := `
INSERT INTO memo_resource (
` + strings.Join(set, ", ") + `
)
VALUES (` + strings.Join(placeholder, ",") + `)
ON CONFLICT(memo_id, resource_id) DO UPDATE
SET
updated_ts = EXCLUDED.updated_ts
RETURNING memo_id, resource_id, created_ts, updated_ts
`
var memoResourceRaw memoResourceRaw
if err := tx.QueryRowContext(ctx, query, args...).Scan(
&memoResourceRaw.MemoID,
&memoResourceRaw.ResourceID,
&memoResourceRaw.CreatedTs,
&memoResourceRaw.UpdatedTs,
); err != nil {
return nil, FormatError(err)
}
return &memoResourceRaw, nil
}
func deleteMemoResource(ctx context.Context, tx *sql.Tx, delete *api.MemoResourceDelete) error {
where, args := []string{}, []any{}
if v := delete.MemoID; v != nil {
where, args = append(where, "memo_id = ?"), append(args, *v)
}
if v := delete.ResourceID; v != nil {
where, args = append(where, "resource_id = ?"), append(args, *v)
}
stmt := `DELETE FROM memo_resource WHERE ` + strings.Join(where, " AND ")
result, err := tx.ExecContext(ctx, stmt, args...)
if err != nil {
return FormatError(err)
}
rows, _ := result.RowsAffected()
if rows == 0 {
return &common.Error{Code: common.NotFound, Err: fmt.Errorf("memo resource not found")}
}
return nil
} }
func vacuumMemoResource(ctx context.Context, tx *sql.Tx) error { func vacuumMemoResource(ctx context.Context, tx *sql.Tx) error {
......
...@@ -48,7 +48,7 @@ type DeleteResource struct { ...@@ -48,7 +48,7 @@ type DeleteResource struct {
ID int ID int
} }
func (s *Store) CreateResourceV1(ctx context.Context, create *Resource) (*Resource, error) { func (s *Store) CreateResource(ctx context.Context, create *Resource) (*Resource, error) {
tx, err := s.db.BeginTx(ctx, nil) tx, err := s.db.BeginTx(ctx, nil)
if err != nil { if err != nil {
return nil, FormatError(err) return nil, FormatError(err)
...@@ -98,6 +98,10 @@ func (s *Store) ListResources(ctx context.Context, find *FindResource) ([]*Resou ...@@ -98,6 +98,10 @@ func (s *Store) ListResources(ctx context.Context, find *FindResource) ([]*Resou
return nil, err return nil, err
} }
if err := tx.Commit(); err != nil {
return nil, err
}
return resources, nil return resources, nil
} }
...@@ -113,14 +117,14 @@ func (s *Store) GetResource(ctx context.Context, find *FindResource) (*Resource, ...@@ -113,14 +117,14 @@ func (s *Store) GetResource(ctx context.Context, find *FindResource) (*Resource,
return nil, err return nil, err
} }
if err := tx.Commit(); err != nil {
return nil, err
}
if len(resources) == 0 { if len(resources) == 0 {
return nil, nil return nil, nil
} }
if err := tx.Commit(); err != nil {
return nil, err
}
return resources[0], nil return resources[0], nil
} }
...@@ -174,7 +178,7 @@ func (s *Store) UpdateResource(ctx context.Context, update *UpdateResource) (*Re ...@@ -174,7 +178,7 @@ func (s *Store) UpdateResource(ctx context.Context, update *UpdateResource) (*Re
return &resource, nil return &resource, nil
} }
func (s *Store) DeleteResourceV1(ctx context.Context, delete *DeleteResource) error { func (s *Store) DeleteResource(ctx context.Context, delete *DeleteResource) error {
tx, err := s.db.BeginTx(ctx, nil) tx, err := s.db.BeginTx(ctx, nil)
if err != nil { if err != nil {
return FormatError(err) return FormatError(err)
......
...@@ -85,6 +85,10 @@ func (s *Store) ListShortcuts(ctx context.Context, find *FindShortcut) ([]*Short ...@@ -85,6 +85,10 @@ func (s *Store) ListShortcuts(ctx context.Context, find *FindShortcut) ([]*Short
return nil, err return nil, err
} }
if err := tx.Commit(); err != nil {
return nil, err
}
return list, nil return list, nil
} }
...@@ -104,6 +108,10 @@ func (s *Store) GetShortcut(ctx context.Context, find *FindShortcut) (*Shortcut, ...@@ -104,6 +108,10 @@ func (s *Store) GetShortcut(ctx context.Context, find *FindShortcut) (*Shortcut,
return nil, nil return nil, nil
} }
if err := tx.Commit(); err != nil {
return nil, err
}
shortcut := list[0] shortcut := list[0]
return shortcut, nil return shortcut, nil
} }
......
...@@ -69,6 +69,10 @@ func (s *Store) ListStorages(ctx context.Context, find *FindStorage) ([]*Storage ...@@ -69,6 +69,10 @@ func (s *Store) ListStorages(ctx context.Context, find *FindStorage) ([]*Storage
return nil, err return nil, err
} }
if err := tx.Commit(); err != nil {
return nil, err
}
return list, nil return list, nil
} }
...@@ -87,6 +91,10 @@ func (s *Store) GetStorage(ctx context.Context, find *FindStorage) (*Storage, er ...@@ -87,6 +91,10 @@ func (s *Store) GetStorage(ctx context.Context, find *FindStorage) (*Storage, er
return nil, nil return nil, nil
} }
if err := tx.Commit(); err != nil {
return nil, err
}
return list[0], nil return list[0], nil
} }
......
...@@ -57,6 +57,10 @@ func (s *Store) ListSystemSettings(ctx context.Context, find *FindSystemSetting) ...@@ -57,6 +57,10 @@ func (s *Store) ListSystemSettings(ctx context.Context, find *FindSystemSetting)
return nil, err return nil, err
} }
if err := tx.Commit(); err != nil {
return nil, err
}
for _, systemSettingMessage := range list { for _, systemSettingMessage := range list {
s.systemSettingCache.Store(systemSettingMessage.Name, systemSettingMessage) s.systemSettingCache.Store(systemSettingMessage.Name, systemSettingMessage)
} }
...@@ -85,6 +89,10 @@ func (s *Store) GetSystemSetting(ctx context.Context, find *FindSystemSetting) ( ...@@ -85,6 +89,10 @@ func (s *Store) GetSystemSetting(ctx context.Context, find *FindSystemSetting) (
return nil, nil return nil, nil
} }
if err := tx.Commit(); err != nil {
return nil, err
}
systemSettingMessage := list[0] systemSettingMessage := list[0]
s.systemSettingCache.Store(systemSettingMessage.Name, systemSettingMessage) s.systemSettingCache.Store(systemSettingMessage.Name, systemSettingMessage)
return systemSettingMessage, nil return systemSettingMessage, nil
......
...@@ -88,6 +88,10 @@ func (s *Store) ListTags(ctx context.Context, find *FindTag) ([]*Tag, error) { ...@@ -88,6 +88,10 @@ func (s *Store) ListTags(ctx context.Context, find *FindTag) ([]*Tag, error) {
return nil, err return nil, err
} }
if err := tx.Commit(); err != nil {
return nil, err
}
return list, nil return list, nil
} }
......
...@@ -120,6 +120,7 @@ func (s *Store) CreateUser(ctx context.Context, create *User) (*User, error) { ...@@ -120,6 +120,7 @@ func (s *Store) CreateUser(ctx context.Context, create *User) (*User, error) {
if err := tx.Commit(); err != nil { if err := tx.Commit(); err != nil {
return nil, err return nil, err
} }
user := create user := create
s.userCache.Store(user.ID, user) s.userCache.Store(user.ID, user)
return user, nil return user, nil
...@@ -202,6 +203,10 @@ func (s *Store) ListUsers(ctx context.Context, find *FindUser) ([]*User, error) ...@@ -202,6 +203,10 @@ func (s *Store) ListUsers(ctx context.Context, find *FindUser) ([]*User, error)
return nil, err return nil, err
} }
if err := tx.Commit(); err != nil {
return nil, err
}
for _, user := range list { for _, user := range list {
s.userCache.Store(user.ID, user) s.userCache.Store(user.ID, user)
} }
...@@ -228,6 +233,11 @@ func (s *Store) GetUser(ctx context.Context, find *FindUser) (*User, error) { ...@@ -228,6 +233,11 @@ func (s *Store) GetUser(ctx context.Context, find *FindUser) (*User, error) {
if len(list) == 0 { if len(list) == 0 {
return nil, nil return nil, nil
} }
if err := tx.Commit(); err != nil {
return nil, err
}
user := list[0] user := list[0]
s.userCache.Store(user.ID, user) s.userCache.Store(user.ID, user)
return user, nil return user, nil
......
...@@ -57,6 +57,10 @@ func (s *Store) ListUserSettings(ctx context.Context, find *FindUserSetting) ([] ...@@ -57,6 +57,10 @@ func (s *Store) ListUserSettings(ctx context.Context, find *FindUserSetting) ([]
return nil, err return nil, err
} }
if err := tx.Commit(); err != nil {
return nil, err
}
for _, userSetting := range userSettingList { for _, userSetting := range userSettingList {
s.userSettingCache.Store(getUserSettingCacheKey(userSetting.UserID, userSetting.Key), userSetting) s.userSettingCache.Store(getUserSettingCacheKey(userSetting.UserID, userSetting.Key), userSetting)
} }
...@@ -85,6 +89,10 @@ func (s *Store) GetUserSetting(ctx context.Context, find *FindUserSetting) (*Use ...@@ -85,6 +89,10 @@ func (s *Store) GetUserSetting(ctx context.Context, find *FindUserSetting) (*Use
return nil, nil return nil, nil
} }
if err := tx.Commit(); err != nil {
return nil, err
}
userSetting := list[0] userSetting := list[0]
s.userSettingCache.Store(getUserSettingCacheKey(userSetting.UserID, userSetting.Key), userSetting) s.userSettingCache.Store(getUserSettingCacheKey(userSetting.UserID, userSetting.Key), userSetting)
return userSetting, nil return userSetting, nil
......
...@@ -9,7 +9,6 @@ import ( ...@@ -9,7 +9,6 @@ import (
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"github.com/usememos/memos/api"
apiv1 "github.com/usememos/memos/api/v1" apiv1 "github.com/usememos/memos/api/v1"
) )
...@@ -26,17 +25,17 @@ func TestMemoRelationServer(t *testing.T) { ...@@ -26,17 +25,17 @@ func TestMemoRelationServer(t *testing.T) {
user, err := s.postAuthSignup(signup) user, err := s.postAuthSignup(signup)
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, signup.Username, user.Username) require.Equal(t, signup.Username, user.Username)
memo, err := s.postMemoCreate(&api.CreateMemoRequest{ memo, err := s.postMemoCreate(&apiv1.CreateMemoRequest{
Content: "test memo", Content: "test memo",
}) })
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, "test memo", memo.Content) require.Equal(t, "test memo", memo.Content)
memo2, err := s.postMemoCreate(&api.CreateMemoRequest{ memo2, err := s.postMemoCreate(&apiv1.CreateMemoRequest{
Content: "test memo2", Content: "test memo2",
RelationList: []*api.MemoRelationUpsert{ RelationList: []*apiv1.UpsertMemoRelationRequest{
{ {
RelatedMemoID: memo.ID, RelatedMemoID: memo.ID,
Type: api.MemoRelationReference, Type: apiv1.MemoRelationReference,
}, },
}, },
}) })
...@@ -46,14 +45,14 @@ func TestMemoRelationServer(t *testing.T) { ...@@ -46,14 +45,14 @@ func TestMemoRelationServer(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
require.Len(t, memoList, 2) require.Len(t, memoList, 2)
require.Len(t, memo2.RelationList, 1) require.Len(t, memo2.RelationList, 1)
err = s.deleteMemoRelation(memo2.ID, memo.ID, api.MemoRelationReference) err = s.deleteMemoRelation(memo2.ID, memo.ID, apiv1.MemoRelationReference)
require.NoError(t, err) require.NoError(t, err)
memo2, err = s.getMemo(memo2.ID) memo2, err = s.getMemo(memo2.ID)
require.NoError(t, err) require.NoError(t, err)
require.Len(t, memo2.RelationList, 0) require.Len(t, memo2.RelationList, 0)
memoRelation, err := s.postMemoRelationUpsert(memo2.ID, &api.MemoRelationUpsert{ memoRelation, err := s.postMemoRelationUpsert(memo2.ID, &apiv1.UpsertMemoRelationRequest{
RelatedMemoID: memo.ID, RelatedMemoID: memo.ID,
Type: api.MemoRelationReference, Type: apiv1.MemoRelationReference,
}) })
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, memo.ID, memoRelation.RelatedMemoID) require.Equal(t, memo.ID, memoRelation.RelatedMemoID)
...@@ -62,13 +61,13 @@ func TestMemoRelationServer(t *testing.T) { ...@@ -62,13 +61,13 @@ func TestMemoRelationServer(t *testing.T) {
require.Len(t, memo2.RelationList, 1) require.Len(t, memo2.RelationList, 1)
} }
func (s *TestingServer) postMemoRelationUpsert(memoID int, memoRelationUpsert *api.MemoRelationUpsert) (*api.MemoRelation, error) { func (s *TestingServer) postMemoRelationUpsert(memoID int, memoRelationUpsert *apiv1.UpsertMemoRelationRequest) (*apiv1.MemoRelation, error) {
rawData, err := json.Marshal(&memoRelationUpsert) rawData, err := json.Marshal(&memoRelationUpsert)
if err != nil { if err != nil {
return nil, errors.Wrap(err, "failed to marshal memo relation upsert") return nil, errors.Wrap(err, "failed to marshal memo relation upsert")
} }
reader := bytes.NewReader(rawData) reader := bytes.NewReader(rawData)
body, err := s.post(fmt.Sprintf("/api/memo/%d/relation", memoID), reader, nil) body, err := s.post(fmt.Sprintf("/api/v1/memo/%d/relation", memoID), reader, nil)
if err != nil { if err != nil {
return nil, err return nil, err
} }
...@@ -79,17 +78,14 @@ func (s *TestingServer) postMemoRelationUpsert(memoID int, memoRelationUpsert *a ...@@ -79,17 +78,14 @@ func (s *TestingServer) postMemoRelationUpsert(memoID int, memoRelationUpsert *a
return nil, errors.Wrap(err, "fail to read response body") return nil, errors.Wrap(err, "fail to read response body")
} }
type MemoCreateResponse struct { memoRelation := &apiv1.MemoRelation{}
Data *api.MemoRelation `json:"data"` if err = json.Unmarshal(buf.Bytes(), memoRelation); err != nil {
}
res := new(MemoCreateResponse)
if err = json.Unmarshal(buf.Bytes(), res); err != nil {
return nil, errors.Wrap(err, "fail to unmarshal post memo relation upsert response") return nil, errors.Wrap(err, "fail to unmarshal post memo relation upsert response")
} }
return res.Data, nil return memoRelation, nil
} }
func (s *TestingServer) deleteMemoRelation(memoID int, relatedMemoID int, relationType api.MemoRelationType) error { func (s *TestingServer) deleteMemoRelation(memoID int, relatedMemoID int, relationType apiv1.MemoRelationType) error {
_, err := s.delete(fmt.Sprintf("/api/memo/%d/relation/%d/type/%s", memoID, relatedMemoID, relationType), nil) _, err := s.delete(fmt.Sprintf("/api/v1/memo/%d/relation/%d/type/%s", memoID, relatedMemoID, relationType), nil)
return err return err
} }
...@@ -9,7 +9,6 @@ import ( ...@@ -9,7 +9,6 @@ import (
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"github.com/usememos/memos/api"
apiv1 "github.com/usememos/memos/api/v1" apiv1 "github.com/usememos/memos/api/v1"
) )
...@@ -26,7 +25,7 @@ func TestMemoServer(t *testing.T) { ...@@ -26,7 +25,7 @@ func TestMemoServer(t *testing.T) {
user, err := s.postAuthSignup(signup) user, err := s.postAuthSignup(signup)
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, signup.Username, user.Username) require.Equal(t, signup.Username, user.Username)
memo, err := s.postMemoCreate(&api.CreateMemoRequest{ memo, err := s.postMemoCreate(&apiv1.CreateMemoRequest{
Content: "test memo", Content: "test memo",
}) })
require.NoError(t, err) require.NoError(t, err)
...@@ -35,20 +34,18 @@ func TestMemoServer(t *testing.T) { ...@@ -35,20 +34,18 @@ func TestMemoServer(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
require.Len(t, memoList, 1) require.Len(t, memoList, 1)
updatedContent := "updated memo" updatedContent := "updated memo"
memo, err = s.patchMemo(&api.PatchMemoRequest{ memo, err = s.patchMemo(&apiv1.PatchMemoRequest{
ID: memo.ID, ID: memo.ID,
Content: &updatedContent, Content: &updatedContent,
}) })
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, updatedContent, memo.Content) require.Equal(t, updatedContent, memo.Content)
require.Equal(t, false, memo.Pinned) require.Equal(t, false, memo.Pinned)
memo, err = s.postMemosOrganizer(&api.MemoOrganizerUpsert{ _, err = s.postMemoOrganizer(memo.ID, &apiv1.UpsertMemoOrganizerRequest{
MemoID: memo.ID,
UserID: user.ID,
Pinned: true, Pinned: true,
}) })
require.NoError(t, err) require.NoError(t, err)
memo, err = s.patchMemo(&api.PatchMemoRequest{ memo, err = s.patchMemo(&apiv1.PatchMemoRequest{
ID: memo.ID, ID: memo.ID,
Content: &updatedContent, Content: &updatedContent,
}) })
...@@ -62,8 +59,8 @@ func TestMemoServer(t *testing.T) { ...@@ -62,8 +59,8 @@ func TestMemoServer(t *testing.T) {
require.Len(t, memoList, 0) require.Len(t, memoList, 0)
} }
func (s *TestingServer) getMemo(memoID int) (*api.MemoResponse, error) { func (s *TestingServer) getMemo(memoID int) (*apiv1.Memo, error) {
body, err := s.get(fmt.Sprintf("/api/memo/%d", memoID), nil) body, err := s.get(fmt.Sprintf("/api/v1/memo/%d", memoID), nil)
if err != nil { if err != nil {
return nil, err return nil, err
} }
...@@ -74,18 +71,15 @@ func (s *TestingServer) getMemo(memoID int) (*api.MemoResponse, error) { ...@@ -74,18 +71,15 @@ func (s *TestingServer) getMemo(memoID int) (*api.MemoResponse, error) {
return nil, errors.Wrap(err, "fail to read response body") return nil, errors.Wrap(err, "fail to read response body")
} }
type MemoCreateResponse struct { memo := &apiv1.Memo{}
Data *api.MemoResponse `json:"data"` if err = json.Unmarshal(buf.Bytes(), memo); err != nil {
}
res := new(MemoCreateResponse)
if err = json.Unmarshal(buf.Bytes(), res); err != nil {
return nil, errors.Wrap(err, "fail to unmarshal get memo response") return nil, errors.Wrap(err, "fail to unmarshal get memo response")
} }
return res.Data, nil return memo, nil
} }
func (s *TestingServer) getMemoList() ([]*api.MemoResponse, error) { func (s *TestingServer) getMemoList() ([]*apiv1.Memo, error) {
body, err := s.get("/api/memo", nil) body, err := s.get("/api/v1/memo", nil)
if err != nil { if err != nil {
return nil, err return nil, err
} }
...@@ -96,23 +90,20 @@ func (s *TestingServer) getMemoList() ([]*api.MemoResponse, error) { ...@@ -96,23 +90,20 @@ func (s *TestingServer) getMemoList() ([]*api.MemoResponse, error) {
return nil, errors.Wrap(err, "fail to read response body") return nil, errors.Wrap(err, "fail to read response body")
} }
type MemoCreateResponse struct { memoList := []*apiv1.Memo{}
Data []*api.MemoResponse `json:"data"` if err = json.Unmarshal(buf.Bytes(), &memoList); err != nil {
}
res := new(MemoCreateResponse)
if err = json.Unmarshal(buf.Bytes(), res); err != nil {
return nil, errors.Wrap(err, "fail to unmarshal get memo list response") return nil, errors.Wrap(err, "fail to unmarshal get memo list response")
} }
return res.Data, nil return memoList, nil
} }
func (s *TestingServer) postMemoCreate(memoCreate *api.CreateMemoRequest) (*api.MemoResponse, error) { func (s *TestingServer) postMemoCreate(memoCreate *apiv1.CreateMemoRequest) (*apiv1.Memo, error) {
rawData, err := json.Marshal(&memoCreate) rawData, err := json.Marshal(&memoCreate)
if err != nil { if err != nil {
return nil, errors.Wrap(err, "failed to marshal memo create") return nil, errors.Wrap(err, "failed to marshal memo create")
} }
reader := bytes.NewReader(rawData) reader := bytes.NewReader(rawData)
body, err := s.post("/api/memo", reader, nil) body, err := s.post("/api/v1/memo", reader, nil)
if err != nil { if err != nil {
return nil, err return nil, err
} }
...@@ -123,23 +114,20 @@ func (s *TestingServer) postMemoCreate(memoCreate *api.CreateMemoRequest) (*api. ...@@ -123,23 +114,20 @@ func (s *TestingServer) postMemoCreate(memoCreate *api.CreateMemoRequest) (*api.
return nil, errors.Wrap(err, "fail to read response body") return nil, errors.Wrap(err, "fail to read response body")
} }
type MemoCreateResponse struct { memo := &apiv1.Memo{}
Data *api.MemoResponse `json:"data"` if err = json.Unmarshal(buf.Bytes(), memo); err != nil {
}
res := new(MemoCreateResponse)
if err = json.Unmarshal(buf.Bytes(), res); err != nil {
return nil, errors.Wrap(err, "fail to unmarshal post memo create response") return nil, errors.Wrap(err, "fail to unmarshal post memo create response")
} }
return res.Data, nil return memo, nil
} }
func (s *TestingServer) patchMemo(memoPatch *api.PatchMemoRequest) (*api.MemoResponse, error) { func (s *TestingServer) patchMemo(memoPatch *apiv1.PatchMemoRequest) (*apiv1.Memo, error) {
rawData, err := json.Marshal(&memoPatch) rawData, err := json.Marshal(&memoPatch)
if err != nil { if err != nil {
return nil, errors.Wrap(err, "failed to marshal memo patch") return nil, errors.Wrap(err, "failed to marshal memo patch")
} }
reader := bytes.NewReader(rawData) reader := bytes.NewReader(rawData)
body, err := s.patch(fmt.Sprintf("/api/memo/%d", memoPatch.ID), reader, nil) body, err := s.patch(fmt.Sprintf("/api/v1/memo/%d", memoPatch.ID), reader, nil)
if err != nil { if err != nil {
return nil, err return nil, err
} }
...@@ -150,28 +138,25 @@ func (s *TestingServer) patchMemo(memoPatch *api.PatchMemoRequest) (*api.MemoRes ...@@ -150,28 +138,25 @@ func (s *TestingServer) patchMemo(memoPatch *api.PatchMemoRequest) (*api.MemoRes
return nil, errors.Wrap(err, "fail to read response body") return nil, errors.Wrap(err, "fail to read response body")
} }
type MemoPatchResponse struct { memo := &apiv1.Memo{}
Data *api.MemoResponse `json:"data"` if err = json.Unmarshal(buf.Bytes(), memo); err != nil {
}
res := new(MemoPatchResponse)
if err = json.Unmarshal(buf.Bytes(), res); err != nil {
return nil, errors.Wrap(err, "fail to unmarshal patch memo response") return nil, errors.Wrap(err, "fail to unmarshal patch memo response")
} }
return res.Data, nil return memo, nil
} }
func (s *TestingServer) deleteMemo(memoID int) error { func (s *TestingServer) deleteMemo(memoID int) error {
_, err := s.delete(fmt.Sprintf("/api/memo/%d", memoID), nil) _, err := s.delete(fmt.Sprintf("/api/v1/memo/%d", memoID), nil)
return err return err
} }
func (s *TestingServer) postMemosOrganizer(memosOrganizer *api.MemoOrganizerUpsert) (*api.MemoResponse, error) { func (s *TestingServer) postMemoOrganizer(memoID int, memosOrganizer *apiv1.UpsertMemoOrganizerRequest) (*apiv1.Memo, error) {
rawData, err := json.Marshal(&memosOrganizer) rawData, err := json.Marshal(&memosOrganizer)
if err != nil { if err != nil {
return nil, errors.Wrap(err, "failed to marshal memos organizer") return nil, errors.Wrap(err, "failed to marshal memos organizer")
} }
reader := bytes.NewReader(rawData) reader := bytes.NewReader(rawData)
body, err := s.post(fmt.Sprintf("/api/memo/%d/organizer", memosOrganizer.MemoID), reader, nil) body, err := s.post(fmt.Sprintf("/api/v1/memo/%d/organizer", memoID), reader, nil)
if err != nil { if err != nil {
return nil, err return nil, err
} }
...@@ -182,12 +167,9 @@ func (s *TestingServer) postMemosOrganizer(memosOrganizer *api.MemoOrganizerUpse ...@@ -182,12 +167,9 @@ func (s *TestingServer) postMemosOrganizer(memosOrganizer *api.MemoOrganizerUpse
return nil, errors.Wrap(err, "fail to read response body") return nil, errors.Wrap(err, "fail to read response body")
} }
type MemoOrganizerResponse struct { memo := &apiv1.Memo{}
Data *api.MemoResponse `json:"data"` if err = json.Unmarshal(buf.Bytes(), memo); err != nil {
}
res := new(MemoOrganizerResponse)
if err = json.Unmarshal(buf.Bytes(), res); err != nil {
return nil, errors.Wrap(err, "fail to unmarshal organizer memo create response") return nil, errors.Wrap(err, "fail to unmarshal organizer memo create response")
} }
return res.Data, err return memo, err
} }
...@@ -13,7 +13,7 @@ func TestMemoRelationStore(t *testing.T) { ...@@ -13,7 +13,7 @@ func TestMemoRelationStore(t *testing.T) {
ts := NewTestingStore(ctx, t) ts := NewTestingStore(ctx, t)
user, err := createTestingHostUser(ctx, ts) user, err := createTestingHostUser(ctx, ts)
require.NoError(t, err) require.NoError(t, err)
memoCreate := &store.MemoMessage{ memoCreate := &store.Memo{
CreatorID: user.ID, CreatorID: user.ID,
Content: "test_content", Content: "test_content",
Visibility: store.Public, Visibility: store.Public,
...@@ -21,7 +21,7 @@ func TestMemoRelationStore(t *testing.T) { ...@@ -21,7 +21,7 @@ func TestMemoRelationStore(t *testing.T) {
memo, err := ts.CreateMemo(ctx, memoCreate) memo, err := ts.CreateMemo(ctx, memoCreate)
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, memoCreate.Content, memo.Content) require.Equal(t, memoCreate.Content, memo.Content)
memo2Create := &store.MemoMessage{ memo2Create := &store.Memo{
CreatorID: user.ID, CreatorID: user.ID,
Content: "test_content_2", Content: "test_content_2",
Visibility: store.Public, Visibility: store.Public,
...@@ -29,14 +29,14 @@ func TestMemoRelationStore(t *testing.T) { ...@@ -29,14 +29,14 @@ func TestMemoRelationStore(t *testing.T) {
memo2, err := ts.CreateMemo(ctx, memo2Create) memo2, err := ts.CreateMemo(ctx, memo2Create)
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, memo2Create.Content, memo2.Content) require.Equal(t, memo2Create.Content, memo2.Content)
memoRelationMessage := &store.MemoRelationMessage{ memoRelationMessage := &store.MemoRelation{
MemoID: memo.ID, MemoID: memo.ID,
RelatedMemoID: memo2.ID, RelatedMemoID: memo2.ID,
Type: store.MemoRelationReference, Type: store.MemoRelationReference,
} }
_, err = ts.UpsertMemoRelation(ctx, memoRelationMessage) _, err = ts.UpsertMemoRelation(ctx, memoRelationMessage)
require.NoError(t, err) require.NoError(t, err)
memoRelation, err := ts.ListMemoRelations(ctx, &store.FindMemoRelationMessage{ memoRelation, err := ts.ListMemoRelations(ctx, &store.FindMemoRelation{
MemoID: &memo.ID, MemoID: &memo.ID,
}) })
require.NoError(t, err) require.NoError(t, err)
...@@ -44,11 +44,11 @@ func TestMemoRelationStore(t *testing.T) { ...@@ -44,11 +44,11 @@ func TestMemoRelationStore(t *testing.T) {
require.Equal(t, memo2.ID, memoRelation[0].RelatedMemoID) require.Equal(t, memo2.ID, memoRelation[0].RelatedMemoID)
require.Equal(t, memo.ID, memoRelation[0].MemoID) require.Equal(t, memo.ID, memoRelation[0].MemoID)
require.Equal(t, store.MemoRelationReference, memoRelation[0].Type) require.Equal(t, store.MemoRelationReference, memoRelation[0].Type)
err = ts.DeleteMemo(ctx, &store.DeleteMemoMessage{ err = ts.DeleteMemo(ctx, &store.DeleteMemo{
ID: memo2.ID, ID: memo2.ID,
}) })
require.NoError(t, err) require.NoError(t, err)
memoRelation, err = ts.ListMemoRelations(ctx, &store.FindMemoRelationMessage{ memoRelation, err = ts.ListMemoRelations(ctx, &store.FindMemoRelation{
MemoID: &memo.ID, MemoID: &memo.ID,
}) })
require.NoError(t, err) require.NoError(t, err)
......
...@@ -13,7 +13,7 @@ func TestMemoStore(t *testing.T) { ...@@ -13,7 +13,7 @@ func TestMemoStore(t *testing.T) {
ts := NewTestingStore(ctx, t) ts := NewTestingStore(ctx, t)
user, err := createTestingHostUser(ctx, ts) user, err := createTestingHostUser(ctx, ts)
require.NoError(t, err) require.NoError(t, err)
memoCreate := &store.MemoMessage{ memoCreate := &store.Memo{
CreatorID: user.ID, CreatorID: user.ID,
Content: "test_content", Content: "test_content",
Visibility: store.Public, Visibility: store.Public,
...@@ -22,23 +22,23 @@ func TestMemoStore(t *testing.T) { ...@@ -22,23 +22,23 @@ func TestMemoStore(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, memoCreate.Content, memo.Content) require.Equal(t, memoCreate.Content, memo.Content)
memoPatchContent := "test_content_2" memoPatchContent := "test_content_2"
memoPatch := &store.UpdateMemoMessage{ memoPatch := &store.UpdateMemo{
ID: memo.ID, ID: memo.ID,
Content: &memoPatchContent, Content: &memoPatchContent,
} }
err = ts.UpdateMemo(ctx, memoPatch) err = ts.UpdateMemo(ctx, memoPatch)
require.NoError(t, err) require.NoError(t, err)
memo, err = ts.GetMemo(ctx, &store.FindMemoMessage{ memo, err = ts.GetMemo(ctx, &store.FindMemo{
ID: &memo.ID, ID: &memo.ID,
}) })
require.NoError(t, err) require.NoError(t, err)
memoList, err := ts.ListMemos(ctx, &store.FindMemoMessage{ memoList, err := ts.ListMemos(ctx, &store.FindMemo{
CreatorID: &user.ID, CreatorID: &user.ID,
}) })
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, 1, len(memoList)) require.Equal(t, 1, len(memoList))
require.Equal(t, memo, memoList[0]) require.Equal(t, memo, memoList[0])
err = ts.DeleteMemo(ctx, &store.DeleteMemoMessage{ err = ts.DeleteMemo(ctx, &store.DeleteMemo{
ID: memo.ID, ID: memo.ID,
}) })
require.NoError(t, err) require.NoError(t, err)
......
...@@ -11,7 +11,7 @@ import ( ...@@ -11,7 +11,7 @@ import (
func TestResourceStore(t *testing.T) { func TestResourceStore(t *testing.T) {
ctx := context.Background() ctx := context.Background()
ts := NewTestingStore(ctx, t) ts := NewTestingStore(ctx, t)
_, err := ts.CreateResourceV1(ctx, &store.Resource{ _, err := ts.CreateResource(ctx, &store.Resource{
CreatorID: 101, CreatorID: 101,
Filename: "test.epub", Filename: "test.epub",
Blob: []byte("test"), Blob: []byte("test"),
...@@ -49,11 +49,11 @@ func TestResourceStore(t *testing.T) { ...@@ -49,11 +49,11 @@ func TestResourceStore(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
require.Nil(t, notFoundResource) require.Nil(t, notFoundResource)
err = ts.DeleteResourceV1(ctx, &store.DeleteResource{ err = ts.DeleteResource(ctx, &store.DeleteResource{
ID: 1, ID: 1,
}) })
require.NoError(t, err) require.NoError(t, err)
err = ts.DeleteResourceV1(ctx, &store.DeleteResource{ err = ts.DeleteResource(ctx, &store.DeleteResource{
ID: 2, ID: 2,
}) })
require.NoError(t, err) require.NoError(t, err)
......
...@@ -11,7 +11,7 @@ const MyAccountSection = () => { ...@@ -11,7 +11,7 @@ const MyAccountSection = () => {
const { t } = useTranslation(); const { t } = useTranslation();
const userStore = useUserStore(); const userStore = useUserStore();
const user = userStore.state.user as User; const user = userStore.state.user as User;
const openAPIRoute = `${window.location.origin}/api/memo?openId=${user.openId}`; const openAPIRoute = `${window.location.origin}/api/v1/memo?openId=${user.openId}`;
const handleResetOpenIdBtnClick = async () => { const handleResetOpenIdBtnClick = async () => {
showCommonDialog({ showCommonDialog({
......
...@@ -52,7 +52,7 @@ const ShareMemoDialog: React.FC<Props> = (props: Props) => { ...@@ -52,7 +52,7 @@ const ShareMemoDialog: React.FC<Props> = (props: Props) => {
useEffect(() => { useEffect(() => {
getMemoStats(user.id) getMemoStats(user.id)
.then(({ data: { data } }) => { .then(({ data }) => {
setPartialState({ setPartialState({
memoAmount: data.length, memoAmount: data.length,
}); });
......
...@@ -57,7 +57,7 @@ const UsageHeatMap = () => { ...@@ -57,7 +57,7 @@ const UsageHeatMap = () => {
useEffect(() => { useEffect(() => {
getMemoStats(currentUserId) getMemoStats(currentUserId)
.then(({ data: { data } }) => { .then(({ data }) => {
setMemoAmount(data.length); setMemoAmount(data.length);
const newStat: DailyUsageStat[] = getInitialUsageStat(usedDaysAmount, beginDayTimestamp); const newStat: DailyUsageStat[] = getInitialUsageStat(usedDaysAmount, beginDayTimestamp);
for (const record of data) { for (const record of data) {
......
...@@ -26,7 +26,7 @@ const DatePicker: React.FC<DatePickerProps> = (props: DatePickerProps) => { ...@@ -26,7 +26,7 @@ const DatePicker: React.FC<DatePickerProps> = (props: DatePickerProps) => {
}, [datestamp]); }, [datestamp]);
useEffect(() => { useEffect(() => {
getMemoStats(currentUserId).then(({ data: { data } }) => { getMemoStats(currentUserId).then(({ data }) => {
const m = new Map(); const m = new Map();
for (const record of data) { for (const record of data) {
const date = getDateStampByDate(record * 1000); const date = getDateStampByDate(record * 1000);
......
import axios from "axios"; import axios from "axios";
type ResponseObject<T> = {
data: T;
error?: string;
message?: string;
};
export function getSystemStatus() { export function getSystemStatus() {
return axios.get<SystemStatus>("/api/v1/status"); return axios.get<SystemStatus>("/api/v1/status");
} }
...@@ -85,7 +79,7 @@ export function getAllMemos(memoFind?: MemoFind) { ...@@ -85,7 +79,7 @@ export function getAllMemos(memoFind?: MemoFind) {
queryList.push(`limit=${memoFind.limit}`); queryList.push(`limit=${memoFind.limit}`);
} }
return axios.get<ResponseObject<Memo[]>>(`/api/memo/all?${queryList.join("&")}`); return axios.get<Memo[]>(`/api/v1/memo/all?${queryList.join("&")}`);
} }
export function getMemoList(memoFind?: MemoFind) { export function getMemoList(memoFind?: MemoFind) {
...@@ -105,39 +99,39 @@ export function getMemoList(memoFind?: MemoFind) { ...@@ -105,39 +99,39 @@ export function getMemoList(memoFind?: MemoFind) {
if (memoFind?.limit) { if (memoFind?.limit) {
queryList.push(`limit=${memoFind.limit}`); queryList.push(`limit=${memoFind.limit}`);
} }
return axios.get<ResponseObject<Memo[]>>(`/api/memo?${queryList.join("&")}`); return axios.get<Memo[]>(`/api/v1/memo?${queryList.join("&")}`);
} }
export function getMemoStats(userId: UserId) { export function getMemoStats(userId: UserId) {
return axios.get<ResponseObject<number[]>>(`/api/memo/stats?creatorId=${userId}`); return axios.get<number[]>(`/api/v1/memo/stats?creatorId=${userId}`);
} }
export function getMemoById(id: MemoId) { export function getMemoById(id: MemoId) {
return axios.get<ResponseObject<Memo>>(`/api/memo/${id}`); return axios.get<Memo>(`/api/v1/memo/${id}`);
} }
export function createMemo(memoCreate: MemoCreate) { export function createMemo(memoCreate: MemoCreate) {
return axios.post<ResponseObject<Memo>>("/api/memo", memoCreate); return axios.post<Memo>("/api/v1/memo", memoCreate);
} }
export function patchMemo(memoPatch: MemoPatch) { export function patchMemo(memoPatch: MemoPatch) {
return axios.patch<ResponseObject<Memo>>(`/api/memo/${memoPatch.id}`, memoPatch); return axios.patch<Memo>(`/api/v1/memo/${memoPatch.id}`, memoPatch);
} }
export function pinMemo(memoId: MemoId) { export function pinMemo(memoId: MemoId) {
return axios.post(`/api/memo/${memoId}/organizer`, { return axios.post(`/api/v1/memo/${memoId}/organizer`, {
pinned: true, pinned: true,
}); });
} }
export function unpinMemo(memoId: MemoId) { export function unpinMemo(memoId: MemoId) {
return axios.post(`/api/memo/${memoId}/organizer`, { return axios.post(`/api/v1/memo/${memoId}/organizer`, {
pinned: false, pinned: false,
}); });
} }
export function deleteMemo(memoId: MemoId) { export function deleteMemo(memoId: MemoId) {
return axios.delete(`/api/memo/${memoId}`); return axios.delete(`/api/v1/memo/${memoId}`);
} }
export function getShortcutList(shortcutFind?: ShortcutFind) { export function getShortcutList(shortcutFind?: ShortcutFind) {
...@@ -192,17 +186,17 @@ export function deleteResourceById(id: ResourceId) { ...@@ -192,17 +186,17 @@ export function deleteResourceById(id: ResourceId) {
} }
export function getMemoResourceList(memoId: MemoId) { export function getMemoResourceList(memoId: MemoId) {
return axios.get<ResponseObject<Resource[]>>(`/api/memo/${memoId}/resource`); return axios.get<Resource[]>(`/api/v1/memo/${memoId}/resource`);
} }
export function upsertMemoResource(memoId: MemoId, resourceId: ResourceId) { export function upsertMemoResource(memoId: MemoId, resourceId: ResourceId) {
return axios.post(`/api/memo/${memoId}/resource`, { return axios.post(`/api/v1/memo/${memoId}/resource`, {
resourceId, resourceId,
}); });
} }
export function deleteMemoResource(memoId: MemoId, resourceId: ResourceId) { export function deleteMemoResource(memoId: MemoId, resourceId: ResourceId) {
return axios.delete(`/api/memo/${memoId}/resource/${resourceId}`); return axios.delete(`/api/v1/memo/${memoId}/resource/${resourceId}`);
} }
export function getTagList(tagFind?: TagFind) { export function getTagList(tagFind?: TagFind) {
......
...@@ -21,7 +21,7 @@ export const useMemoStore = () => { ...@@ -21,7 +21,7 @@ export const useMemoStore = () => {
const memoCacheStore = useMemoCacheStore(); const memoCacheStore = useMemoCacheStore();
const fetchMemoById = async (memoId: MemoId) => { const fetchMemoById = async (memoId: MemoId) => {
const { data } = (await api.getMemoById(memoId)).data; const { data } = await api.getMemoById(memoId);
const memo = convertResponseModelMemo(data); const memo = convertResponseModelMemo(data);
return memo; return memo;
...@@ -42,7 +42,7 @@ export const useMemoStore = () => { ...@@ -42,7 +42,7 @@ export const useMemoStore = () => {
if (userStore.isVisitorMode()) { if (userStore.isVisitorMode()) {
memoFind.creatorId = userStore.getUserIdFromPath(); memoFind.creatorId = userStore.getUserIdFromPath();
} }
const { data } = (await api.getMemoList(memoFind)).data; const { data } = await api.getMemoList(memoFind);
const fetchedMemos = data.map((m) => convertResponseModelMemo(m)); const fetchedMemos = data.map((m) => convertResponseModelMemo(m));
store.dispatch(upsertMemos(fetchedMemos)); store.dispatch(upsertMemos(fetchedMemos));
store.dispatch(setIsFetching(false)); store.dispatch(setIsFetching(false));
...@@ -60,7 +60,7 @@ export const useMemoStore = () => { ...@@ -60,7 +60,7 @@ export const useMemoStore = () => {
offset, offset,
}; };
const { data } = (await api.getAllMemos(memoFind)).data; const { data } = await api.getAllMemos(memoFind);
const fetchedMemos = data.map((m) => convertResponseModelMemo(m)); const fetchedMemos = data.map((m) => convertResponseModelMemo(m));
for (const m of fetchedMemos) { for (const m of fetchedMemos) {
...@@ -76,7 +76,7 @@ export const useMemoStore = () => { ...@@ -76,7 +76,7 @@ export const useMemoStore = () => {
if (userStore.isVisitorMode()) { if (userStore.isVisitorMode()) {
memoFind.creatorId = userStore.getUserIdFromPath(); memoFind.creatorId = userStore.getUserIdFromPath();
} }
const { data } = (await api.getMemoList(memoFind)).data; const { data } = await api.getMemoList(memoFind);
const archivedMemos = data.map((m) => { const archivedMemos = data.map((m) => {
return convertResponseModelMemo(m); return convertResponseModelMemo(m);
}); });
...@@ -97,14 +97,14 @@ export const useMemoStore = () => { ...@@ -97,14 +97,14 @@ export const useMemoStore = () => {
return state.memos.filter((m) => m.content.match(regex)); return state.memos.filter((m) => m.content.match(regex));
}, },
createMemo: async (memoCreate: MemoCreate) => { createMemo: async (memoCreate: MemoCreate) => {
const { data } = (await api.createMemo(memoCreate)).data; const { data } = await api.createMemo(memoCreate);
const memo = convertResponseModelMemo(data); const memo = convertResponseModelMemo(data);
store.dispatch(createMemo(memo)); store.dispatch(createMemo(memo));
memoCacheStore.setMemoCache(memo); memoCacheStore.setMemoCache(memo);
return memo; return memo;
}, },
patchMemo: async (memoPatch: MemoPatch): Promise<Memo> => { patchMemo: async (memoPatch: MemoPatch): Promise<Memo> => {
const { data } = (await api.patchMemo(memoPatch)).data; const { data } = await api.patchMemo(memoPatch);
const memo = convertResponseModelMemo(data); const memo = convertResponseModelMemo(data);
store.dispatch(patchMemo(omit(memo, "pinned"))); store.dispatch(patchMemo(omit(memo, "pinned")));
memoCacheStore.setMemoCache(memo); memoCacheStore.setMemoCache(memo);
......
...@@ -12,7 +12,7 @@ export const useMemoCacheStore = create( ...@@ -12,7 +12,7 @@ export const useMemoCacheStore = create(
return memo; return memo;
} }
const { data } = (await api.getMemoById(memoId)).data; const { data } = await api.getMemoById(memoId);
const formatedMemo = convertResponseModelMemo(data); const formatedMemo = convertResponseModelMemo(data);
set((state) => { set((state) => {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment