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
if err != nil {
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,
Type: string(ActivityUserAuthSignIn),
Level: string(ActivityInfo),
......@@ -256,7 +256,7 @@ func (s *APIV1Service) createAuthSignUpActivity(c echo.Context, user *store.User
if err != nil {
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,
Type: string(ActivityUserAuthSignUp),
Level: string(ActivityInfo),
......
package server
package v1
import (
"fmt"
......@@ -9,7 +9,7 @@ import (
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 {
urlStr := c.QueryParam("url")
if urlStr == "" {
......@@ -23,7 +23,7 @@ func registerGetterPublicRoutes(g *echo.Group) {
if err != nil {
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 {
......
......@@ -82,7 +82,7 @@ func JWTMiddleware(server *APIV1Service, next echo.HandlerFunc, secret string) e
}
// 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)
}
......@@ -93,7 +93,7 @@ func JWTMiddleware(server *APIV1Service, next echo.HandlerFunc, secret string) e
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 {
if common.HasPrefixes(path, "/api/v1/memo") && method == http.MethodGet {
return next(c)
}
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 (
"encoding/json"
......@@ -6,13 +6,29 @@ import (
"net/http"
"strconv"
"github.com/usememos/memos/api"
"github.com/labstack/echo/v4"
"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 {
ctx := c.Request().Context()
memoID, err := strconv.Atoi(c.Param("memoId"))
......@@ -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)
}
memoRelationUpsert := &api.MemoRelationUpsert{}
if err := json.NewDecoder(c.Request().Body).Decode(memoRelationUpsert); err != nil {
request := &UpsertMemoRelationRequest{}
if err := json.NewDecoder(c.Request().Body).Decode(request); err != nil {
return echo.NewHTTPError(http.StatusBadRequest, "Malformatted post memo relation request").SetInternal(err)
}
memoRelation, err := s.Store.UpsertMemoRelation(ctx, &store.MemoRelationMessage{
memoRelation, err := s.Store.UpsertMemoRelation(ctx, &store.MemoRelation{
MemoID: memoID,
RelatedMemoID: memoRelationUpsert.RelatedMemoID,
Type: store.MemoRelationType(memoRelationUpsert.Type),
RelatedMemoID: request.RelatedMemoID,
Type: store.MemoRelationType(request.Type),
})
if err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to upsert memo relation").SetInternal(err)
}
return c.JSON(http.StatusOK, composeResponse(memoRelation))
return c.JSON(http.StatusOK, memoRelation)
})
g.GET("/memo/:memoId/relation", func(c echo.Context) error {
......@@ -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)
}
memoRelationList, err := s.Store.ListMemoRelations(ctx, &store.FindMemoRelationMessage{
memoRelationList, err := s.Store.ListMemoRelations(ctx, &store.FindMemoRelation{
MemoID: &memoID,
})
if err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to list memo relations").SetInternal(err)
}
return c.JSON(http.StatusOK, composeResponse(memoRelationList))
return c.JSON(http.StatusOK, memoRelationList)
})
g.DELETE("/memo/:memoId/relation/:relatedMemoId/type/:relationType", func(c echo.Context) error {
......@@ -64,7 +80,7 @@ func (s *Server) registerMemoRelationRoutes(g *echo.Group) {
}
relationType := store.MemoRelationType(c.Param("relationType"))
if err := s.Store.DeleteMemoRelation(ctx, &store.DeleteMemoRelationMessage{
if err := s.Store.DeleteMemoRelation(ctx, &store.DeleteMemoRelation{
MemoID: &memoID,
RelatedMemoID: &relatedMemoID,
Type: &relationType,
......@@ -75,10 +91,10 @@ func (s *Server) registerMemoRelationRoutes(g *echo.Group) {
})
}
func convertMemoRelationMessageToMemoRelation(memoRelation *store.MemoRelationMessage) *api.MemoRelation {
return &api.MemoRelation{
func convertMemoRelationFromStore(memoRelation *store.MemoRelation) *MemoRelation {
return &MemoRelation{
MemoID: memoRelation.MemoID,
RelatedMemoID: memoRelation.RelatedMemoID,
Type: api.MemoRelationType(memoRelation.Type),
Type: MemoRelationType(memoRelation.Type),
}
}
package v1
import (
"encoding/json"
"fmt"
"net/http"
"strconv"
"time"
"github.com/labstack/echo/v4"
"github.com/usememos/memos/store"
)
type MemoResource struct {
MemoID int
ResourceID int
CreatedTs int64
UpdatedTs int64
MemoID int `json:"memoId"`
ResourceID int `json:"resourceId"`
CreatedTs int64 `json:"createdTs"`
UpdatedTs int64 `json:"updatedTs"`
}
type MemoResourceUpsert struct {
MemoID int `json:"-"`
ResourceID int
UpdatedTs *int64
type UpsertMemoResourceRequest struct {
ResourceID int `json:"resourceId"`
UpdatedTs *int64 `json:"updatedTs"`
}
type MemoResourceFind struct {
......@@ -22,3 +32,100 @@ type MemoResourceDelete struct {
MemoID *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) {
}
}
resource, err := s.Store.CreateResourceV1(ctx, create)
resource, err := s.Store.CreateResource(ctx, create)
if err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to create resource").SetInternal(err)
}
......@@ -311,7 +311,7 @@ func (s *APIV1Service) registerResourceRoutes(g *echo.Group) {
}
create.PublicID = publicID
resource, err := s.Store.CreateResourceV1(ctx, create)
resource, err := s.Store.CreateResource(ctx, create)
if err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to create resource").SetInternal(err)
}
......@@ -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))
}
if err := s.Store.DeleteResourceV1(ctx, &store.DeleteResource{
if err := s.Store.DeleteResource(ctx, &store.DeleteResource{
ID: resourceID,
}); err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to delete resource").SetInternal(err)
......@@ -522,7 +522,7 @@ func (s *APIV1Service) createResourceCreateActivity(ctx context.Context, resourc
if err != nil {
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,
Type: ActivityResourceCreate.String(),
Level: ActivityInfo.String(),
......
......@@ -210,7 +210,7 @@ func (s *APIV1Service) createShortcutCreateActivity(c echo.Context, shortcut *Sh
if err != nil {
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,
Type: ActivityShortcutCreate.String(),
Level: ActivityInfo.String(),
......
......@@ -84,7 +84,7 @@ func (s *APIV1Service) registerTagRoutes(g *echo.Group) {
return echo.NewHTTPError(http.StatusBadRequest, "Missing user session")
}
normalRowStatus := store.Normal
memoFind := &store.FindMemoMessage{
memoFind := &store.FindMemo{
CreatorID: &userID,
ContentSearch: []string{"#"},
RowStatus: &normalRowStatus,
......@@ -157,7 +157,7 @@ func (s *APIV1Service) createTagCreateActivity(c echo.Context, tag *Tag) error {
if err != nil {
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,
Type: ActivityTagCreate.String(),
Level: ActivityInfo.String(),
......
......@@ -377,7 +377,7 @@ func (s *APIV1Service) createUserCreateActivity(c echo.Context, user *User) erro
if err != nil {
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,
Type: ActivityUserCreate.String(),
Level: ActivityInfo.String(),
......
......@@ -35,10 +35,15 @@ func (s *APIV1Service) Register(rootGroup *echo.Group) {
s.registerShortcutRoutes(apiV1Group)
s.registerStorageRoutes(apiV1Group)
s.registerResourceRoutes(apiV1Group)
s.registerMemoRoutes(apiV1Group)
s.registerMemoOrganizerRoutes(apiV1Group)
s.registerMemoResourceRoutes(apiV1Group)
s.registerMemoRelationRoutes(apiV1Group)
publicGroup := rootGroup.Group("/o")
publicGroup.Use(func(next echo.HandlerFunc) echo.HandlerFunc {
return JWTMiddleware(s, next, s.Secret)
})
s.registerGetterPublicRoutes(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) {
}
normalStatus := store.Normal
memoFind := store.FindMemoMessage{
memoFind := store.FindMemo{
RowStatus: &normalStatus,
VisibilityList: []store.Visibility{store.Public},
}
......@@ -57,7 +57,7 @@ func (s *Server) registerRSSRoutes(g *echo.Group) {
}
normalStatus := store.Normal
memoFind := store.FindMemoMessage{
memoFind := store.FindMemo{
CreatorID: &id,
RowStatus: &normalStatus,
VisibilityList: []store.Visibility{store.Public},
......@@ -80,7 +80,7 @@ func (s *Server) registerRSSRoutes(g *echo.Group) {
const MaxRSSItemCount = 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{
Title: profile.Name,
Link: &feeds.Link{Href: baseURL},
......
......@@ -4,6 +4,7 @@ import (
"context"
"encoding/json"
"fmt"
"net/http"
"time"
"github.com/google/uuid"
......@@ -87,20 +88,6 @@ func NewServer(ctx context.Context, profile *profile.Profile, store *store.Store
rootGroup := e.Group("")
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.Register(rootGroup)
......@@ -185,7 +172,7 @@ func (s *Server) createServerStartActivity(ctx context.Context) error {
if err != nil {
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,
Type: apiv1.ActivityServerStart.String(),
Level: apiv1.ActivityInfo.String(),
......@@ -196,3 +183,12 @@ func (s *Server) createServerStartActivity(ctx context.Context) error {
}
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 (
"strconv"
"github.com/pkg/errors"
"github.com/usememos/memos/api"
apiv1 "github.com/usememos/memos/api/v1"
"github.com/usememos/memos/common"
"github.com/usememos/memos/plugin/telegram"
......@@ -61,20 +60,19 @@ func (t *telegramHandler) MessageHandle(ctx context.Context, bot *telegram.Bot,
return err
}
// create memo
memoCreate := api.CreateMemoRequest{
create := &store.Memo{
CreatorID: creatorID,
Visibility: api.Private,
Visibility: store.Private,
}
if message.Text != nil {
memoCreate.Content = *message.Text
create.Content = *message.Text
}
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 {
_, err := bot.EditMessage(ctx, message.Chat.ID, reply.MessageID, fmt.Sprintf("failed to CreateMemo: %s", err), nil)
return err
......@@ -90,7 +88,7 @@ func (t *telegramHandler) MessageHandle(ctx context.Context, bot *telegram.Bot,
case ".png":
mime = "image/png"
}
resource, err := t.store.CreateResourceV1(ctx, &store.Resource{
resource, err := t.store.CreateResource(ctx, &store.Resource{
CreatorID: creatorID,
Filename: filename,
Type: mime,
......@@ -103,7 +101,7 @@ func (t *telegramHandler) MessageHandle(ctx context.Context, bot *telegram.Bot,
return err
}
_, err = t.store.UpsertMemoResource(ctx, &api.MemoResourceUpsert{
_, err = t.store.UpsertMemoResource(ctx, &store.UpsertMemoResource{
MemoID: memoMessage.ID,
ResourceID: resource.ID,
})
......@@ -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))
}
update := store.UpdateMemoMessage{
update := store.UpdateMemo{
ID: memoID,
Visibility: &visibility,
}
......
......@@ -4,7 +4,7 @@ import (
"context"
)
type ActivityMessage struct {
type Activity struct {
ID int
// Standard fields
......@@ -18,7 +18,7 @@ type ActivityMessage struct {
}
// 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)
if err != nil {
return nil, err
......@@ -45,6 +45,7 @@ func (s *Store) CreateActivity(ctx context.Context, create *ActivityMessage) (*A
if err := tx.Commit(); err != nil {
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
return nil, err
}
if err := tx.Commit(); err != nil {
return nil, err
}
for _, item := range list {
s.idpCache.Store(item.ID, item)
}
......@@ -150,6 +154,10 @@ func (s *Store) GetIdentityProvider(ctx context.Context, find *FindIdentityProvi
return nil, nil
}
if err := tx.Commit(); err != nil {
return nil, err
}
identityProvider := list[0]
s.idpCache.Store(identityProvider.ID, identityProvider)
return identityProvider, nil
......
......@@ -7,8 +7,6 @@ import (
"strconv"
"strings"
"time"
"github.com/usememos/memos/common"
)
// Visibility is the type of a visibility.
......@@ -35,7 +33,7 @@ func (v Visibility) String() string {
return "PRIVATE"
}
type MemoMessage struct {
type Memo struct {
ID int
// Standard fields
......@@ -51,10 +49,10 @@ type MemoMessage struct {
// Composed fields
Pinned bool
ResourceIDList []int
RelationList []*MemoRelationMessage
RelationList []*MemoRelation
}
type FindMemoMessage struct {
type FindMemo struct {
ID *int
// Standard fields
......@@ -72,7 +70,7 @@ type FindMemoMessage struct {
OrderByUpdatedTs bool
}
type UpdateMemoMessage struct {
type UpdateMemo struct {
ID int
CreatedTs *int64
UpdatedTs *int64
......@@ -81,14 +79,14 @@ type UpdateMemoMessage struct {
Visibility *Visibility
}
type DeleteMemoMessage struct {
type DeleteMemo struct {
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)
if err != nil {
return nil, FormatError(err)
return nil, err
}
defer tx.Rollback()
......@@ -119,19 +117,20 @@ func (s *Store) CreateMemo(ctx context.Context, create *MemoMessage) (*MemoMessa
&create.UpdatedTs,
&create.RowStatus,
); err != nil {
return nil, FormatError(err)
return nil, err
}
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)
if err != nil {
return nil, FormatError(err)
return nil, err
}
defer tx.Rollback()
......@@ -140,13 +139,17 @@ func (s *Store) ListMemos(ctx context.Context, find *FindMemoMessage) ([]*MemoMe
return nil, err
}
if err := tx.Commit(); err != nil {
return nil, err
}
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)
if err != nil {
return nil, FormatError(err)
return nil, err
}
defer tx.Rollback()
......@@ -155,14 +158,18 @@ func (s *Store) GetMemo(ctx context.Context, find *FindMemoMessage) (*MemoMessag
return nil, err
}
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]
return memoMessage, nil
memo := list[0]
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)
if err != nil {
return err
......@@ -199,27 +206,20 @@ func (s *Store) UpdateMemo(ctx context.Context, update *UpdateMemoMessage) error
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)
if err != nil {
return FormatError(err)
return err
}
defer tx.Rollback()
where, args := []string{"id = ?"}, []any{delete.ID}
stmt := `DELETE FROM memo WHERE ` + strings.Join(where, " AND ")
result, err := tx.ExecContext(ctx, stmt, args...)
if err != nil {
return FormatError(err)
}
rows, err := result.RowsAffected()
_, err = tx.ExecContext(ctx, stmt, args...)
if err != nil {
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 {
return err
}
......@@ -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) {
tx, err := s.db.BeginTx(ctx, nil)
if err != nil {
return nil, FormatError(err)
return nil, err
}
defer tx.Rollback()
......@@ -247,7 +247,7 @@ func (s *Store) FindMemosVisibilityList(ctx context.Context, memoIDs []int) ([]V
rows, err := tx.QueryContext(ctx, query, args...)
if err != nil {
return nil, FormatError(err)
return nil, err
}
defer rows.Close()
......@@ -255,19 +255,19 @@ func (s *Store) FindMemosVisibilityList(ctx context.Context, memoIDs []int) ([]V
for rows.Next() {
var visibility Visibility
if err := rows.Scan(&visibility); err != nil {
return nil, FormatError(err)
return nil, err
}
visibilityList = append(visibilityList, visibility)
}
if err := rows.Err(); err != nil {
return nil, FormatError(err)
return nil, err
}
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{}
if v := find.ID; v != nil {
......@@ -343,68 +343,68 @@ func listMemos(ctx context.Context, tx *sql.Tx, find *FindMemoMessage) ([]*MemoM
rows, err := tx.QueryContext(ctx, query, args...)
if err != nil {
return nil, FormatError(err)
return nil, err
}
defer rows.Close()
memoMessageList := make([]*MemoMessage, 0)
list := make([]*Memo, 0)
for rows.Next() {
var memoMessage MemoMessage
var memo Memo
var memoResourceIDList sql.NullString
var memoRelationList sql.NullString
if err := rows.Scan(
&memoMessage.ID,
&memoMessage.CreatorID,
&memoMessage.CreatedTs,
&memoMessage.UpdatedTs,
&memoMessage.RowStatus,
&memoMessage.Content,
&memoMessage.Visibility,
&memoMessage.Pinned,
&memo.ID,
&memo.CreatorID,
&memo.CreatedTs,
&memo.UpdatedTs,
&memo.RowStatus,
&memo.Content,
&memo.Visibility,
&memo.Pinned,
&memoResourceIDList,
&memoRelationList,
); err != nil {
return nil, FormatError(err)
return nil, err
}
if memoResourceIDList.Valid {
idStringList := strings.Split(memoResourceIDList.String, ",")
memoMessage.ResourceIDList = make([]int, 0, len(idStringList))
memo.ResourceIDList = make([]int, 0, len(idStringList))
for _, idString := range idStringList {
id, err := strconv.Atoi(idString)
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 {
memoMessage.RelationList = make([]*MemoRelationMessage, 0)
memo.RelationList = make([]*MemoRelation, 0)
relatedMemoTypeList := strings.Split(memoRelationList.String, ",")
for _, relatedMemoType := range relatedMemoTypeList {
relatedMemoTypeList := strings.Split(relatedMemoType, ":")
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])
if err != nil {
return nil, FormatError(err)
return nil, err
}
memoMessage.RelationList = append(memoMessage.RelationList, &MemoRelationMessage{
MemoID: memoMessage.ID,
memo.RelationList = append(memo.RelationList, &MemoRelation{
MemoID: memo.ID,
RelatedMemoID: relatedMemoID,
Type: MemoRelationType(relatedMemoTypeList[1]),
})
}
}
memoMessageList = append(memoMessageList, &memoMessage)
list = append(list, &memo)
}
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 {
......@@ -420,7 +420,7 @@ func vacuumMemo(ctx context.Context, tx *sql.Tx) error {
)`
_, err := tx.ExecContext(ctx, stmt)
if err != nil {
return FormatError(err)
return err
}
return nil
......
......@@ -5,142 +5,99 @@ import (
"database/sql"
"fmt"
"strings"
"github.com/usememos/memos/api"
"github.com/usememos/memos/common"
)
// memoOrganizerRaw is the store model for an MemoOrganizer.
// Fields have exactly the same meanings as MemoOrganizer.
type memoOrganizerRaw struct {
// Domain specific fields
type MemoOrganizer struct {
MemoID int
UserID int
Pinned bool
}
func (raw *memoOrganizerRaw) toMemoOrganizer() *api.MemoOrganizer {
return &api.MemoOrganizer{
MemoID: raw.MemoID,
UserID: raw.UserID,
Pinned: raw.Pinned,
}
type FindMemoOrganizer struct {
MemoID int
UserID int
}
func (s *Store) FindMemoOrganizer(ctx context.Context, find *api.MemoOrganizerFind) (*api.MemoOrganizer, error) {
tx, err := s.db.BeginTx(ctx, nil)
if err != nil {
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
type DeleteMemoOrganizer struct {
MemoID *int
UserID *int
}
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)
if err != nil {
return FormatError(err)
return nil, err
}
defer tx.Rollback()
if err := upsertMemoOrganizer(ctx, tx, upsert); err != nil {
return err
query := `
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 {
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)
if err != nil {
return FormatError(err)
return nil, err
}
defer tx.Rollback()
if err := deleteMemoOrganizer(ctx, tx, delete); err != nil {
return err
where, args := []string{}, []any{}
if find.MemoID != 0 {
where = append(where, "memo_id = ?")
args = append(args, find.MemoID)
}
if err := tx.Commit(); err != nil {
return FormatError(err)
if find.UserID != 0 {
where = append(where, "user_id = ?")
args = append(args, find.UserID)
}
return nil
}
func findMemoOrganizer(ctx context.Context, tx *sql.Tx, find *api.MemoOrganizerFind) (*memoOrganizerRaw, error) {
query := `
query := fmt.Sprintf(`
SELECT
memo_id,
user_id,
pinned
FROM memo_organizer
WHERE memo_id = ? AND user_id = ?
`
row, err := tx.QueryContext(ctx, query, find.MemoID, find.UserID)
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")}
}
WHERE %s
`, strings.Join(where, " AND "))
row := tx.QueryRowContext(ctx, query, args...)
var memoOrganizerRaw memoOrganizerRaw
memoOrganizer := &MemoOrganizer{}
if err := row.Scan(
&memoOrganizerRaw.MemoID,
&memoOrganizerRaw.UserID,
&memoOrganizerRaw.Pinned,
&memoOrganizer.MemoID,
&memoOrganizer.UserID,
&memoOrganizer.Pinned,
); err != nil {
return nil, FormatError(err)
}
if err := row.Err(); err != nil {
return nil, err
}
return &memoOrganizerRaw, nil
return memoOrganizer, nil
}
func upsertMemoOrganizer(ctx context.Context, tx *sql.Tx, upsert *api.MemoOrganizerUpsert) error {
query := `
INSERT INTO memo_organizer (
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 {
func (s *Store) DeleteMemoOrganizerV1(ctx context.Context, delete *DeleteMemoOrganizer) error {
tx, err := s.db.BeginTx(ctx, nil)
if err != nil {
return FormatError(err)
}
defer tx.Rollback()
return nil
}
func deleteMemoOrganizer(ctx context.Context, tx *sql.Tx, delete *api.MemoOrganizerDelete) error {
where, args := []string{}, []any{}
if v := delete.MemoID; v != nil {
......@@ -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 ")
result, err := tx.ExecContext(ctx, stmt, args...)
_, 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 organizer not found")}
if err := tx.Commit(); err != nil {
return FormatError(err)
}
return nil
......
......@@ -3,10 +3,7 @@ package store
import (
"context"
"database/sql"
"fmt"
"strings"
"github.com/usememos/memos/common"
)
type MemoRelationType string
......@@ -16,28 +13,28 @@ const (
MemoRelationAdditional MemoRelationType = "ADDITIONAL"
)
type MemoRelationMessage struct {
type MemoRelation struct {
MemoID int
RelatedMemoID int
Type MemoRelationType
}
type FindMemoRelationMessage struct {
type FindMemoRelation struct {
MemoID *int
RelatedMemoID *int
Type *MemoRelationType
}
type DeleteMemoRelationMessage struct {
type DeleteMemoRelation struct {
MemoID *int
RelatedMemoID *int
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)
if err != nil {
return nil, FormatError(err)
return nil, err
}
defer tx.Rollback()
......@@ -52,7 +49,7 @@ func (s *Store) UpsertMemoRelation(ctx context.Context, create *MemoRelationMess
type = EXCLUDED.type
RETURNING memo_id, related_memo_id, type
`
memoRelationMessage := &MemoRelationMessage{}
memoRelationMessage := &MemoRelation{}
if err := tx.QueryRowContext(
ctx,
query,
......@@ -64,18 +61,18 @@ func (s *Store) UpsertMemoRelation(ctx context.Context, create *MemoRelationMess
&memoRelationMessage.RelatedMemoID,
&memoRelationMessage.Type,
); err != nil {
return nil, FormatError(err)
return nil, err
}
if err := tx.Commit(); err != nil {
return nil, FormatError(err)
return nil, err
}
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)
if err != nil {
return nil, FormatError(err)
return nil, err
}
defer tx.Rollback()
......@@ -84,13 +81,17 @@ func (s *Store) ListMemoRelations(ctx context.Context, find *FindMemoRelationMes
return nil, err
}
if err := tx.Commit(); err != nil {
return nil, err
}
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)
if err != nil {
return nil, FormatError(err)
return nil, err
}
defer tx.Rollback()
......@@ -100,15 +101,20 @@ func (s *Store) GetMemoRelation(ctx context.Context, find *FindMemoRelationMessa
}
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
}
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)
if err != nil {
return FormatError(err)
return err
}
defer tx.Rollback()
......@@ -127,16 +133,17 @@ func (s *Store) DeleteMemoRelation(ctx context.Context, delete *DeleteMemoRelati
DELETE FROM memo_relation
WHERE ` + strings.Join(where, " AND ")
if _, err := tx.ExecContext(ctx, query, args...); err != nil {
return FormatError(err)
return err
}
if err := tx.Commit(); err != nil {
return FormatError(err)
// Prevent lint warning.
return err
}
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{}
if find.MemoID != nil {
where, args = append(where, "memo_id = ?"), append(args, find.MemoID)
......@@ -156,24 +163,24 @@ func listMemoRelations(ctx context.Context, tx *sql.Tx, find *FindMemoRelationMe
FROM memo_relation
WHERE `+strings.Join(where, " AND "), args...)
if err != nil {
return nil, FormatError(err)
return nil, err
}
defer rows.Close()
memoRelationMessages := []*MemoRelationMessage{}
memoRelationMessages := []*MemoRelation{}
for rows.Next() {
memoRelationMessage := &MemoRelationMessage{}
memoRelationMessage := &MemoRelation{}
if err := rows.Scan(
&memoRelationMessage.MemoID,
&memoRelationMessage.RelatedMemoID,
&memoRelationMessage.Type,
); err != nil {
return nil, FormatError(err)
return nil, err
}
memoRelationMessages = append(memoRelationMessages, memoRelationMessage)
}
if err := rows.Err(); err != nil {
return nil, FormatError(err)
return nil, err
}
return memoRelationMessages, nil
}
......@@ -183,7 +190,7 @@ func vacuumMemoRelations(ctx context.Context, tx *sql.Tx) error {
DELETE FROM memo_relation
WHERE memo_id NOT IN (SELECT id FROM memo) OR related_memo_id NOT IN (SELECT id FROM memo)
`); err != nil {
return FormatError(err)
return err
}
return nil
}
......@@ -3,11 +3,7 @@ package store
import (
"context"
"database/sql"
"fmt"
"strings"
"github.com/usememos/memos/api"
"github.com/usememos/memos/common"
)
type MemoResource struct {
......@@ -17,165 +13,126 @@ type MemoResource struct {
UpdatedTs int64
}
type UpsertMemoResource struct {
MemoID int
ResourceID int
CreatedTs int64
UpdatedTs *int64
}
type FindMemoResource struct {
MemoID *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)
if err != nil {
return nil, err
return nil, FormatError(err)
}
defer tx.Rollback()
list, err := listMemoResources(ctx, tx, find)
if err != nil {
return nil, err
}
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{}
set := []string{"memo_id", "resource_id"}
args := []any{upsert.MemoID, upsert.ResourceID}
placeholder := []string{"?", "?"}
if v := find.MemoID; v != nil {
where, args = append(where, "memo_id = ?"), append(args, *v)
}
if v := find.ResourceID; v != nil {
where, args = append(where, "resource_id = ?"), append(args, *v)
if v := upsert.UpdatedTs; v != nil {
set, args, placeholder = append(set, "updated_ts"), append(args, v), append(placeholder, "?")
}
query := `
SELECT
memo_id,
resource_id,
created_ts,
updated_ts
FROM memo_resource
WHERE ` + strings.Join(where, " AND ") + `
ORDER BY updated_ts DESC
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
`
rows, err := tx.QueryContext(ctx, query, args...)
if err != nil {
memoResource := &MemoResource{}
if err := tx.QueryRowContext(ctx, query, args...).Scan(
&memoResource.MemoID,
&memoResource.ResourceID,
&memoResource.CreatedTs,
&memoResource.UpdatedTs,
); err != nil {
return nil, FormatError(err)
}
defer rows.Close()
list := make([]*MemoResource, 0)
for rows.Next() {
var memoResource MemoResource
if err := rows.Scan(
&memoResource.MemoID,
&memoResource.ResourceID,
&memoResource.CreatedTs,
&memoResource.UpdatedTs,
); err != nil {
return nil, FormatError(err)
}
list = append(list, &memoResource)
}
if err := rows.Err(); err != nil {
return nil, err
if err := tx.Commit(); err != nil {
return nil, FormatError(err)
}
return list, nil
}
// memoResourceRaw is the store model for an MemoResource.
// 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,
}
return memoResource, nil
}
func (s *Store) FindMemoResourceList(ctx context.Context, find *api.MemoResourceFind) ([]*api.MemoResource, error) {
func (s *Store) ListMemoResources(ctx context.Context, find *FindMemoResource) ([]*MemoResource, error) {
tx, err := s.db.BeginTx(ctx, nil)
if err != nil {
return nil, FormatError(err)
return nil, err
}
defer tx.Rollback()
memoResourceRawList, err := findMemoResourceList(ctx, tx, find)
list, err := listMemoResources(ctx, tx, find)
if err != nil {
return nil, err
}
list := []*api.MemoResource{}
for _, raw := range memoResourceRawList {
memoResource := raw.toMemoResource()
list = append(list, memoResource)
if err := tx.Commit(); err != nil {
return nil, err
}
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)
if err != nil {
return nil, FormatError(err)
return nil, err
}
defer tx.Rollback()
list, err := findMemoResourceList(ctx, tx, find)
list, err := listMemoResources(ctx, tx, find)
if err != nil {
return nil, err
}
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)
if err != nil {
return nil, FormatError(err)
return FormatError(err)
}
defer tx.Rollback()
memoResourceRaw, err := upsertMemoResource(ctx, tx, upsert)
if err != nil {
return nil, err
}
where, args := []string{}, []any{}
if err := tx.Commit(); err != nil {
return nil, FormatError(err)
if v := delete.MemoID; v != nil {
where, args = append(where, "memo_id = ?"), append(args, *v)
}
return memoResourceRaw.toMemoResource(), nil
}
func (s *Store) DeleteMemoResource(ctx context.Context, delete *api.MemoResourceDelete) error {
tx, err := s.db.BeginTx(ctx, nil)
if err != nil {
return FormatError(err)
if v := delete.ResourceID; v != nil {
where, args = append(where, "resource_id = ?"), append(args, *v)
}
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)
}
......@@ -186,7 +143,7 @@ func (s *Store) DeleteMemoResource(ctx context.Context, delete *api.MemoResource
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{}
if v := find.MemoID; v != nil {
......@@ -212,82 +169,26 @@ func findMemoResourceList(ctx context.Context, tx *sql.Tx, find *api.MemoResourc
}
defer rows.Close()
memoResourceRawList := make([]*memoResourceRaw, 0)
list := make([]*MemoResource, 0)
for rows.Next() {
var memoResourceRaw memoResourceRaw
var memoResource MemoResource
if err := rows.Scan(
&memoResourceRaw.MemoID,
&memoResourceRaw.ResourceID,
&memoResourceRaw.CreatedTs,
&memoResourceRaw.UpdatedTs,
&memoResource.MemoID,
&memoResource.ResourceID,
&memoResource.CreatedTs,
&memoResource.UpdatedTs,
); err != nil {
return nil, FormatError(err)
}
memoResourceRawList = append(memoResourceRawList, &memoResourceRaw)
list = append(list, &memoResource)
}
if err := rows.Err(); err != nil {
return nil, err
}
return memoResourceRawList, 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
return list, nil
}
func vacuumMemoResource(ctx context.Context, tx *sql.Tx) error {
......
......@@ -48,7 +48,7 @@ type DeleteResource struct {
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)
if err != nil {
return nil, FormatError(err)
......@@ -98,6 +98,10 @@ func (s *Store) ListResources(ctx context.Context, find *FindResource) ([]*Resou
return nil, err
}
if err := tx.Commit(); err != nil {
return nil, err
}
return resources, nil
}
......@@ -113,14 +117,14 @@ func (s *Store) GetResource(ctx context.Context, find *FindResource) (*Resource,
return nil, err
}
if err := tx.Commit(); err != nil {
return nil, err
}
if len(resources) == 0 {
return nil, nil
}
if err := tx.Commit(); err != nil {
return nil, err
}
return resources[0], nil
}
......@@ -174,7 +178,7 @@ func (s *Store) UpdateResource(ctx context.Context, update *UpdateResource) (*Re
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)
if err != nil {
return FormatError(err)
......
......@@ -85,6 +85,10 @@ func (s *Store) ListShortcuts(ctx context.Context, find *FindShortcut) ([]*Short
return nil, err
}
if err := tx.Commit(); err != nil {
return nil, err
}
return list, nil
}
......@@ -104,6 +108,10 @@ func (s *Store) GetShortcut(ctx context.Context, find *FindShortcut) (*Shortcut,
return nil, nil
}
if err := tx.Commit(); err != nil {
return nil, err
}
shortcut := list[0]
return shortcut, nil
}
......
......@@ -69,6 +69,10 @@ func (s *Store) ListStorages(ctx context.Context, find *FindStorage) ([]*Storage
return nil, err
}
if err := tx.Commit(); err != nil {
return nil, err
}
return list, nil
}
......@@ -87,6 +91,10 @@ func (s *Store) GetStorage(ctx context.Context, find *FindStorage) (*Storage, er
return nil, nil
}
if err := tx.Commit(); err != nil {
return nil, err
}
return list[0], nil
}
......
......@@ -57,6 +57,10 @@ func (s *Store) ListSystemSettings(ctx context.Context, find *FindSystemSetting)
return nil, err
}
if err := tx.Commit(); err != nil {
return nil, err
}
for _, systemSettingMessage := range list {
s.systemSettingCache.Store(systemSettingMessage.Name, systemSettingMessage)
}
......@@ -85,6 +89,10 @@ func (s *Store) GetSystemSetting(ctx context.Context, find *FindSystemSetting) (
return nil, nil
}
if err := tx.Commit(); err != nil {
return nil, err
}
systemSettingMessage := list[0]
s.systemSettingCache.Store(systemSettingMessage.Name, systemSettingMessage)
return systemSettingMessage, nil
......
......@@ -88,6 +88,10 @@ func (s *Store) ListTags(ctx context.Context, find *FindTag) ([]*Tag, error) {
return nil, err
}
if err := tx.Commit(); err != nil {
return nil, err
}
return list, nil
}
......
......@@ -120,6 +120,7 @@ func (s *Store) CreateUser(ctx context.Context, create *User) (*User, error) {
if err := tx.Commit(); err != nil {
return nil, err
}
user := create
s.userCache.Store(user.ID, user)
return user, nil
......@@ -202,6 +203,10 @@ func (s *Store) ListUsers(ctx context.Context, find *FindUser) ([]*User, error)
return nil, err
}
if err := tx.Commit(); err != nil {
return nil, err
}
for _, user := range list {
s.userCache.Store(user.ID, user)
}
......@@ -228,6 +233,11 @@ func (s *Store) GetUser(ctx context.Context, find *FindUser) (*User, error) {
if len(list) == 0 {
return nil, nil
}
if err := tx.Commit(); err != nil {
return nil, err
}
user := list[0]
s.userCache.Store(user.ID, user)
return user, nil
......
......@@ -57,6 +57,10 @@ func (s *Store) ListUserSettings(ctx context.Context, find *FindUserSetting) ([]
return nil, err
}
if err := tx.Commit(); err != nil {
return nil, err
}
for _, userSetting := range userSettingList {
s.userSettingCache.Store(getUserSettingCacheKey(userSetting.UserID, userSetting.Key), userSetting)
}
......@@ -85,6 +89,10 @@ func (s *Store) GetUserSetting(ctx context.Context, find *FindUserSetting) (*Use
return nil, nil
}
if err := tx.Commit(); err != nil {
return nil, err
}
userSetting := list[0]
s.userSettingCache.Store(getUserSettingCacheKey(userSetting.UserID, userSetting.Key), userSetting)
return userSetting, nil
......
......@@ -9,7 +9,6 @@ import (
"github.com/pkg/errors"
"github.com/stretchr/testify/require"
"github.com/usememos/memos/api"
apiv1 "github.com/usememos/memos/api/v1"
)
......@@ -26,17 +25,17 @@ func TestMemoRelationServer(t *testing.T) {
user, err := s.postAuthSignup(signup)
require.NoError(t, err)
require.Equal(t, signup.Username, user.Username)
memo, err := s.postMemoCreate(&api.CreateMemoRequest{
memo, err := s.postMemoCreate(&apiv1.CreateMemoRequest{
Content: "test memo",
})
require.NoError(t, err)
require.Equal(t, "test memo", memo.Content)
memo2, err := s.postMemoCreate(&api.CreateMemoRequest{
memo2, err := s.postMemoCreate(&apiv1.CreateMemoRequest{
Content: "test memo2",
RelationList: []*api.MemoRelationUpsert{
RelationList: []*apiv1.UpsertMemoRelationRequest{
{
RelatedMemoID: memo.ID,
Type: api.MemoRelationReference,
Type: apiv1.MemoRelationReference,
},
},
})
......@@ -46,14 +45,14 @@ func TestMemoRelationServer(t *testing.T) {
require.NoError(t, err)
require.Len(t, memoList, 2)
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)
memo2, err = s.getMemo(memo2.ID)
require.NoError(t, err)
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,
Type: api.MemoRelationReference,
Type: apiv1.MemoRelationReference,
})
require.NoError(t, err)
require.Equal(t, memo.ID, memoRelation.RelatedMemoID)
......@@ -62,13 +61,13 @@ func TestMemoRelationServer(t *testing.T) {
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)
if err != nil {
return nil, errors.Wrap(err, "failed to marshal memo relation upsert")
}
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 {
return nil, err
}
......@@ -79,17 +78,14 @@ func (s *TestingServer) postMemoRelationUpsert(memoID int, memoRelationUpsert *a
return nil, errors.Wrap(err, "fail to read response body")
}
type MemoCreateResponse struct {
Data *api.MemoRelation `json:"data"`
}
res := new(MemoCreateResponse)
if err = json.Unmarshal(buf.Bytes(), res); err != nil {
memoRelation := &apiv1.MemoRelation{}
if err = json.Unmarshal(buf.Bytes(), memoRelation); err != nil {
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 {
_, err := s.delete(fmt.Sprintf("/api/memo/%d/relation/%d/type/%s", memoID, relatedMemoID, relationType), nil)
func (s *TestingServer) deleteMemoRelation(memoID int, relatedMemoID int, relationType apiv1.MemoRelationType) error {
_, err := s.delete(fmt.Sprintf("/api/v1/memo/%d/relation/%d/type/%s", memoID, relatedMemoID, relationType), nil)
return err
}
......@@ -9,7 +9,6 @@ import (
"github.com/pkg/errors"
"github.com/stretchr/testify/require"
"github.com/usememos/memos/api"
apiv1 "github.com/usememos/memos/api/v1"
)
......@@ -26,7 +25,7 @@ func TestMemoServer(t *testing.T) {
user, err := s.postAuthSignup(signup)
require.NoError(t, err)
require.Equal(t, signup.Username, user.Username)
memo, err := s.postMemoCreate(&api.CreateMemoRequest{
memo, err := s.postMemoCreate(&apiv1.CreateMemoRequest{
Content: "test memo",
})
require.NoError(t, err)
......@@ -35,20 +34,18 @@ func TestMemoServer(t *testing.T) {
require.NoError(t, err)
require.Len(t, memoList, 1)
updatedContent := "updated memo"
memo, err = s.patchMemo(&api.PatchMemoRequest{
memo, err = s.patchMemo(&apiv1.PatchMemoRequest{
ID: memo.ID,
Content: &updatedContent,
})
require.NoError(t, err)
require.Equal(t, updatedContent, memo.Content)
require.Equal(t, false, memo.Pinned)
memo, err = s.postMemosOrganizer(&api.MemoOrganizerUpsert{
MemoID: memo.ID,
UserID: user.ID,
_, err = s.postMemoOrganizer(memo.ID, &apiv1.UpsertMemoOrganizerRequest{
Pinned: true,
})
require.NoError(t, err)
memo, err = s.patchMemo(&api.PatchMemoRequest{
memo, err = s.patchMemo(&apiv1.PatchMemoRequest{
ID: memo.ID,
Content: &updatedContent,
})
......@@ -62,8 +59,8 @@ func TestMemoServer(t *testing.T) {
require.Len(t, memoList, 0)
}
func (s *TestingServer) getMemo(memoID int) (*api.MemoResponse, error) {
body, err := s.get(fmt.Sprintf("/api/memo/%d", memoID), nil)
func (s *TestingServer) getMemo(memoID int) (*apiv1.Memo, error) {
body, err := s.get(fmt.Sprintf("/api/v1/memo/%d", memoID), nil)
if err != nil {
return nil, err
}
......@@ -74,18 +71,15 @@ func (s *TestingServer) getMemo(memoID int) (*api.MemoResponse, error) {
return nil, errors.Wrap(err, "fail to read response body")
}
type MemoCreateResponse struct {
Data *api.MemoResponse `json:"data"`
}
res := new(MemoCreateResponse)
if err = json.Unmarshal(buf.Bytes(), res); err != nil {
memo := &apiv1.Memo{}
if err = json.Unmarshal(buf.Bytes(), memo); err != nil {
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) {
body, err := s.get("/api/memo", nil)
func (s *TestingServer) getMemoList() ([]*apiv1.Memo, error) {
body, err := s.get("/api/v1/memo", nil)
if err != nil {
return nil, err
}
......@@ -96,23 +90,20 @@ func (s *TestingServer) getMemoList() ([]*api.MemoResponse, error) {
return nil, errors.Wrap(err, "fail to read response body")
}
type MemoCreateResponse struct {
Data []*api.MemoResponse `json:"data"`
}
res := new(MemoCreateResponse)
if err = json.Unmarshal(buf.Bytes(), res); err != nil {
memoList := []*apiv1.Memo{}
if err = json.Unmarshal(buf.Bytes(), &memoList); err != nil {
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)
if err != nil {
return nil, errors.Wrap(err, "failed to marshal memo create")
}
reader := bytes.NewReader(rawData)
body, err := s.post("/api/memo", reader, nil)
body, err := s.post("/api/v1/memo", reader, nil)
if err != nil {
return nil, err
}
......@@ -123,23 +114,20 @@ func (s *TestingServer) postMemoCreate(memoCreate *api.CreateMemoRequest) (*api.
return nil, errors.Wrap(err, "fail to read response body")
}
type MemoCreateResponse struct {
Data *api.MemoResponse `json:"data"`
}
res := new(MemoCreateResponse)
if err = json.Unmarshal(buf.Bytes(), res); err != nil {
memo := &apiv1.Memo{}
if err = json.Unmarshal(buf.Bytes(), memo); err != nil {
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)
if err != nil {
return nil, errors.Wrap(err, "failed to marshal memo patch")
}
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 {
return nil, err
}
......@@ -150,28 +138,25 @@ func (s *TestingServer) patchMemo(memoPatch *api.PatchMemoRequest) (*api.MemoRes
return nil, errors.Wrap(err, "fail to read response body")
}
type MemoPatchResponse struct {
Data *api.MemoResponse `json:"data"`
}
res := new(MemoPatchResponse)
if err = json.Unmarshal(buf.Bytes(), res); err != nil {
memo := &apiv1.Memo{}
if err = json.Unmarshal(buf.Bytes(), memo); err != nil {
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 {
_, err := s.delete(fmt.Sprintf("/api/memo/%d", memoID), nil)
_, err := s.delete(fmt.Sprintf("/api/v1/memo/%d", memoID), nil)
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)
if err != nil {
return nil, errors.Wrap(err, "failed to marshal memos organizer")
}
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 {
return nil, err
}
......@@ -182,12 +167,9 @@ func (s *TestingServer) postMemosOrganizer(memosOrganizer *api.MemoOrganizerUpse
return nil, errors.Wrap(err, "fail to read response body")
}
type MemoOrganizerResponse struct {
Data *api.MemoResponse `json:"data"`
}
res := new(MemoOrganizerResponse)
if err = json.Unmarshal(buf.Bytes(), res); err != nil {
memo := &apiv1.Memo{}
if err = json.Unmarshal(buf.Bytes(), memo); err != nil {
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) {
ts := NewTestingStore(ctx, t)
user, err := createTestingHostUser(ctx, ts)
require.NoError(t, err)
memoCreate := &store.MemoMessage{
memoCreate := &store.Memo{
CreatorID: user.ID,
Content: "test_content",
Visibility: store.Public,
......@@ -21,7 +21,7 @@ func TestMemoRelationStore(t *testing.T) {
memo, err := ts.CreateMemo(ctx, memoCreate)
require.NoError(t, err)
require.Equal(t, memoCreate.Content, memo.Content)
memo2Create := &store.MemoMessage{
memo2Create := &store.Memo{
CreatorID: user.ID,
Content: "test_content_2",
Visibility: store.Public,
......@@ -29,14 +29,14 @@ func TestMemoRelationStore(t *testing.T) {
memo2, err := ts.CreateMemo(ctx, memo2Create)
require.NoError(t, err)
require.Equal(t, memo2Create.Content, memo2.Content)
memoRelationMessage := &store.MemoRelationMessage{
memoRelationMessage := &store.MemoRelation{
MemoID: memo.ID,
RelatedMemoID: memo2.ID,
Type: store.MemoRelationReference,
}
_, err = ts.UpsertMemoRelation(ctx, memoRelationMessage)
require.NoError(t, err)
memoRelation, err := ts.ListMemoRelations(ctx, &store.FindMemoRelationMessage{
memoRelation, err := ts.ListMemoRelations(ctx, &store.FindMemoRelation{
MemoID: &memo.ID,
})
require.NoError(t, err)
......@@ -44,11 +44,11 @@ func TestMemoRelationStore(t *testing.T) {
require.Equal(t, memo2.ID, memoRelation[0].RelatedMemoID)
require.Equal(t, memo.ID, memoRelation[0].MemoID)
require.Equal(t, store.MemoRelationReference, memoRelation[0].Type)
err = ts.DeleteMemo(ctx, &store.DeleteMemoMessage{
err = ts.DeleteMemo(ctx, &store.DeleteMemo{
ID: memo2.ID,
})
require.NoError(t, err)
memoRelation, err = ts.ListMemoRelations(ctx, &store.FindMemoRelationMessage{
memoRelation, err = ts.ListMemoRelations(ctx, &store.FindMemoRelation{
MemoID: &memo.ID,
})
require.NoError(t, err)
......
......@@ -13,7 +13,7 @@ func TestMemoStore(t *testing.T) {
ts := NewTestingStore(ctx, t)
user, err := createTestingHostUser(ctx, ts)
require.NoError(t, err)
memoCreate := &store.MemoMessage{
memoCreate := &store.Memo{
CreatorID: user.ID,
Content: "test_content",
Visibility: store.Public,
......@@ -22,23 +22,23 @@ func TestMemoStore(t *testing.T) {
require.NoError(t, err)
require.Equal(t, memoCreate.Content, memo.Content)
memoPatchContent := "test_content_2"
memoPatch := &store.UpdateMemoMessage{
memoPatch := &store.UpdateMemo{
ID: memo.ID,
Content: &memoPatchContent,
}
err = ts.UpdateMemo(ctx, memoPatch)
require.NoError(t, err)
memo, err = ts.GetMemo(ctx, &store.FindMemoMessage{
memo, err = ts.GetMemo(ctx, &store.FindMemo{
ID: &memo.ID,
})
require.NoError(t, err)
memoList, err := ts.ListMemos(ctx, &store.FindMemoMessage{
memoList, err := ts.ListMemos(ctx, &store.FindMemo{
CreatorID: &user.ID,
})
require.NoError(t, err)
require.Equal(t, 1, len(memoList))
require.Equal(t, memo, memoList[0])
err = ts.DeleteMemo(ctx, &store.DeleteMemoMessage{
err = ts.DeleteMemo(ctx, &store.DeleteMemo{
ID: memo.ID,
})
require.NoError(t, err)
......
......@@ -11,7 +11,7 @@ import (
func TestResourceStore(t *testing.T) {
ctx := context.Background()
ts := NewTestingStore(ctx, t)
_, err := ts.CreateResourceV1(ctx, &store.Resource{
_, err := ts.CreateResource(ctx, &store.Resource{
CreatorID: 101,
Filename: "test.epub",
Blob: []byte("test"),
......@@ -49,11 +49,11 @@ func TestResourceStore(t *testing.T) {
require.NoError(t, err)
require.Nil(t, notFoundResource)
err = ts.DeleteResourceV1(ctx, &store.DeleteResource{
err = ts.DeleteResource(ctx, &store.DeleteResource{
ID: 1,
})
require.NoError(t, err)
err = ts.DeleteResourceV1(ctx, &store.DeleteResource{
err = ts.DeleteResource(ctx, &store.DeleteResource{
ID: 2,
})
require.NoError(t, err)
......
......@@ -11,7 +11,7 @@ const MyAccountSection = () => {
const { t } = useTranslation();
const userStore = useUserStore();
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 () => {
showCommonDialog({
......
......@@ -52,7 +52,7 @@ const ShareMemoDialog: React.FC<Props> = (props: Props) => {
useEffect(() => {
getMemoStats(user.id)
.then(({ data: { data } }) => {
.then(({ data }) => {
setPartialState({
memoAmount: data.length,
});
......
......@@ -57,7 +57,7 @@ const UsageHeatMap = () => {
useEffect(() => {
getMemoStats(currentUserId)
.then(({ data: { data } }) => {
.then(({ data }) => {
setMemoAmount(data.length);
const newStat: DailyUsageStat[] = getInitialUsageStat(usedDaysAmount, beginDayTimestamp);
for (const record of data) {
......
......@@ -26,7 +26,7 @@ const DatePicker: React.FC<DatePickerProps> = (props: DatePickerProps) => {
}, [datestamp]);
useEffect(() => {
getMemoStats(currentUserId).then(({ data: { data } }) => {
getMemoStats(currentUserId).then(({ data }) => {
const m = new Map();
for (const record of data) {
const date = getDateStampByDate(record * 1000);
......
import axios from "axios";
type ResponseObject<T> = {
data: T;
error?: string;
message?: string;
};
export function getSystemStatus() {
return axios.get<SystemStatus>("/api/v1/status");
}
......@@ -85,7 +79,7 @@ export function getAllMemos(memoFind?: MemoFind) {
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) {
......@@ -105,39 +99,39 @@ export function getMemoList(memoFind?: MemoFind) {
if (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) {
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) {
return axios.get<ResponseObject<Memo>>(`/api/memo/${id}`);
return axios.get<Memo>(`/api/v1/memo/${id}`);
}
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) {
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) {
return axios.post(`/api/memo/${memoId}/organizer`, {
return axios.post(`/api/v1/memo/${memoId}/organizer`, {
pinned: true,
});
}
export function unpinMemo(memoId: MemoId) {
return axios.post(`/api/memo/${memoId}/organizer`, {
return axios.post(`/api/v1/memo/${memoId}/organizer`, {
pinned: false,
});
}
export function deleteMemo(memoId: MemoId) {
return axios.delete(`/api/memo/${memoId}`);
return axios.delete(`/api/v1/memo/${memoId}`);
}
export function getShortcutList(shortcutFind?: ShortcutFind) {
......@@ -192,17 +186,17 @@ export function deleteResourceById(id: ResourceId) {
}
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) {
return axios.post(`/api/memo/${memoId}/resource`, {
return axios.post(`/api/v1/memo/${memoId}/resource`, {
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) {
......
......@@ -21,7 +21,7 @@ export const useMemoStore = () => {
const memoCacheStore = useMemoCacheStore();
const fetchMemoById = async (memoId: MemoId) => {
const { data } = (await api.getMemoById(memoId)).data;
const { data } = await api.getMemoById(memoId);
const memo = convertResponseModelMemo(data);
return memo;
......@@ -42,7 +42,7 @@ export const useMemoStore = () => {
if (userStore.isVisitorMode()) {
memoFind.creatorId = userStore.getUserIdFromPath();
}
const { data } = (await api.getMemoList(memoFind)).data;
const { data } = await api.getMemoList(memoFind);
const fetchedMemos = data.map((m) => convertResponseModelMemo(m));
store.dispatch(upsertMemos(fetchedMemos));
store.dispatch(setIsFetching(false));
......@@ -60,7 +60,7 @@ export const useMemoStore = () => {
offset,
};
const { data } = (await api.getAllMemos(memoFind)).data;
const { data } = await api.getAllMemos(memoFind);
const fetchedMemos = data.map((m) => convertResponseModelMemo(m));
for (const m of fetchedMemos) {
......@@ -76,7 +76,7 @@ export const useMemoStore = () => {
if (userStore.isVisitorMode()) {
memoFind.creatorId = userStore.getUserIdFromPath();
}
const { data } = (await api.getMemoList(memoFind)).data;
const { data } = await api.getMemoList(memoFind);
const archivedMemos = data.map((m) => {
return convertResponseModelMemo(m);
});
......@@ -97,14 +97,14 @@ export const useMemoStore = () => {
return state.memos.filter((m) => m.content.match(regex));
},
createMemo: async (memoCreate: MemoCreate) => {
const { data } = (await api.createMemo(memoCreate)).data;
const { data } = await api.createMemo(memoCreate);
const memo = convertResponseModelMemo(data);
store.dispatch(createMemo(memo));
memoCacheStore.setMemoCache(memo);
return memo;
},
patchMemo: async (memoPatch: MemoPatch): Promise<Memo> => {
const { data } = (await api.patchMemo(memoPatch)).data;
const { data } = await api.patchMemo(memoPatch);
const memo = convertResponseModelMemo(data);
store.dispatch(patchMemo(omit(memo, "pinned")));
memoCacheStore.setMemoCache(memo);
......
......@@ -12,7 +12,7 @@ export const useMemoCacheStore = create(
return memo;
}
const { data } = (await api.getMemoById(memoId)).data;
const { data } = await api.getMemoById(memoId);
const formatedMemo = convertResponseModelMemo(data);
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