mirror of
https://github.com/usememos/memos.git
synced 2025-06-05 22:09:59 +02:00
refactor: migrate resource to apiv1 (#1901)
This commit is contained in:
@ -20,51 +20,3 @@ type Resource struct {
|
|||||||
// Related fields
|
// Related fields
|
||||||
LinkedMemoAmount int `json:"linkedMemoAmount"`
|
LinkedMemoAmount int `json:"linkedMemoAmount"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type ResourceCreate struct {
|
|
||||||
// Standard fields
|
|
||||||
CreatorID int `json:"-"`
|
|
||||||
|
|
||||||
// Domain specific fields
|
|
||||||
Filename string `json:"filename"`
|
|
||||||
Blob []byte `json:"-"`
|
|
||||||
InternalPath string `json:"internalPath"`
|
|
||||||
ExternalLink string `json:"externalLink"`
|
|
||||||
Type string `json:"type"`
|
|
||||||
Size int64 `json:"-"`
|
|
||||||
PublicID string `json:"publicId"`
|
|
||||||
DownloadToLocal bool `json:"downloadToLocal"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type ResourceFind struct {
|
|
||||||
ID *int `json:"id"`
|
|
||||||
|
|
||||||
// Standard fields
|
|
||||||
CreatorID *int `json:"creatorId"`
|
|
||||||
|
|
||||||
// Domain specific fields
|
|
||||||
Filename *string `json:"filename"`
|
|
||||||
MemoID *int
|
|
||||||
PublicID *string `json:"publicId"`
|
|
||||||
GetBlob bool
|
|
||||||
|
|
||||||
// Pagination
|
|
||||||
Limit *int
|
|
||||||
Offset *int
|
|
||||||
}
|
|
||||||
|
|
||||||
type ResourcePatch struct {
|
|
||||||
ID int `json:"-"`
|
|
||||||
|
|
||||||
// Standard fields
|
|
||||||
UpdatedTs *int64
|
|
||||||
|
|
||||||
// Domain specific fields
|
|
||||||
Filename *string `json:"filename"`
|
|
||||||
ResetPublicID *bool `json:"resetPublicId"`
|
|
||||||
PublicID *string `json:"-"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type ResourceDelete struct {
|
|
||||||
ID int
|
|
||||||
}
|
|
||||||
|
24
api/v1/memo_resource.go
Normal file
24
api/v1/memo_resource.go
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
package v1
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
@ -1,4 +1,4 @@
|
|||||||
package server
|
package v1
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
@ -21,8 +21,6 @@ import (
|
|||||||
"github.com/disintegration/imaging"
|
"github.com/disintegration/imaging"
|
||||||
"github.com/labstack/echo/v4"
|
"github.com/labstack/echo/v4"
|
||||||
"github.com/pkg/errors"
|
"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/common"
|
||||||
"github.com/usememos/memos/common/log"
|
"github.com/usememos/memos/common/log"
|
||||||
"github.com/usememos/memos/plugin/storage/s3"
|
"github.com/usememos/memos/plugin/storage/s3"
|
||||||
@ -30,6 +28,48 @@ import (
|
|||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
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"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type CreateResourceRequest struct {
|
||||||
|
Filename string `json:"filename"`
|
||||||
|
InternalPath string `json:"internalPath"`
|
||||||
|
ExternalLink string `json:"externalLink"`
|
||||||
|
Type string `json:"type"`
|
||||||
|
PublicID string `json:"publicId"`
|
||||||
|
DownloadToLocal bool `json:"downloadToLocal"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type FindResourceRequest struct {
|
||||||
|
ID *int `json:"id"`
|
||||||
|
CreatorID *int `json:"creatorId"`
|
||||||
|
Filename *string `json:"filename"`
|
||||||
|
PublicID *string `json:"publicId"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type UpdateResourceRequest struct {
|
||||||
|
Filename *string `json:"filename"`
|
||||||
|
ResetPublicID *bool `json:"resetPublicId"`
|
||||||
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
// The upload memory buffer is 32 MiB.
|
// The upload memory buffer is 32 MiB.
|
||||||
// It should be kept low, so RAM usage doesn't get out of control.
|
// It should be kept low, so RAM usage doesn't get out of control.
|
||||||
@ -43,7 +83,7 @@ const (
|
|||||||
|
|
||||||
var fileKeyPattern = regexp.MustCompile(`\{[a-z]{1,9}\}`)
|
var fileKeyPattern = regexp.MustCompile(`\{[a-z]{1,9}\}`)
|
||||||
|
|
||||||
func (s *Server) registerResourceRoutes(g *echo.Group) {
|
func (s *APIV1Service) registerResourceRoutes(g *echo.Group) {
|
||||||
g.POST("/resource", func(c echo.Context) error {
|
g.POST("/resource", func(c echo.Context) error {
|
||||||
ctx := c.Request().Context()
|
ctx := c.Request().Context()
|
||||||
userID, ok := c.Get(getUserIDContextKey()).(int)
|
userID, ok := c.Get(getUserIDContextKey()).(int)
|
||||||
@ -51,15 +91,21 @@ func (s *Server) registerResourceRoutes(g *echo.Group) {
|
|||||||
return echo.NewHTTPError(http.StatusUnauthorized, "Missing user in session")
|
return echo.NewHTTPError(http.StatusUnauthorized, "Missing user in session")
|
||||||
}
|
}
|
||||||
|
|
||||||
resourceCreate := &api.ResourceCreate{}
|
request := &CreateResourceRequest{}
|
||||||
if err := json.NewDecoder(c.Request().Body).Decode(resourceCreate); err != nil {
|
if err := json.NewDecoder(c.Request().Body).Decode(request); err != nil {
|
||||||
return echo.NewHTTPError(http.StatusBadRequest, "Malformatted post resource request").SetInternal(err)
|
return echo.NewHTTPError(http.StatusBadRequest, "Malformatted post resource request").SetInternal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
resourceCreate.CreatorID = userID
|
create := &store.Resource{
|
||||||
if resourceCreate.ExternalLink != "" {
|
CreatorID: userID,
|
||||||
|
Filename: request.Filename,
|
||||||
|
ExternalLink: request.ExternalLink,
|
||||||
|
Type: request.Type,
|
||||||
|
PublicID: common.GenUUID(),
|
||||||
|
}
|
||||||
|
if request.ExternalLink != "" {
|
||||||
// Only allow those external links scheme with http/https
|
// Only allow those external links scheme with http/https
|
||||||
linkURL, err := url.Parse(resourceCreate.ExternalLink)
|
linkURL, err := url.Parse(request.ExternalLink)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return echo.NewHTTPError(http.StatusBadRequest, "Invalid external link").SetInternal(err)
|
return echo.NewHTTPError(http.StatusBadRequest, "Invalid external link").SetInternal(err)
|
||||||
}
|
}
|
||||||
@ -67,24 +113,24 @@ func (s *Server) registerResourceRoutes(g *echo.Group) {
|
|||||||
return echo.NewHTTPError(http.StatusBadRequest, "Invalid external link scheme")
|
return echo.NewHTTPError(http.StatusBadRequest, "Invalid external link scheme")
|
||||||
}
|
}
|
||||||
|
|
||||||
if resourceCreate.DownloadToLocal {
|
if request.DownloadToLocal {
|
||||||
resp, err := http.Get(linkURL.String())
|
resp, err := http.Get(linkURL.String())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return echo.NewHTTPError(http.StatusBadRequest, "Failed to request "+resourceCreate.ExternalLink)
|
return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Failed to request %s", request.ExternalLink))
|
||||||
}
|
}
|
||||||
defer resp.Body.Close()
|
defer resp.Body.Close()
|
||||||
|
|
||||||
blob, err := io.ReadAll(resp.Body)
|
blob, err := io.ReadAll(resp.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return echo.NewHTTPError(http.StatusBadRequest, "Failed to read "+resourceCreate.ExternalLink)
|
return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Failed to read %s", request.ExternalLink))
|
||||||
}
|
}
|
||||||
resourceCreate.Blob = blob
|
create.Blob = blob
|
||||||
|
|
||||||
mediaType, _, err := mime.ParseMediaType(resp.Header.Get("Content-Type"))
|
mediaType, _, err := mime.ParseMediaType(resp.Header.Get("Content-Type"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return echo.NewHTTPError(http.StatusBadRequest, "Failed to read mime from "+resourceCreate.ExternalLink)
|
return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Failed to read mime from %s", request.ExternalLink))
|
||||||
}
|
}
|
||||||
resourceCreate.Type = mediaType
|
create.Type = mediaType
|
||||||
|
|
||||||
filename := path.Base(linkURL.Path)
|
filename := path.Base(linkURL.Path)
|
||||||
if path.Ext(filename) == "" {
|
if path.Ext(filename) == "" {
|
||||||
@ -93,20 +139,19 @@ func (s *Server) registerResourceRoutes(g *echo.Group) {
|
|||||||
filename += extensions[0]
|
filename += extensions[0]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
resourceCreate.Filename = filename
|
create.Filename = filename
|
||||||
resourceCreate.PublicID = common.GenUUID()
|
create.ExternalLink = ""
|
||||||
resourceCreate.ExternalLink = ""
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
resource, err := s.Store.CreateResource(ctx, resourceCreate)
|
resource, err := s.Store.CreateResourceV1(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)
|
||||||
}
|
}
|
||||||
if err := s.createResourceCreateActivity(ctx, resource); err != nil {
|
if err := s.createResourceCreateActivity(ctx, resource); err != nil {
|
||||||
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to create activity").SetInternal(err)
|
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to create activity").SetInternal(err)
|
||||||
}
|
}
|
||||||
return c.JSON(http.StatusOK, composeResponse(resource))
|
return c.JSON(http.StatusOK, convertResourceFromStore(resource))
|
||||||
})
|
})
|
||||||
|
|
||||||
g.POST("/resource/blob", func(c echo.Context) error {
|
g.POST("/resource/blob", func(c echo.Context) error {
|
||||||
@ -117,7 +162,7 @@ func (s *Server) registerResourceRoutes(g *echo.Group) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// This is the backend default max upload size limit.
|
// This is the backend default max upload size limit.
|
||||||
maxUploadSetting := s.Store.GetSystemSettingValueWithDefault(&ctx, apiv1.SystemSettingMaxUploadSizeMiBName.String(), "32")
|
maxUploadSetting := s.Store.GetSystemSettingValueWithDefault(&ctx, SystemSettingMaxUploadSizeMiBName.String(), "32")
|
||||||
var settingMaxUploadSizeBytes int
|
var settingMaxUploadSizeBytes int
|
||||||
if settingMaxUploadSizeMiB, err := strconv.Atoi(maxUploadSetting); err == nil {
|
if settingMaxUploadSizeMiB, err := strconv.Atoi(maxUploadSetting); err == nil {
|
||||||
settingMaxUploadSizeBytes = settingMaxUploadSizeMiB * MebiByte
|
settingMaxUploadSizeBytes = settingMaxUploadSizeMiB * MebiByte
|
||||||
@ -150,12 +195,12 @@ func (s *Server) registerResourceRoutes(g *echo.Group) {
|
|||||||
}
|
}
|
||||||
defer sourceFile.Close()
|
defer sourceFile.Close()
|
||||||
|
|
||||||
var resourceCreate *api.ResourceCreate
|
create := &store.Resource{}
|
||||||
systemSettingStorageServiceID, err := s.Store.GetSystemSetting(ctx, &store.FindSystemSetting{Name: apiv1.SystemSettingStorageServiceIDName.String()})
|
systemSettingStorageServiceID, err := s.Store.GetSystemSetting(ctx, &store.FindSystemSetting{Name: SystemSettingStorageServiceIDName.String()})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to find storage").SetInternal(err)
|
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to find storage").SetInternal(err)
|
||||||
}
|
}
|
||||||
storageServiceID := apiv1.DatabaseStorage
|
storageServiceID := DatabaseStorage
|
||||||
if systemSettingStorageServiceID != nil {
|
if systemSettingStorageServiceID != nil {
|
||||||
err = json.Unmarshal([]byte(systemSettingStorageServiceID.Value), &storageServiceID)
|
err = json.Unmarshal([]byte(systemSettingStorageServiceID.Value), &storageServiceID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -164,23 +209,23 @@ func (s *Server) registerResourceRoutes(g *echo.Group) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
publicID := common.GenUUID()
|
publicID := common.GenUUID()
|
||||||
if storageServiceID == apiv1.DatabaseStorage {
|
if storageServiceID == DatabaseStorage {
|
||||||
fileBytes, err := io.ReadAll(sourceFile)
|
fileBytes, err := io.ReadAll(sourceFile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to read file").SetInternal(err)
|
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to read file").SetInternal(err)
|
||||||
}
|
}
|
||||||
resourceCreate = &api.ResourceCreate{
|
create = &store.Resource{
|
||||||
CreatorID: userID,
|
CreatorID: userID,
|
||||||
Filename: file.Filename,
|
Filename: file.Filename,
|
||||||
Type: filetype,
|
Type: filetype,
|
||||||
Size: size,
|
Size: size,
|
||||||
Blob: fileBytes,
|
Blob: fileBytes,
|
||||||
}
|
}
|
||||||
} else if storageServiceID == apiv1.LocalStorage {
|
} else if storageServiceID == LocalStorage {
|
||||||
// filepath.Join() should be used for local file paths,
|
// filepath.Join() should be used for local file paths,
|
||||||
// as it handles the os-specific path separator automatically.
|
// as it handles the os-specific path separator automatically.
|
||||||
// path.Join() always uses '/' as path separator.
|
// path.Join() always uses '/' as path separator.
|
||||||
systemSettingLocalStoragePath, err := s.Store.GetSystemSetting(ctx, &store.FindSystemSetting{Name: apiv1.SystemSettingLocalStoragePathName.String()})
|
systemSettingLocalStoragePath, err := s.Store.GetSystemSetting(ctx, &store.FindSystemSetting{Name: SystemSettingLocalStoragePathName.String()})
|
||||||
if err != nil && common.ErrorCode(err) != common.NotFound {
|
if err != nil && common.ErrorCode(err) != common.NotFound {
|
||||||
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to find local storage path setting").SetInternal(err)
|
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to find local storage path setting").SetInternal(err)
|
||||||
}
|
}
|
||||||
@ -211,7 +256,7 @@ func (s *Server) registerResourceRoutes(g *echo.Group) {
|
|||||||
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to copy file").SetInternal(err)
|
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to copy file").SetInternal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
resourceCreate = &api.ResourceCreate{
|
create = &store.Resource{
|
||||||
CreatorID: userID,
|
CreatorID: userID,
|
||||||
Filename: file.Filename,
|
Filename: file.Filename,
|
||||||
Type: filetype,
|
Type: filetype,
|
||||||
@ -223,12 +268,12 @@ func (s *Server) registerResourceRoutes(g *echo.Group) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to find storage").SetInternal(err)
|
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to find storage").SetInternal(err)
|
||||||
}
|
}
|
||||||
storageMessage, err := apiv1.ConvertStorageFromStore(storage)
|
storageMessage, err := ConvertStorageFromStore(storage)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to convert storage").SetInternal(err)
|
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to convert storage").SetInternal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if storageMessage.Type == apiv1.StorageS3 {
|
if storageMessage.Type == StorageS3 {
|
||||||
s3Config := storageMessage.Config.S3Config
|
s3Config := storageMessage.Config.S3Config
|
||||||
s3Client, err := s3.NewClient(ctx, &s3.Config{
|
s3Client, err := s3.NewClient(ctx, &s3.Config{
|
||||||
AccessKey: s3Config.AccessKey,
|
AccessKey: s3Config.AccessKey,
|
||||||
@ -253,7 +298,7 @@ func (s *Server) registerResourceRoutes(g *echo.Group) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to upload via s3 client").SetInternal(err)
|
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to upload via s3 client").SetInternal(err)
|
||||||
}
|
}
|
||||||
resourceCreate = &api.ResourceCreate{
|
create = &store.Resource{
|
||||||
CreatorID: userID,
|
CreatorID: userID,
|
||||||
Filename: filename,
|
Filename: filename,
|
||||||
Type: filetype,
|
Type: filetype,
|
||||||
@ -265,15 +310,15 @@ func (s *Server) registerResourceRoutes(g *echo.Group) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
resourceCreate.PublicID = publicID
|
create.PublicID = publicID
|
||||||
resource, err := s.Store.CreateResource(ctx, resourceCreate)
|
resource, err := s.Store.CreateResourceV1(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)
|
||||||
}
|
}
|
||||||
if err := s.createResourceCreateActivity(ctx, resource); err != nil {
|
if err := s.createResourceCreateActivity(ctx, resource); err != nil {
|
||||||
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to create activity").SetInternal(err)
|
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to create activity").SetInternal(err)
|
||||||
}
|
}
|
||||||
return c.JSON(http.StatusOK, composeResponse(resource))
|
return c.JSON(http.StatusOK, convertResourceFromStore(resource))
|
||||||
})
|
})
|
||||||
|
|
||||||
g.GET("/resource", func(c echo.Context) error {
|
g.GET("/resource", func(c echo.Context) error {
|
||||||
@ -282,21 +327,25 @@ func (s *Server) registerResourceRoutes(g *echo.Group) {
|
|||||||
if !ok {
|
if !ok {
|
||||||
return echo.NewHTTPError(http.StatusUnauthorized, "Missing user in session")
|
return echo.NewHTTPError(http.StatusUnauthorized, "Missing user in session")
|
||||||
}
|
}
|
||||||
resourceFind := &api.ResourceFind{
|
find := &store.FindResource{
|
||||||
CreatorID: &userID,
|
CreatorID: &userID,
|
||||||
}
|
}
|
||||||
if limit, err := strconv.Atoi(c.QueryParam("limit")); err == nil {
|
if limit, err := strconv.Atoi(c.QueryParam("limit")); err == nil {
|
||||||
resourceFind.Limit = &limit
|
find.Limit = &limit
|
||||||
}
|
}
|
||||||
if offset, err := strconv.Atoi(c.QueryParam("offset")); err == nil {
|
if offset, err := strconv.Atoi(c.QueryParam("offset")); err == nil {
|
||||||
resourceFind.Offset = &offset
|
find.Offset = &offset
|
||||||
}
|
}
|
||||||
|
|
||||||
list, err := s.Store.FindResourceList(ctx, resourceFind)
|
list, err := s.Store.ListResources(ctx, find)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to fetch resource list").SetInternal(err)
|
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to fetch resource list").SetInternal(err)
|
||||||
}
|
}
|
||||||
return c.JSON(http.StatusOK, composeResponse(list))
|
resourceMessageList := []*Resource{}
|
||||||
|
for _, resource := range list {
|
||||||
|
resourceMessageList = append(resourceMessageList, convertResourceFromStore(resource))
|
||||||
|
}
|
||||||
|
return c.JSON(http.StatusOK, resourceMessageList)
|
||||||
})
|
})
|
||||||
|
|
||||||
g.PATCH("/resource/:resourceId", func(c echo.Context) error {
|
g.PATCH("/resource/:resourceId", func(c echo.Context) error {
|
||||||
@ -311,10 +360,9 @@ func (s *Server) registerResourceRoutes(g *echo.Group) {
|
|||||||
return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("ID is not a number: %s", c.Param("resourceId"))).SetInternal(err)
|
return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("ID is not a number: %s", c.Param("resourceId"))).SetInternal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
resourceFind := &api.ResourceFind{
|
resource, err := s.Store.GetResource(ctx, &store.FindResource{
|
||||||
ID: &resourceID,
|
ID: &resourceID,
|
||||||
}
|
})
|
||||||
resource, err := s.Store.FindResource(ctx, resourceFind)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to find resource").SetInternal(err)
|
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to find resource").SetInternal(err)
|
||||||
}
|
}
|
||||||
@ -322,25 +370,29 @@ func (s *Server) registerResourceRoutes(g *echo.Group) {
|
|||||||
return echo.NewHTTPError(http.StatusUnauthorized, "Unauthorized")
|
return echo.NewHTTPError(http.StatusUnauthorized, "Unauthorized")
|
||||||
}
|
}
|
||||||
|
|
||||||
currentTs := time.Now().Unix()
|
request := &UpdateResourceRequest{}
|
||||||
resourcePatch := &api.ResourcePatch{
|
if err := json.NewDecoder(c.Request().Body).Decode(request); err != nil {
|
||||||
UpdatedTs: ¤tTs,
|
|
||||||
}
|
|
||||||
if err := json.NewDecoder(c.Request().Body).Decode(resourcePatch); err != nil {
|
|
||||||
return echo.NewHTTPError(http.StatusBadRequest, "Malformatted patch resource request").SetInternal(err)
|
return echo.NewHTTPError(http.StatusBadRequest, "Malformatted patch resource request").SetInternal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if resourcePatch.ResetPublicID != nil && *resourcePatch.ResetPublicID {
|
currentTs := time.Now().Unix()
|
||||||
|
update := &store.UpdateResource{
|
||||||
|
ID: resourceID,
|
||||||
|
UpdatedTs: ¤tTs,
|
||||||
|
}
|
||||||
|
if request.Filename != nil && *request.Filename != "" {
|
||||||
|
update.Filename = request.Filename
|
||||||
|
}
|
||||||
|
if request.ResetPublicID != nil && *request.ResetPublicID {
|
||||||
publicID := common.GenUUID()
|
publicID := common.GenUUID()
|
||||||
resourcePatch.PublicID = &publicID
|
update.PublicID = &publicID
|
||||||
}
|
}
|
||||||
|
|
||||||
resourcePatch.ID = resourceID
|
resource, err = s.Store.UpdateResource(ctx, update)
|
||||||
resource, err = s.Store.PatchResource(ctx, resourcePatch)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to patch resource").SetInternal(err)
|
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to patch resource").SetInternal(err)
|
||||||
}
|
}
|
||||||
return c.JSON(http.StatusOK, composeResponse(resource))
|
return c.JSON(http.StatusOK, convertResourceFromStore(resource))
|
||||||
})
|
})
|
||||||
|
|
||||||
g.DELETE("/resource/:resourceId", func(c echo.Context) error {
|
g.DELETE("/resource/:resourceId", func(c echo.Context) error {
|
||||||
@ -355,15 +407,15 @@ func (s *Server) registerResourceRoutes(g *echo.Group) {
|
|||||||
return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("ID is not a number: %s", c.Param("resourceId"))).SetInternal(err)
|
return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("ID is not a number: %s", c.Param("resourceId"))).SetInternal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
resource, err := s.Store.FindResource(ctx, &api.ResourceFind{
|
resource, err := s.Store.GetResource(ctx, &store.FindResource{
|
||||||
ID: &resourceID,
|
ID: &resourceID,
|
||||||
CreatorID: &userID,
|
CreatorID: &userID,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to find resource").SetInternal(err)
|
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to find resource").SetInternal(err)
|
||||||
}
|
}
|
||||||
if resource.CreatorID != userID {
|
if resource == nil {
|
||||||
return echo.NewHTTPError(http.StatusUnauthorized, "Unauthorized")
|
return echo.NewHTTPError(http.StatusNotFound, "Resource not found")
|
||||||
}
|
}
|
||||||
|
|
||||||
if resource.InternalPath != "" {
|
if resource.InternalPath != "" {
|
||||||
@ -378,20 +430,16 @@ func (s *Server) 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))
|
||||||
}
|
}
|
||||||
|
|
||||||
resourceDelete := &api.ResourceDelete{
|
if err := s.Store.DeleteResourceV1(ctx, &store.DeleteResource{
|
||||||
ID: resourceID,
|
ID: resourceID,
|
||||||
}
|
}); err != nil {
|
||||||
if err := s.Store.DeleteResource(ctx, resourceDelete); err != nil {
|
|
||||||
if common.ErrorCode(err) == common.NotFound {
|
|
||||||
return echo.NewHTTPError(http.StatusNotFound, fmt.Sprintf("Resource ID not found: %d", resourceID))
|
|
||||||
}
|
|
||||||
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to delete resource").SetInternal(err)
|
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to delete resource").SetInternal(err)
|
||||||
}
|
}
|
||||||
return c.JSON(http.StatusOK, true)
|
return c.JSON(http.StatusOK, true)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) registerResourcePublicRoutes(g *echo.Group) {
|
func (s *APIV1Service) registerResourcePublicRoutes(g *echo.Group) {
|
||||||
f := func(c echo.Context) error {
|
f := func(c echo.Context) error {
|
||||||
ctx := c.Request().Context()
|
ctx := c.Request().Context()
|
||||||
resourceID, err := strconv.Atoi(c.Param("resourceId"))
|
resourceID, err := strconv.Atoi(c.Param("resourceId"))
|
||||||
@ -399,7 +447,7 @@ func (s *Server) registerResourcePublicRoutes(g *echo.Group) {
|
|||||||
return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("ID is not a number: %s", c.Param("resourceId"))).SetInternal(err)
|
return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("ID is not a number: %s", c.Param("resourceId"))).SetInternal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
resourceVisibility, err := CheckResourceVisibility(ctx, s.Store, resourceID)
|
resourceVisibility, err := checkResourceVisibility(ctx, s.Store, resourceID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return echo.NewHTTPError(http.StatusBadRequest, "Failed to get resource visibility").SetInternal(err)
|
return echo.NewHTTPError(http.StatusBadRequest, "Failed to get resource visibility").SetInternal(err)
|
||||||
}
|
}
|
||||||
@ -410,11 +458,10 @@ func (s *Server) registerResourcePublicRoutes(g *echo.Group) {
|
|||||||
return echo.NewHTTPError(http.StatusUnauthorized, "Resource visibility not match").SetInternal(err)
|
return echo.NewHTTPError(http.StatusUnauthorized, "Resource visibility not match").SetInternal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
resourceFind := &api.ResourceFind{
|
resource, err := s.Store.GetResource(ctx, &store.FindResource{
|
||||||
ID: &resourceID,
|
ID: &resourceID,
|
||||||
GetBlob: true,
|
GetBlob: true,
|
||||||
}
|
})
|
||||||
resource, err := s.Store.FindResource(ctx, resourceFind)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return echo.NewHTTPError(http.StatusInternalServerError, fmt.Sprintf("Failed to find resource by ID: %v", resourceID)).SetInternal(err)
|
return echo.NewHTTPError(http.StatusInternalServerError, fmt.Sprintf("Failed to find resource by ID: %v", resourceID)).SetInternal(err)
|
||||||
}
|
}
|
||||||
@ -465,8 +512,8 @@ func (s *Server) registerResourcePublicRoutes(g *echo.Group) {
|
|||||||
g.GET("/r/:resourceId/*", f)
|
g.GET("/r/:resourceId/*", f)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) createResourceCreateActivity(ctx context.Context, resource *api.Resource) error {
|
func (s *APIV1Service) createResourceCreateActivity(ctx context.Context, resource *store.Resource) error {
|
||||||
payload := apiv1.ActivityResourceCreatePayload{
|
payload := ActivityResourceCreatePayload{
|
||||||
Filename: resource.Filename,
|
Filename: resource.Filename,
|
||||||
Type: resource.Type,
|
Type: resource.Type,
|
||||||
Size: resource.Size,
|
Size: resource.Size,
|
||||||
@ -477,8 +524,8 @@ func (s *Server) createResourceCreateActivity(ctx context.Context, resource *api
|
|||||||
}
|
}
|
||||||
activity, err := s.Store.CreateActivity(ctx, &store.ActivityMessage{
|
activity, err := s.Store.CreateActivity(ctx, &store.ActivityMessage{
|
||||||
CreatorID: resource.CreatorID,
|
CreatorID: resource.CreatorID,
|
||||||
Type: apiv1.ActivityResourceCreate.String(),
|
Type: ActivityResourceCreate.String(),
|
||||||
Level: apiv1.ActivityInfo.String(),
|
Level: ActivityInfo.String(),
|
||||||
Payload: string(payloadBytes),
|
Payload: string(payloadBytes),
|
||||||
})
|
})
|
||||||
if err != nil || activity == nil {
|
if err != nil || activity == nil {
|
||||||
@ -560,12 +607,10 @@ func getOrGenerateThumbnailImage(srcBlob []byte, dstPath string) ([]byte, error)
|
|||||||
return dstBlob, nil
|
return dstBlob, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func CheckResourceVisibility(ctx context.Context, s *store.Store, resourceID int) (store.Visibility, error) {
|
func checkResourceVisibility(ctx context.Context, s *store.Store, resourceID int) (store.Visibility, error) {
|
||||||
memoResourceFind := &api.MemoResourceFind{
|
memoResources, err := s.ListMemoResources(ctx, &store.FindMemoResource{
|
||||||
ResourceID: &resourceID,
|
ResourceID: &resourceID,
|
||||||
}
|
})
|
||||||
|
|
||||||
memoResources, err := s.FindMemoResourceList(ctx, memoResourceFind)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return store.Private, err
|
return store.Private, err
|
||||||
}
|
}
|
||||||
@ -604,3 +649,20 @@ func CheckResourceVisibility(ctx context.Context, s *store.Store, resourceID int
|
|||||||
// If all memo is PRIVATE, the resource do
|
// If all memo is PRIVATE, the resource do
|
||||||
return store.Private, nil
|
return store.Private, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func convertResourceFromStore(resource *store.Resource) *Resource {
|
||||||
|
return &Resource{
|
||||||
|
ID: resource.ID,
|
||||||
|
CreatorID: resource.CreatorID,
|
||||||
|
CreatedTs: resource.CreatedTs,
|
||||||
|
UpdatedTs: resource.UpdatedTs,
|
||||||
|
Filename: resource.Filename,
|
||||||
|
Blob: resource.Blob,
|
||||||
|
InternalPath: resource.InternalPath,
|
||||||
|
ExternalLink: resource.ExternalLink,
|
||||||
|
Type: resource.Type,
|
||||||
|
Size: resource.Size,
|
||||||
|
PublicID: resource.PublicID,
|
||||||
|
LinkedMemoAmount: resource.LinkedMemoAmount,
|
||||||
|
}
|
||||||
|
}
|
@ -34,4 +34,11 @@ func (s *APIV1Service) Register(rootGroup *echo.Group) {
|
|||||||
s.registerTagRoutes(apiV1Group)
|
s.registerTagRoutes(apiV1Group)
|
||||||
s.registerShortcutRoutes(apiV1Group)
|
s.registerShortcutRoutes(apiV1Group)
|
||||||
s.registerStorageRoutes(apiV1Group)
|
s.registerStorageRoutes(apiV1Group)
|
||||||
|
s.registerResourceRoutes(apiV1Group)
|
||||||
|
|
||||||
|
publicGroup := rootGroup.Group("/o")
|
||||||
|
publicGroup.Use(func(next echo.HandlerFunc) echo.HandlerFunc {
|
||||||
|
return JWTMiddleware(s, next, s.Secret)
|
||||||
|
})
|
||||||
|
s.registerResourcePublicRoutes(publicGroup)
|
||||||
}
|
}
|
||||||
|
@ -685,13 +685,15 @@ func (s *Server) composeMemoMessageToMemoResponse(ctx context.Context, memoMessa
|
|||||||
|
|
||||||
resourceList := []*api.Resource{}
|
resourceList := []*api.Resource{}
|
||||||
for _, resourceID := range memoMessage.ResourceIDList {
|
for _, resourceID := range memoMessage.ResourceIDList {
|
||||||
resource, err := s.Store.FindResource(ctx, &api.ResourceFind{
|
resource, err := s.Store.GetResource(ctx, &store.FindResource{
|
||||||
ID: &resourceID,
|
ID: &resourceID,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
resourceList = append(resourceList, resource)
|
if resource != nil {
|
||||||
|
resourceList = append(resourceList, convertResourceFromStore(resource))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
memoResponse.ResourceList = resourceList
|
memoResponse.ResourceList = resourceList
|
||||||
|
|
||||||
@ -714,3 +716,20 @@ func (s *Server) getMemoDisplayWithUpdatedTsSettingValue(ctx context.Context) (b
|
|||||||
}
|
}
|
||||||
return memoDisplayWithUpdatedTs, nil
|
return memoDisplayWithUpdatedTs, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func convertResourceFromStore(resource *store.Resource) *api.Resource {
|
||||||
|
return &api.Resource{
|
||||||
|
ID: resource.ID,
|
||||||
|
CreatorID: resource.CreatorID,
|
||||||
|
CreatedTs: resource.CreatedTs,
|
||||||
|
UpdatedTs: resource.UpdatedTs,
|
||||||
|
Filename: resource.Filename,
|
||||||
|
Blob: resource.Blob,
|
||||||
|
InternalPath: resource.InternalPath,
|
||||||
|
ExternalLink: resource.ExternalLink,
|
||||||
|
Type: resource.Type,
|
||||||
|
Size: resource.Size,
|
||||||
|
PublicID: resource.PublicID,
|
||||||
|
LinkedMemoAmount: resource.LinkedMemoAmount,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -29,10 +29,9 @@ func (s *Server) registerMemoResourceRoutes(g *echo.Group) {
|
|||||||
if err := json.NewDecoder(c.Request().Body).Decode(memoResourceUpsert); err != nil {
|
if err := json.NewDecoder(c.Request().Body).Decode(memoResourceUpsert); err != nil {
|
||||||
return echo.NewHTTPError(http.StatusBadRequest, "Malformatted post memo resource request").SetInternal(err)
|
return echo.NewHTTPError(http.StatusBadRequest, "Malformatted post memo resource request").SetInternal(err)
|
||||||
}
|
}
|
||||||
resourceFind := &api.ResourceFind{
|
resource, err := s.Store.GetResource(ctx, &store.FindResource{
|
||||||
ID: &memoResourceUpsert.ResourceID,
|
ID: &memoResourceUpsert.ResourceID,
|
||||||
}
|
})
|
||||||
resource, err := s.Store.FindResource(ctx, resourceFind)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to fetch resource").SetInternal(err)
|
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to fetch resource").SetInternal(err)
|
||||||
}
|
}
|
||||||
@ -48,7 +47,7 @@ func (s *Server) registerMemoResourceRoutes(g *echo.Group) {
|
|||||||
if _, err := s.Store.UpsertMemoResource(ctx, memoResourceUpsert); err != nil {
|
if _, err := s.Store.UpsertMemoResource(ctx, memoResourceUpsert); err != nil {
|
||||||
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to upsert memo resource").SetInternal(err)
|
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to upsert memo resource").SetInternal(err)
|
||||||
}
|
}
|
||||||
return c.JSON(http.StatusOK, composeResponse(resource))
|
return c.JSON(http.StatusOK, true)
|
||||||
})
|
})
|
||||||
|
|
||||||
g.GET("/memo/:memoId/resource", func(c echo.Context) error {
|
g.GET("/memo/:memoId/resource", func(c echo.Context) error {
|
||||||
@ -58,13 +57,16 @@ func (s *Server) registerMemoResourceRoutes(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)
|
||||||
}
|
}
|
||||||
|
|
||||||
resourceFind := &api.ResourceFind{
|
list, err := s.Store.ListResources(ctx, &store.FindResource{
|
||||||
MemoID: &memoID,
|
MemoID: &memoID,
|
||||||
}
|
})
|
||||||
resourceList, err := s.Store.FindResourceList(ctx, resourceFind)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to fetch resource list").SetInternal(err)
|
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))
|
return c.JSON(http.StatusOK, composeResponse(resourceList))
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -11,7 +11,6 @@ import (
|
|||||||
|
|
||||||
"github.com/gorilla/feeds"
|
"github.com/gorilla/feeds"
|
||||||
"github.com/labstack/echo/v4"
|
"github.com/labstack/echo/v4"
|
||||||
"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/store"
|
"github.com/usememos/memos/store"
|
||||||
@ -102,7 +101,7 @@ func (s *Server) generateRSSFromMemoList(ctx context.Context, memoList []*store.
|
|||||||
}
|
}
|
||||||
if len(memo.ResourceIDList) > 0 {
|
if len(memo.ResourceIDList) > 0 {
|
||||||
resourceID := memo.ResourceIDList[0]
|
resourceID := memo.ResourceIDList[0]
|
||||||
resource, err := s.Store.FindResource(ctx, &api.ResourceFind{
|
resource, err := s.Store.GetResource(ctx, &store.FindResource{
|
||||||
ID: &resourceID,
|
ID: &resourceID,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -92,7 +92,6 @@ func NewServer(ctx context.Context, profile *profile.Profile, store *store.Store
|
|||||||
return JWTMiddleware(s, next, s.Secret)
|
return JWTMiddleware(s, next, s.Secret)
|
||||||
})
|
})
|
||||||
registerGetterPublicRoutes(publicGroup)
|
registerGetterPublicRoutes(publicGroup)
|
||||||
s.registerResourcePublicRoutes(publicGroup)
|
|
||||||
|
|
||||||
apiGroup := e.Group("/api")
|
apiGroup := e.Group("/api")
|
||||||
apiGroup.Use(func(next echo.HandlerFunc) echo.HandlerFunc {
|
apiGroup.Use(func(next echo.HandlerFunc) echo.HandlerFunc {
|
||||||
@ -100,7 +99,6 @@ func NewServer(ctx context.Context, profile *profile.Profile, store *store.Store
|
|||||||
})
|
})
|
||||||
s.registerMemoRoutes(apiGroup)
|
s.registerMemoRoutes(apiGroup)
|
||||||
s.registerMemoResourceRoutes(apiGroup)
|
s.registerMemoResourceRoutes(apiGroup)
|
||||||
s.registerResourceRoutes(apiGroup)
|
|
||||||
s.registerMemoRelationRoutes(apiGroup)
|
s.registerMemoRelationRoutes(apiGroup)
|
||||||
|
|
||||||
apiV1Service := apiv1.NewAPIV1Service(s.Secret, profile, store)
|
apiV1Service := apiv1.NewAPIV1Service(s.Secret, profile, store)
|
||||||
|
@ -90,15 +90,14 @@ func (t *telegramHandler) MessageHandle(ctx context.Context, bot *telegram.Bot,
|
|||||||
case ".png":
|
case ".png":
|
||||||
mime = "image/png"
|
mime = "image/png"
|
||||||
}
|
}
|
||||||
resourceCreate := api.ResourceCreate{
|
resource, err := t.store.CreateResourceV1(ctx, &store.Resource{
|
||||||
CreatorID: creatorID,
|
CreatorID: creatorID,
|
||||||
Filename: filename,
|
Filename: filename,
|
||||||
Type: mime,
|
Type: mime,
|
||||||
Size: int64(len(blob)),
|
Size: int64(len(blob)),
|
||||||
Blob: blob,
|
Blob: blob,
|
||||||
PublicID: common.GenUUID(),
|
PublicID: common.GenUUID(),
|
||||||
}
|
})
|
||||||
resource, err := t.store.CreateResource(ctx, &resourceCreate)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
_, err := bot.EditMessage(ctx, message.Chat.ID, reply.MessageID, fmt.Sprintf("failed to CreateResource: %s", err), nil)
|
_, err := bot.EditMessage(ctx, message.Chat.ID, reply.MessageID, fmt.Sprintf("failed to CreateResource: %s", err), nil)
|
||||||
return err
|
return err
|
||||||
|
@ -10,6 +10,85 @@ import (
|
|||||||
"github.com/usememos/memos/common"
|
"github.com/usememos/memos/common"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type MemoResource 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) {
|
||||||
|
tx, err := s.db.BeginTx(ctx, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, 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{}
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
query := `
|
||||||
|
SELECT
|
||||||
|
memo_id,
|
||||||
|
resource_id,
|
||||||
|
created_ts,
|
||||||
|
updated_ts
|
||||||
|
FROM memo_resource
|
||||||
|
WHERE ` + strings.Join(where, " AND ") + `
|
||||||
|
ORDER BY updated_ts DESC
|
||||||
|
`
|
||||||
|
rows, err := tx.QueryContext(ctx, query, args...)
|
||||||
|
if 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
|
||||||
|
}
|
||||||
|
|
||||||
|
return list, nil
|
||||||
|
}
|
||||||
|
|
||||||
// memoResourceRaw is the store model for an MemoResource.
|
// memoResourceRaw is the store model for an MemoResource.
|
||||||
// Fields have exactly the same meanings as MemoResource.
|
// Fields have exactly the same meanings as MemoResource.
|
||||||
type memoResourceRaw struct {
|
type memoResourceRaw struct {
|
||||||
|
@ -5,14 +5,9 @@ import (
|
|||||||
"database/sql"
|
"database/sql"
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/usememos/memos/api"
|
|
||||||
"github.com/usememos/memos/common"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// resourceRaw is the store model for an Resource.
|
type Resource struct {
|
||||||
// Fields have exactly the same meanings as Resource.
|
|
||||||
type resourceRaw struct {
|
|
||||||
ID int
|
ID int
|
||||||
|
|
||||||
// Standard fields
|
// Standard fields
|
||||||
@ -31,217 +26,177 @@ type resourceRaw struct {
|
|||||||
LinkedMemoAmount int
|
LinkedMemoAmount int
|
||||||
}
|
}
|
||||||
|
|
||||||
func (raw *resourceRaw) toResource() *api.Resource {
|
type FindResource struct {
|
||||||
return &api.Resource{
|
GetBlob bool
|
||||||
ID: raw.ID,
|
ID *int
|
||||||
|
CreatorID *int
|
||||||
// Standard fields
|
Filename *string
|
||||||
CreatorID: raw.CreatorID,
|
MemoID *int
|
||||||
CreatedTs: raw.CreatedTs,
|
PublicID *string
|
||||||
UpdatedTs: raw.UpdatedTs,
|
Limit *int
|
||||||
|
Offset *int
|
||||||
// Domain specific fields
|
|
||||||
Filename: raw.Filename,
|
|
||||||
Blob: raw.Blob,
|
|
||||||
InternalPath: raw.InternalPath,
|
|
||||||
ExternalLink: raw.ExternalLink,
|
|
||||||
Type: raw.Type,
|
|
||||||
Size: raw.Size,
|
|
||||||
PublicID: raw.PublicID,
|
|
||||||
LinkedMemoAmount: raw.LinkedMemoAmount,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Store) CreateResource(ctx context.Context, create *api.ResourceCreate) (*api.Resource, error) {
|
type UpdateResource struct {
|
||||||
|
ID int
|
||||||
|
UpdatedTs *int64
|
||||||
|
Filename *string
|
||||||
|
PublicID *string
|
||||||
|
}
|
||||||
|
|
||||||
|
type DeleteResource struct {
|
||||||
|
ID int
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Store) CreateResourceV1(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)
|
||||||
}
|
}
|
||||||
defer tx.Rollback()
|
defer tx.Rollback()
|
||||||
|
|
||||||
resourceRaw, err := createResourceImpl(ctx, tx, create)
|
if err := tx.QueryRowContext(ctx, `
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := tx.Commit(); err != nil {
|
|
||||||
return nil, FormatError(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
resource := resourceRaw.toResource()
|
|
||||||
|
|
||||||
return resource, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Store) PatchResource(ctx context.Context, patch *api.ResourcePatch) (*api.Resource, error) {
|
|
||||||
tx, err := s.db.BeginTx(ctx, nil)
|
|
||||||
if err != nil {
|
|
||||||
return nil, FormatError(err)
|
|
||||||
}
|
|
||||||
defer tx.Rollback()
|
|
||||||
|
|
||||||
resourceRaw, err := patchResourceImpl(ctx, tx, patch)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := tx.Commit(); err != nil {
|
|
||||||
return nil, FormatError(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
s.resourceCache.Store(resourceRaw.ID, resourceRaw)
|
|
||||||
resource := resourceRaw.toResource()
|
|
||||||
|
|
||||||
return resource, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Store) FindResourceList(ctx context.Context, find *api.ResourceFind) ([]*api.Resource, error) {
|
|
||||||
tx, err := s.db.BeginTx(ctx, nil)
|
|
||||||
if err != nil {
|
|
||||||
return nil, FormatError(err)
|
|
||||||
}
|
|
||||||
defer tx.Rollback()
|
|
||||||
|
|
||||||
resourceRawList, err := findResourceListImpl(ctx, tx, find)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
resourceList := []*api.Resource{}
|
|
||||||
for _, raw := range resourceRawList {
|
|
||||||
if !find.GetBlob {
|
|
||||||
s.resourceCache.Store(raw.ID, raw)
|
|
||||||
}
|
|
||||||
resourceList = append(resourceList, raw.toResource())
|
|
||||||
}
|
|
||||||
|
|
||||||
return resourceList, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Store) FindResource(ctx context.Context, find *api.ResourceFind) (*api.Resource, error) {
|
|
||||||
if !find.GetBlob && find.ID != nil {
|
|
||||||
if raw, ok := s.resourceCache.Load(find.ID); ok {
|
|
||||||
return raw.(*resourceRaw).toResource(), nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
tx, err := s.db.BeginTx(ctx, nil)
|
|
||||||
if err != nil {
|
|
||||||
return nil, FormatError(err)
|
|
||||||
}
|
|
||||||
defer tx.Rollback()
|
|
||||||
|
|
||||||
list, err := findResourceListImpl(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")}
|
|
||||||
}
|
|
||||||
|
|
||||||
resourceRaw := list[0]
|
|
||||||
if !find.GetBlob {
|
|
||||||
s.resourceCache.Store(resourceRaw.ID, resourceRaw)
|
|
||||||
}
|
|
||||||
resource := resourceRaw.toResource()
|
|
||||||
|
|
||||||
return resource, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Store) DeleteResource(ctx context.Context, delete *api.ResourceDelete) error {
|
|
||||||
tx, err := s.db.BeginTx(ctx, nil)
|
|
||||||
if err != nil {
|
|
||||||
return FormatError(err)
|
|
||||||
}
|
|
||||||
defer tx.Rollback()
|
|
||||||
|
|
||||||
if err := deleteResource(ctx, tx, delete); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := s.vacuumImpl(ctx, tx); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := tx.Commit(); err != nil {
|
|
||||||
return FormatError(err)
|
|
||||||
}
|
|
||||||
s.resourceCache.Delete(delete.ID)
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func createResourceImpl(ctx context.Context, tx *sql.Tx, create *api.ResourceCreate) (*resourceRaw, error) {
|
|
||||||
fields := []string{"filename", "blob", "external_link", "type", "size", "creator_id", "internal_path", "public_id"}
|
|
||||||
values := []any{create.Filename, create.Blob, create.ExternalLink, create.Type, create.Size, create.CreatorID, create.InternalPath, create.PublicID}
|
|
||||||
placeholders := []string{"?", "?", "?", "?", "?", "?", "?", "?"}
|
|
||||||
query := `
|
|
||||||
INSERT INTO resource (
|
INSERT INTO resource (
|
||||||
` + strings.Join(fields, ",") + `
|
filename,
|
||||||
|
blob,
|
||||||
|
external_link,
|
||||||
|
type,
|
||||||
|
size,
|
||||||
|
creator_id,
|
||||||
|
internal_path,
|
||||||
|
public_id
|
||||||
)
|
)
|
||||||
VALUES (` + strings.Join(placeholders, ",") + `)
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
||||||
RETURNING id, ` + strings.Join(fields, ",") + `, created_ts, updated_ts
|
RETURNING id, created_ts, updated_ts
|
||||||
`
|
`,
|
||||||
var resourceRaw resourceRaw
|
create.Filename, create.Blob, create.ExternalLink, create.Type, create.Size, create.CreatorID, create.InternalPath, create.PublicID,
|
||||||
dests := []any{
|
).Scan(&create.ID, &create.CreatedTs, &create.UpdatedTs); err != nil {
|
||||||
&resourceRaw.ID,
|
return nil, err
|
||||||
&resourceRaw.Filename,
|
|
||||||
&resourceRaw.Blob,
|
|
||||||
&resourceRaw.ExternalLink,
|
|
||||||
&resourceRaw.Type,
|
|
||||||
&resourceRaw.Size,
|
|
||||||
&resourceRaw.CreatorID,
|
|
||||||
&resourceRaw.InternalPath,
|
|
||||||
&resourceRaw.PublicID,
|
|
||||||
}
|
|
||||||
dests = append(dests, []any{&resourceRaw.CreatedTs, &resourceRaw.UpdatedTs}...)
|
|
||||||
if err := tx.QueryRowContext(ctx, query, values...).Scan(dests...); err != nil {
|
|
||||||
return nil, FormatError(err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return &resourceRaw, nil
|
if err := tx.Commit(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
resource := create
|
||||||
|
return resource, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func patchResourceImpl(ctx context.Context, tx *sql.Tx, patch *api.ResourcePatch) (*resourceRaw, error) {
|
func (s *Store) ListResources(ctx context.Context, find *FindResource) ([]*Resource, error) {
|
||||||
|
tx, err := s.db.BeginTx(ctx, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, FormatError(err)
|
||||||
|
}
|
||||||
|
defer tx.Rollback()
|
||||||
|
|
||||||
|
resources, err := listResources(ctx, tx, find)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := tx.Commit(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return resources, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Store) GetResource(ctx context.Context, find *FindResource) (*Resource, error) {
|
||||||
|
tx, err := s.db.BeginTx(ctx, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, FormatError(err)
|
||||||
|
}
|
||||||
|
defer tx.Rollback()
|
||||||
|
|
||||||
|
resources, err := listResources(ctx, tx, find)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := tx.Commit(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(resources) == 0 {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return resources[0], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Store) UpdateResource(ctx context.Context, update *UpdateResource) (*Resource, error) {
|
||||||
|
tx, err := s.db.BeginTx(ctx, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, FormatError(err)
|
||||||
|
}
|
||||||
|
defer tx.Rollback()
|
||||||
|
|
||||||
set, args := []string{}, []any{}
|
set, args := []string{}, []any{}
|
||||||
|
|
||||||
if v := patch.UpdatedTs; v != nil {
|
if v := update.UpdatedTs; v != nil {
|
||||||
set, args = append(set, "updated_ts = ?"), append(args, *v)
|
set, args = append(set, "updated_ts = ?"), append(args, *v)
|
||||||
}
|
}
|
||||||
if v := patch.Filename; v != nil {
|
if v := update.Filename; v != nil {
|
||||||
set, args = append(set, "filename = ?"), append(args, *v)
|
set, args = append(set, "filename = ?"), append(args, *v)
|
||||||
}
|
}
|
||||||
if v := patch.PublicID; v != nil {
|
if v := update.PublicID; v != nil {
|
||||||
set, args = append(set, "public_id = ?"), append(args, *v)
|
set, args = append(set, "public_id = ?"), append(args, *v)
|
||||||
}
|
}
|
||||||
|
|
||||||
args = append(args, patch.ID)
|
args = append(args, update.ID)
|
||||||
fields := []string{"id", "filename", "external_link", "type", "size", "creator_id", "created_ts", "updated_ts", "internal_path", "public_id"}
|
fields := []string{"id", "filename", "external_link", "type", "size", "creator_id", "created_ts", "updated_ts", "internal_path", "public_id"}
|
||||||
query := `
|
query := `
|
||||||
UPDATE resource
|
UPDATE resource
|
||||||
SET ` + strings.Join(set, ", ") + `
|
SET ` + strings.Join(set, ", ") + `
|
||||||
WHERE id = ?
|
WHERE id = ?
|
||||||
RETURNING ` + strings.Join(fields, ", ")
|
RETURNING ` + strings.Join(fields, ", ")
|
||||||
var resourceRaw resourceRaw
|
resource := Resource{}
|
||||||
dests := []any{
|
dests := []any{
|
||||||
&resourceRaw.ID,
|
&resource.ID,
|
||||||
&resourceRaw.Filename,
|
&resource.Filename,
|
||||||
&resourceRaw.ExternalLink,
|
&resource.ExternalLink,
|
||||||
&resourceRaw.Type,
|
&resource.Type,
|
||||||
&resourceRaw.Size,
|
&resource.Size,
|
||||||
&resourceRaw.CreatorID,
|
&resource.CreatorID,
|
||||||
&resourceRaw.CreatedTs,
|
&resource.CreatedTs,
|
||||||
&resourceRaw.UpdatedTs,
|
&resource.UpdatedTs,
|
||||||
&resourceRaw.InternalPath,
|
&resource.InternalPath,
|
||||||
&resourceRaw.PublicID,
|
&resource.PublicID,
|
||||||
}
|
}
|
||||||
if err := tx.QueryRowContext(ctx, query, args...).Scan(dests...); err != nil {
|
if err := tx.QueryRowContext(ctx, query, args...).Scan(dests...); err != nil {
|
||||||
return nil, FormatError(err)
|
return nil, FormatError(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return &resourceRaw, nil
|
if err := tx.Commit(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &resource, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func findResourceListImpl(ctx context.Context, tx *sql.Tx, find *api.ResourceFind) ([]*resourceRaw, error) {
|
func (s *Store) DeleteResourceV1(ctx context.Context, delete *DeleteResource) error {
|
||||||
|
tx, err := s.db.BeginTx(ctx, nil)
|
||||||
|
if err != nil {
|
||||||
|
return FormatError(err)
|
||||||
|
}
|
||||||
|
defer tx.Rollback()
|
||||||
|
|
||||||
|
if _, err := tx.ExecContext(ctx, `
|
||||||
|
DELETE FROM resource
|
||||||
|
WHERE id = ?
|
||||||
|
`, delete.ID); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := tx.Commit(); err != nil {
|
||||||
|
// Prevent linter warning.
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func listResources(ctx context.Context, tx *sql.Tx, find *FindResource) ([]*Resource, 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 {
|
||||||
@ -288,53 +243,36 @@ func findResourceListImpl(ctx context.Context, tx *sql.Tx, find *api.ResourceFin
|
|||||||
}
|
}
|
||||||
defer rows.Close()
|
defer rows.Close()
|
||||||
|
|
||||||
resourceRawList := make([]*resourceRaw, 0)
|
list := make([]*Resource, 0)
|
||||||
for rows.Next() {
|
for rows.Next() {
|
||||||
var resourceRaw resourceRaw
|
resource := Resource{}
|
||||||
dests := []any{
|
dests := []any{
|
||||||
&resourceRaw.LinkedMemoAmount,
|
&resource.LinkedMemoAmount,
|
||||||
&resourceRaw.ID,
|
&resource.ID,
|
||||||
&resourceRaw.Filename,
|
&resource.Filename,
|
||||||
&resourceRaw.ExternalLink,
|
&resource.ExternalLink,
|
||||||
&resourceRaw.Type,
|
&resource.Type,
|
||||||
&resourceRaw.Size,
|
&resource.Size,
|
||||||
&resourceRaw.CreatorID,
|
&resource.CreatorID,
|
||||||
&resourceRaw.CreatedTs,
|
&resource.CreatedTs,
|
||||||
&resourceRaw.UpdatedTs,
|
&resource.UpdatedTs,
|
||||||
&resourceRaw.InternalPath,
|
&resource.InternalPath,
|
||||||
&resourceRaw.PublicID,
|
&resource.PublicID,
|
||||||
}
|
}
|
||||||
if find.GetBlob {
|
if find.GetBlob {
|
||||||
dests = append(dests, &resourceRaw.Blob)
|
dests = append(dests, &resource.Blob)
|
||||||
}
|
}
|
||||||
if err := rows.Scan(dests...); err != nil {
|
if err := rows.Scan(dests...); err != nil {
|
||||||
return nil, FormatError(err)
|
return nil, FormatError(err)
|
||||||
}
|
}
|
||||||
resourceRawList = append(resourceRawList, &resourceRaw)
|
list = append(list, &resource)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := rows.Err(); err != nil {
|
if err := rows.Err(); err != nil {
|
||||||
return nil, FormatError(err)
|
return nil, FormatError(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return resourceRawList, nil
|
return list, nil
|
||||||
}
|
|
||||||
|
|
||||||
func deleteResource(ctx context.Context, tx *sql.Tx, delete *api.ResourceDelete) error {
|
|
||||||
where, args := []string{"id = ?"}, []any{delete.ID}
|
|
||||||
|
|
||||||
stmt := `DELETE FROM 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("resource not found")}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func vacuumResource(ctx context.Context, tx *sql.Tx) error {
|
func vacuumResource(ctx context.Context, tx *sql.Tx) error {
|
||||||
|
@ -17,7 +17,6 @@ type Store struct {
|
|||||||
userSettingCache sync.Map // map[string]*UserSetting
|
userSettingCache sync.Map // map[string]*UserSetting
|
||||||
shortcutCache sync.Map // map[int]*shortcutRaw
|
shortcutCache sync.Map // map[int]*shortcutRaw
|
||||||
idpCache sync.Map // map[int]*IdentityProvider
|
idpCache sync.Map // map[int]*IdentityProvider
|
||||||
resourceCache sync.Map // map[int]*resourceRaw
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// New creates a new instance of Store.
|
// New creates a new instance of Store.
|
||||||
|
@ -5,13 +5,13 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
"github.com/usememos/memos/api"
|
"github.com/usememos/memos/store"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestResourceStore(t *testing.T) {
|
func TestResourceStore(t *testing.T) {
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
store := NewTestingStore(ctx, t)
|
ts := NewTestingStore(ctx, t)
|
||||||
_, err := store.CreateResource(ctx, &api.ResourceCreate{
|
_, err := ts.CreateResourceV1(ctx, &store.Resource{
|
||||||
CreatorID: 101,
|
CreatorID: 101,
|
||||||
Filename: "test.epub",
|
Filename: "test.epub",
|
||||||
Blob: []byte("test"),
|
Blob: []byte("test"),
|
||||||
@ -25,34 +25,36 @@ func TestResourceStore(t *testing.T) {
|
|||||||
|
|
||||||
correctFilename := "test.epub"
|
correctFilename := "test.epub"
|
||||||
incorrectFilename := "test.png"
|
incorrectFilename := "test.png"
|
||||||
res, err := store.FindResource(ctx, &api.ResourceFind{
|
res, err := ts.GetResource(ctx, &store.FindResource{
|
||||||
Filename: &correctFilename,
|
Filename: &correctFilename,
|
||||||
})
|
})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Equal(t, correctFilename, res.Filename)
|
require.Equal(t, correctFilename, res.Filename)
|
||||||
require.Equal(t, 1, res.ID)
|
require.Equal(t, 1, res.ID)
|
||||||
_, err = store.FindResource(ctx, &api.ResourceFind{
|
notFoundResource, err := ts.GetResource(ctx, &store.FindResource{
|
||||||
Filename: &incorrectFilename,
|
Filename: &incorrectFilename,
|
||||||
})
|
})
|
||||||
require.Error(t, err)
|
require.NoError(t, err)
|
||||||
|
require.Nil(t, notFoundResource)
|
||||||
|
|
||||||
correctCreatorID := 101
|
correctCreatorID := 101
|
||||||
incorrectCreatorID := 102
|
incorrectCreatorID := 102
|
||||||
_, err = store.FindResource(ctx, &api.ResourceFind{
|
_, err = ts.GetResource(ctx, &store.FindResource{
|
||||||
CreatorID: &correctCreatorID,
|
CreatorID: &correctCreatorID,
|
||||||
})
|
})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
_, err = store.FindResource(ctx, &api.ResourceFind{
|
notFoundResource, err = ts.GetResource(ctx, &store.FindResource{
|
||||||
CreatorID: &incorrectCreatorID,
|
CreatorID: &incorrectCreatorID,
|
||||||
})
|
})
|
||||||
require.Error(t, err)
|
require.NoError(t, err)
|
||||||
|
require.Nil(t, notFoundResource)
|
||||||
|
|
||||||
err = store.DeleteResource(ctx, &api.ResourceDelete{
|
err = ts.DeleteResourceV1(ctx, &store.DeleteResource{
|
||||||
ID: 1,
|
ID: 1,
|
||||||
})
|
})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
err = store.DeleteResource(ctx, &api.ResourceDelete{
|
err = ts.DeleteResourceV1(ctx, &store.DeleteResource{
|
||||||
ID: 2,
|
ID: 2,
|
||||||
})
|
})
|
||||||
require.Error(t, err)
|
require.NoError(t, err)
|
||||||
}
|
}
|
||||||
|
@ -161,7 +161,7 @@ export function deleteShortcutById(shortcutId: ShortcutId) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function getResourceList() {
|
export function getResourceList() {
|
||||||
return axios.get<ResponseObject<Resource[]>>("/api/resource");
|
return axios.get<Resource[]>("/api/v1/resource");
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getResourceListWithLimit(resourceFind?: ResourceFind) {
|
export function getResourceListWithLimit(resourceFind?: ResourceFind) {
|
||||||
@ -172,23 +172,23 @@ export function getResourceListWithLimit(resourceFind?: ResourceFind) {
|
|||||||
if (resourceFind?.limit) {
|
if (resourceFind?.limit) {
|
||||||
queryList.push(`limit=${resourceFind.limit}`);
|
queryList.push(`limit=${resourceFind.limit}`);
|
||||||
}
|
}
|
||||||
return axios.get<ResponseObject<Resource[]>>(`/api/resource?${queryList.join("&")}`);
|
return axios.get<Resource[]>(`/api/v1/resource?${queryList.join("&")}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function createResource(resourceCreate: ResourceCreate) {
|
export function createResource(resourceCreate: ResourceCreate) {
|
||||||
return axios.post<ResponseObject<Resource>>("/api/resource", resourceCreate);
|
return axios.post<Resource>("/api/v1/resource", resourceCreate);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function createResourceWithBlob(formData: FormData) {
|
export function createResourceWithBlob(formData: FormData) {
|
||||||
return axios.post<ResponseObject<Resource>>("/api/resource/blob", formData);
|
return axios.post<Resource>("/api/v1/resource/blob", formData);
|
||||||
}
|
|
||||||
|
|
||||||
export function deleteResourceById(id: ResourceId) {
|
|
||||||
return axios.delete(`/api/resource/${id}`);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function patchResource(resourcePatch: ResourcePatch) {
|
export function patchResource(resourcePatch: ResourcePatch) {
|
||||||
return axios.patch<ResponseObject<Resource>>(`/api/resource/${resourcePatch.id}`, resourcePatch);
|
return axios.patch<Resource>(`/api/v1/resource/${resourcePatch.id}`, resourcePatch);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function deleteResourceById(id: ResourceId) {
|
||||||
|
return axios.delete(`/api/v1/resource/${id}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getMemoResourceList(memoId: MemoId) {
|
export function getMemoResourceList(memoId: MemoId) {
|
||||||
@ -196,7 +196,7 @@ export function getMemoResourceList(memoId: MemoId) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function upsertMemoResource(memoId: MemoId, resourceId: ResourceId) {
|
export function upsertMemoResource(memoId: MemoId, resourceId: ResourceId) {
|
||||||
return axios.post<ResponseObject<Resource>>(`/api/memo/${memoId}/resource`, {
|
return axios.post(`/api/memo/${memoId}/resource`, {
|
||||||
resourceId,
|
resourceId,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -25,7 +25,7 @@ export const useResourceStore = () => {
|
|||||||
return store.getState().resource;
|
return store.getState().resource;
|
||||||
},
|
},
|
||||||
async fetchResourceList(): Promise<Resource[]> {
|
async fetchResourceList(): Promise<Resource[]> {
|
||||||
const { data } = (await api.getResourceList()).data;
|
const { data } = await api.getResourceList();
|
||||||
const resourceList = data.map((m) => convertResponseModelResource(m));
|
const resourceList = data.map((m) => convertResponseModelResource(m));
|
||||||
store.dispatch(setResources(resourceList));
|
store.dispatch(setResources(resourceList));
|
||||||
return resourceList;
|
return resourceList;
|
||||||
@ -35,13 +35,13 @@ export const useResourceStore = () => {
|
|||||||
limit,
|
limit,
|
||||||
offset,
|
offset,
|
||||||
};
|
};
|
||||||
const { data } = (await api.getResourceListWithLimit(resourceFind)).data;
|
const { data } = await api.getResourceListWithLimit(resourceFind);
|
||||||
const resourceList = data.map((m) => convertResponseModelResource(m));
|
const resourceList = data.map((m) => convertResponseModelResource(m));
|
||||||
store.dispatch(upsertResources(resourceList));
|
store.dispatch(upsertResources(resourceList));
|
||||||
return resourceList;
|
return resourceList;
|
||||||
},
|
},
|
||||||
async createResource(resourceCreate: ResourceCreate): Promise<Resource> {
|
async createResource(resourceCreate: ResourceCreate): Promise<Resource> {
|
||||||
const { data } = (await api.createResource(resourceCreate)).data;
|
const { data } = await api.createResource(resourceCreate);
|
||||||
const resource = convertResponseModelResource(data);
|
const resource = convertResponseModelResource(data);
|
||||||
const resourceList = state.resources;
|
const resourceList = state.resources;
|
||||||
store.dispatch(setResources([resource, ...resourceList]));
|
store.dispatch(setResources([resource, ...resourceList]));
|
||||||
@ -55,7 +55,7 @@ export const useResourceStore = () => {
|
|||||||
|
|
||||||
const formData = new FormData();
|
const formData = new FormData();
|
||||||
formData.append("file", file, filename);
|
formData.append("file", file, filename);
|
||||||
const { data } = (await api.createResourceWithBlob(formData)).data;
|
const { data } = await api.createResourceWithBlob(formData);
|
||||||
const resource = convertResponseModelResource(data);
|
const resource = convertResponseModelResource(data);
|
||||||
const resourceList = state.resources;
|
const resourceList = state.resources;
|
||||||
store.dispatch(setResources([resource, ...resourceList]));
|
store.dispatch(setResources([resource, ...resourceList]));
|
||||||
@ -71,7 +71,7 @@ export const useResourceStore = () => {
|
|||||||
|
|
||||||
const formData = new FormData();
|
const formData = new FormData();
|
||||||
formData.append("file", file, filename);
|
formData.append("file", file, filename);
|
||||||
const { data } = (await api.createResourceWithBlob(formData)).data;
|
const { data } = await api.createResourceWithBlob(formData);
|
||||||
const resource = convertResponseModelResource(data);
|
const resource = convertResponseModelResource(data);
|
||||||
newResourceList = [resource, ...newResourceList];
|
newResourceList = [resource, ...newResourceList];
|
||||||
}
|
}
|
||||||
@ -84,7 +84,7 @@ export const useResourceStore = () => {
|
|||||||
store.dispatch(deleteResource(id));
|
store.dispatch(deleteResource(id));
|
||||||
},
|
},
|
||||||
async patchResource(resourcePatch: ResourcePatch): Promise<Resource> {
|
async patchResource(resourcePatch: ResourcePatch): Promise<Resource> {
|
||||||
const { data } = (await api.patchResource(resourcePatch)).data;
|
const { data } = await api.patchResource(resourcePatch);
|
||||||
const resource = convertResponseModelResource(data);
|
const resource = convertResponseModelResource(data);
|
||||||
store.dispatch(patchResource(resource));
|
store.dispatch(patchResource(resource));
|
||||||
return resource;
|
return resource;
|
||||||
|
Reference in New Issue
Block a user