Unverified Commit 1fa9f162 authored by boojack's avatar boojack Committed by GitHub

refactor: migrate resource to apiv1 (#1901)

parent 5ea561af
......@@ -20,51 +20,3 @@ type Resource struct {
// Related fields
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
}
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
}
This diff is collapsed.
......@@ -34,4 +34,11 @@ func (s *APIV1Service) Register(rootGroup *echo.Group) {
s.registerTagRoutes(apiV1Group)
s.registerShortcutRoutes(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{}
for _, resourceID := range memoMessage.ResourceIDList {
resource, err := s.Store.FindResource(ctx, &api.ResourceFind{
resource, err := s.Store.GetResource(ctx, &store.FindResource{
ID: &resourceID,
})
if err != nil {
return nil, err
}
resourceList = append(resourceList, resource)
if resource != nil {
resourceList = append(resourceList, convertResourceFromStore(resource))
}
}
memoResponse.ResourceList = resourceList
......@@ -714,3 +716,20 @@ func (s *Server) getMemoDisplayWithUpdatedTsSettingValue(ctx context.Context) (b
}
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 {
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,
}
resource, err := s.Store.FindResource(ctx, resourceFind)
})
if err != nil {
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 {
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 {
......@@ -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)
}
resourceFind := &api.ResourceFind{
list, err := s.Store.ListResources(ctx, &store.FindResource{
MemoID: &memoID,
}
resourceList, err := s.Store.FindResourceList(ctx, resourceFind)
})
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))
})
......
......@@ -11,7 +11,6 @@ import (
"github.com/gorilla/feeds"
"github.com/labstack/echo/v4"
"github.com/usememos/memos/api"
apiv1 "github.com/usememos/memos/api/v1"
"github.com/usememos/memos/common"
"github.com/usememos/memos/store"
......@@ -102,7 +101,7 @@ func (s *Server) generateRSSFromMemoList(ctx context.Context, memoList []*store.
}
if len(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,
})
if err != nil {
......
......@@ -92,7 +92,6 @@ func NewServer(ctx context.Context, profile *profile.Profile, store *store.Store
return JWTMiddleware(s, next, s.Secret)
})
registerGetterPublicRoutes(publicGroup)
s.registerResourcePublicRoutes(publicGroup)
apiGroup := e.Group("/api")
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.registerMemoResourceRoutes(apiGroup)
s.registerResourceRoutes(apiGroup)
s.registerMemoRelationRoutes(apiGroup)
apiV1Service := apiv1.NewAPIV1Service(s.Secret, profile, store)
......
......@@ -90,15 +90,14 @@ func (t *telegramHandler) MessageHandle(ctx context.Context, bot *telegram.Bot,
case ".png":
mime = "image/png"
}
resourceCreate := api.ResourceCreate{
resource, err := t.store.CreateResourceV1(ctx, &store.Resource{
CreatorID: creatorID,
Filename: filename,
Type: mime,
Size: int64(len(blob)),
Blob: blob,
PublicID: common.GenUUID(),
}
resource, err := t.store.CreateResource(ctx, &resourceCreate)
})
if err != nil {
_, err := bot.EditMessage(ctx, message.Chat.ID, reply.MessageID, fmt.Sprintf("failed to CreateResource: %s", err), nil)
return err
......
......@@ -10,6 +10,85 @@ import (
"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.
// Fields have exactly the same meanings as MemoResource.
type memoResourceRaw struct {
......
This diff is collapsed.
......@@ -17,7 +17,6 @@ type Store struct {
userSettingCache sync.Map // map[string]*UserSetting
shortcutCache sync.Map // map[int]*shortcutRaw
idpCache sync.Map // map[int]*IdentityProvider
resourceCache sync.Map // map[int]*resourceRaw
}
// New creates a new instance of Store.
......
......@@ -5,13 +5,13 @@ import (
"testing"
"github.com/stretchr/testify/require"
"github.com/usememos/memos/api"
"github.com/usememos/memos/store"
)
func TestResourceStore(t *testing.T) {
ctx := context.Background()
store := NewTestingStore(ctx, t)
_, err := store.CreateResource(ctx, &api.ResourceCreate{
ts := NewTestingStore(ctx, t)
_, err := ts.CreateResourceV1(ctx, &store.Resource{
CreatorID: 101,
Filename: "test.epub",
Blob: []byte("test"),
......@@ -25,34 +25,36 @@ func TestResourceStore(t *testing.T) {
correctFilename := "test.epub"
incorrectFilename := "test.png"
res, err := store.FindResource(ctx, &api.ResourceFind{
res, err := ts.GetResource(ctx, &store.FindResource{
Filename: &correctFilename,
})
require.NoError(t, err)
require.Equal(t, correctFilename, res.Filename)
require.Equal(t, 1, res.ID)
_, err = store.FindResource(ctx, &api.ResourceFind{
notFoundResource, err := ts.GetResource(ctx, &store.FindResource{
Filename: &incorrectFilename,
})
require.Error(t, err)
require.NoError(t, err)
require.Nil(t, notFoundResource)
correctCreatorID := 101
incorrectCreatorID := 102
_, err = store.FindResource(ctx, &api.ResourceFind{
_, err = ts.GetResource(ctx, &store.FindResource{
CreatorID: &correctCreatorID,
})
require.NoError(t, err)
_, err = store.FindResource(ctx, &api.ResourceFind{
notFoundResource, err = ts.GetResource(ctx, &store.FindResource{
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,
})
require.NoError(t, err)
err = store.DeleteResource(ctx, &api.ResourceDelete{
err = ts.DeleteResourceV1(ctx, &store.DeleteResource{
ID: 2,
})
require.Error(t, err)
require.NoError(t, err)
}
......@@ -161,7 +161,7 @@ export function deleteShortcutById(shortcutId: ShortcutId) {
}
export function getResourceList() {
return axios.get<ResponseObject<Resource[]>>("/api/resource");
return axios.get<Resource[]>("/api/v1/resource");
}
export function getResourceListWithLimit(resourceFind?: ResourceFind) {
......@@ -172,23 +172,23 @@ export function getResourceListWithLimit(resourceFind?: ResourceFind) {
if (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) {
return axios.post<ResponseObject<Resource>>("/api/resource", resourceCreate);
return axios.post<Resource>("/api/v1/resource", resourceCreate);
}
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) {
return axios.patch<Resource>(`/api/v1/resource/${resourcePatch.id}`, resourcePatch);
}
export function patchResource(resourcePatch: ResourcePatch) {
return axios.patch<ResponseObject<Resource>>(`/api/resource/${resourcePatch.id}`, resourcePatch);
export function deleteResourceById(id: ResourceId) {
return axios.delete(`/api/v1/resource/${id}`);
}
export function getMemoResourceList(memoId: MemoId) {
......@@ -196,7 +196,7 @@ export function getMemoResourceList(memoId: MemoId) {
}
export function upsertMemoResource(memoId: MemoId, resourceId: ResourceId) {
return axios.post<ResponseObject<Resource>>(`/api/memo/${memoId}/resource`, {
return axios.post(`/api/memo/${memoId}/resource`, {
resourceId,
});
}
......
......@@ -25,7 +25,7 @@ export const useResourceStore = () => {
return store.getState().resource;
},
async fetchResourceList(): Promise<Resource[]> {
const { data } = (await api.getResourceList()).data;
const { data } = await api.getResourceList();
const resourceList = data.map((m) => convertResponseModelResource(m));
store.dispatch(setResources(resourceList));
return resourceList;
......@@ -35,13 +35,13 @@ export const useResourceStore = () => {
limit,
offset,
};
const { data } = (await api.getResourceListWithLimit(resourceFind)).data;
const { data } = await api.getResourceListWithLimit(resourceFind);
const resourceList = data.map((m) => convertResponseModelResource(m));
store.dispatch(upsertResources(resourceList));
return resourceList;
},
async createResource(resourceCreate: ResourceCreate): Promise<Resource> {
const { data } = (await api.createResource(resourceCreate)).data;
const { data } = await api.createResource(resourceCreate);
const resource = convertResponseModelResource(data);
const resourceList = state.resources;
store.dispatch(setResources([resource, ...resourceList]));
......@@ -55,7 +55,7 @@ export const useResourceStore = () => {
const formData = new FormData();
formData.append("file", file, filename);
const { data } = (await api.createResourceWithBlob(formData)).data;
const { data } = await api.createResourceWithBlob(formData);
const resource = convertResponseModelResource(data);
const resourceList = state.resources;
store.dispatch(setResources([resource, ...resourceList]));
......@@ -71,7 +71,7 @@ export const useResourceStore = () => {
const formData = new FormData();
formData.append("file", file, filename);
const { data } = (await api.createResourceWithBlob(formData)).data;
const { data } = await api.createResourceWithBlob(formData);
const resource = convertResponseModelResource(data);
newResourceList = [resource, ...newResourceList];
}
......@@ -84,7 +84,7 @@ export const useResourceStore = () => {
store.dispatch(deleteResource(id));
},
async patchResource(resourcePatch: ResourcePatch): Promise<Resource> {
const { data } = (await api.patchResource(resourcePatch)).data;
const { data } = await api.patchResource(resourcePatch);
const resource = convertResponseModelResource(data);
store.dispatch(patchResource(resource));
return resource;
......
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