Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(server): implement public api for assets #1277

Merged
merged 9 commits into from
Oct 24, 2024
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 31 additions & 4 deletions server/e2e/publicapi_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -203,16 +203,42 @@ func TestPublicAPI(t *testing.T) {
"error": "not found",
})

e.GET("/api/p/{project}/assets", publicAPIProjectAlias).
Expect().
Status(http.StatusOK).
JSON().
IsEqual(map[string]any{
"hasMore": false,
"limit": 50,
"offset": 0,
"page": 1,
"results": []map[string]any{
map[string]any{
"id": publicAPIAsset1ID.String(),
"type": "asset",
"url": fmt.Sprintf("https://example.com/assets/%s/%s/aaa.zip", publicAPIAssetUUID[:2], publicAPIAssetUUID[2:]),
"contentType": "application/zip",
"files": []string{
fmt.Sprintf("https://example.com/assets/%s/%s/aaa/bbb.txt", publicAPIAssetUUID[:2], publicAPIAssetUUID[2:]),
fmt.Sprintf("https://example.com/assets/%s/%s/aaa/ccc.txt", publicAPIAssetUUID[:2], publicAPIAssetUUID[2:]),
},
},
},
"totalCount": 1,
})

e.GET("/api/p/{project}/assets/{assetid}", publicAPIProjectAlias, publicAPIAsset1ID).
Expect().
Status(http.StatusOK).
JSON().
IsEqual(map[string]any{
"type": "asset",
"id": publicAPIAsset1ID.String(),
"url": fmt.Sprintf("https://example.com/assets/%s/%s/aaa.zip", publicAPIAssetUUID[:2], publicAPIAssetUUID[2:]),
"type": "asset",
"id": publicAPIAsset1ID.String(),
"url": fmt.Sprintf("https://example.com/assets/%s/%s/aaa.zip", publicAPIAssetUUID[:2], publicAPIAssetUUID[2:]),
"contentType": "application/zip",
"files": []string{
fmt.Sprintf("https://example.com/assets/%s/%s/aaa/bbb.txt", publicAPIAssetUUID[:2], publicAPIAssetUUID[2:]),
fmt.Sprintf("https://example.com/assets/%s/%s/aaa/ccc.txt", publicAPIAssetUUID[:2], publicAPIAssetUUID[2:]),
},
})

Expand Down Expand Up @@ -354,7 +380,8 @@ func publicAPISeeder(ctx context.Context, r *repo.Container) error {

a := asset.New().ID(publicAPIAsset1ID).Project(p1.ID()).CreatedByUser(uid).Size(1).Thread(id.NewThreadID()).
FileName("aaa.zip").UUID(publicAPIAssetUUID).MustBuild()
af := asset.NewFile().Name("bbb.txt").Path("aaa/bbb.txt").Build()
c := []*asset.File{asset.NewFile().Name("bbb.txt").Path("aaa/bbb.txt").Build(), asset.NewFile().Name("ccc.txt").Path("aaa/ccc.txt").Build()}
af := asset.NewFile().Name("aaa.zip").Path("aaa.zip").ContentType("application/zip").Size(10).Children(c).Build()

fid := id.NewFieldID()
gst := schema.GeometryObjectSupportedTypeList{schema.GeometryObjectSupportedTypePoint, schema.GeometryObjectSupportedTypeLineString}
Expand Down
31 changes: 20 additions & 11 deletions server/internal/adapter/publicapi/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ func GetController(ctx context.Context) *Controller {

func Echo(e *echo.Group) {
e.Use(middleware.CORS())
e.GET("/:project/:model", PublicApiItemList())
e.GET("/:project/:model", PublicApiItemOrAssetList())
e.GET("/:project/:model/:item", PublicApiItemOrAsset())
}

Expand All @@ -59,33 +59,42 @@ func PublicApiItemOrAsset() echo.HandlerFunc {
}
}

func PublicApiItemList() echo.HandlerFunc {
func PublicApiItemOrAssetList() echo.HandlerFunc {
return func(c echo.Context) error {
ctx := c.Request().Context()
ctrl := GetController(ctx)

mKey := c.Param("model")
pKey := c.Param("project")
p, err := listParamFromEchoContext(c)
if err != nil {
return c.JSON(http.StatusBadRequest, "invalid offset or limit")
}

if mKey == "assets" {
res, err := ctrl.GetAssets(ctx, pKey, p)
if err != nil {
return err
}
return c.JSON(http.StatusOK, res)
}

resType := ""
m := c.Param("model")
if strings.Contains(m, ".") {
m, resType, _ = strings.Cut(m, ".")
if strings.Contains(mKey, ".") {
mKey, resType, _ = strings.Cut(mKey, ".")
}
if resType != "csv" && resType != "json" && resType != "geojson" {
resType = "json"
}

items, _, err := ctrl.GetItems(ctx, c.Param("project"), m, p)
res, _, err := ctrl.GetItems(ctx, pKey, mKey, p)
if err != nil {
return err
}

vi, s, err1 := ctrl.GetVersionedItems(ctx, c.Param("project"), m, p)
if err1 != nil {
return err1
vi, s, err := ctrl.GetVersionedItems(ctx, pKey, mKey, p)
if err != nil {
return err
}

switch resType {
Expand All @@ -94,9 +103,9 @@ func PublicApiItemList() echo.HandlerFunc {
case "geojson":
return toGeoJSON(c, vi, s)
case "json":
return c.JSON(http.StatusOK, items)
return c.JSON(http.StatusOK, res)
default:
return c.JSON(http.StatusOK, items)
return c.JSON(http.StatusOK, res)
}
}
}
Expand Down
37 changes: 37 additions & 0 deletions server/internal/adapter/publicapi/asset.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,12 @@ import (
"context"
"errors"

"github.com/reearth/reearth-cms/server/internal/usecase/interfaces"
"github.com/reearth/reearth-cms/server/pkg/asset"
"github.com/reearth/reearth-cms/server/pkg/id"
"github.com/reearth/reearth-cms/server/pkg/project"
"github.com/reearth/reearthx/rerror"
"github.com/reearth/reearthx/util"
)

func (c *Controller) GetAsset(ctx context.Context, prj, i string) (Asset, error) {
Expand Down Expand Up @@ -34,3 +38,36 @@ func (c *Controller) GetAsset(ctx context.Context, prj, i string) (Asset, error)

return NewAsset(a, f, c.assetUrlResolver), nil
}

func (c *Controller) GetAssets(ctx context.Context, pKey string, p ListParam) (ListResult[Asset], error) {
prj, err := c.checkProject(ctx, pKey)
if err != nil {
return ListResult[Asset]{}, err
}

if prj.Publication().Scope() != project.PublicationScopePublic || !prj.Publication().AssetPublic() {
return ListResult[Asset]{}, rerror.ErrNotFound
}
nourbalaha marked this conversation as resolved.
Show resolved Hide resolved

al, pi, err := c.usecases.Asset.FindByProject(ctx, prj.ID(), interfaces.AssetFilter{
Sort: nil,
Keyword: nil,
Pagination: p.Pagination,
}, nil)

if err != nil {
if errors.Is(err, rerror.ErrNotFound) {
return ListResult[Asset]{}, rerror.ErrNotFound
}
return ListResult[Asset]{}, err
}
nourbalaha marked this conversation as resolved.
Show resolved Hide resolved

fileMap, err := c.usecases.Asset.FindFilesByIDs(ctx, al.IDs(), nil)
if err != nil {
return ListResult[Asset]{}, err
}

return NewListResult(util.Map(al, func(a *asset.Asset) Asset {
return NewAsset(a, fileMap[a.ID()], c.assetUrlResolver)
}), pi, p.Pagination), nil
}
4 changes: 2 additions & 2 deletions server/internal/adapter/publicapi/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -177,9 +177,9 @@ func NewAsset(a *asset.Asset, f *asset.File, urlResolver asset.URLResolver) Asse
base, _ := url.Parse(u)
base.Path = path.Dir(base.Path)

files = lo.Map(f.FlattenChildren(), func(f *asset.File, _ int) string {
files = lo.Map(f.FilePaths(), func(p string, _ int) string {
b := *base
b.Path = path.Join(b.Path, f.Path())
b.Path = path.Join(b.Path, p)
return b.String()
})
}
Expand Down
26 changes: 26 additions & 0 deletions server/internal/infrastructure/memory/asset_file.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,32 @@ func (r *AssetFile) FindByID(ctx context.Context, id id.AssetID) (*asset.File, e
return rerror.ErrIfNil(f, rerror.ErrNotFound)
}

func (r *AssetFile) FindByIDs(ctx context.Context, ids id.AssetIDList) (map[id.AssetID]*asset.File, error) {
if r.err != nil {
return nil, r.err
}

filesMap := make(map[id.AssetID]*asset.File)
for _, id := range ids {
f := r.data.Find(func(key asset.ID, value *asset.File) bool {
return key == id
}).Clone()
fs := r.files.Find(func(key asset.ID, value []*asset.File) bool {
return key == id
})
if len(fs) > 0 {
f.SetFiles(fs)
}
if f == nil {
return nil, rerror.ErrNotFound
}
nourbalaha marked this conversation as resolved.
Show resolved Hide resolved
filesMap[id] = f
}

return filesMap, nil
}


func (r *AssetFile) Save(ctx context.Context, id id.AssetID, file *asset.File) error {
if r.err != nil {
return r.err
Expand Down
46 changes: 46 additions & 0 deletions server/internal/infrastructure/mongo/asset_file.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,52 @@ func (r *AssetFile) FindByID(ctx context.Context, id id.AssetID) (*asset.File, e
return f, nil
}

func (r *AssetFile) FindByIDs(ctx context.Context, ids id.AssetIDList) (map[id.AssetID]*asset.File, error) {
filesMap := make(map[id.AssetID]*asset.File)

c := &mongodoc.AssetAndFileConsumer{}
if err := r.client.Find(ctx, bson.M{
"id": bson.M{"$in": ids.Strings()},
}, c, options.Find().SetProjection(bson.M{
"id": 1,
"file": 1,
"flatfiles": 1,
})); err != nil {
return nil, err
}

for _, result := range c.Result {
assetID := result.ID
f := result.File.Model()
if f == nil {
return nil, rerror.ErrNotFound
}

if result.FlatFiles {
var afc mongodoc.AssetFilesConsumer
if err := r.assetFilesClient.Find(ctx, bson.M{
"assetid": assetID,
}, &afc, options.Find().SetSort(bson.D{
{Key: "page", Value: 1},
})); err != nil {
return nil, err
}
files := afc.Result().Model()
f.SetFiles(files)
} else if len(f.Children()) > 0 {
f.SetFiles(f.FlattenChildren())
}

aId, err := id.AssetIDFrom(assetID)
if err != nil {
return nil, err
}
filesMap[aId] = f
}

return filesMap, nil
}

func (r *AssetFile) Save(ctx context.Context, id id.AssetID, file *asset.File) error {
doc := mongodoc.NewFile(file)
_, err := r.client.Client().UpdateOne(ctx, bson.M{
Expand Down
14 changes: 14 additions & 0 deletions server/internal/usecase/interactor/asset.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,20 @@ func (i *Asset) FindFileByID(ctx context.Context, aid id.AssetID, _ *usecase.Ope
return files, nil
}

func (i *Asset) FindFilesByIDs(ctx context.Context, ids id.AssetIDList, _ *usecase.Operator) (map[id.AssetID]*asset.File, error) {
_, err := i.repos.Asset.FindByIDs(ctx, ids)
if err != nil {
return nil, err
}

files, err := i.repos.AssetFile.FindByIDs(ctx, ids)
if err != nil {
return nil, err
}

return files, nil
}

func (i *Asset) DownloadByID(ctx context.Context, aid id.AssetID, _ *usecase.Operator) (io.ReadCloser, error) {
a, err := i.repos.Asset.FindByID(ctx, aid)
if err != nil {
Expand Down
1 change: 1 addition & 0 deletions server/internal/usecase/interfaces/asset.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ type Asset interface {
FindByIDs(context.Context, []id.AssetID, *usecase.Operator) (asset.List, error)
FindByProject(context.Context, id.ProjectID, AssetFilter, *usecase.Operator) (asset.List, *usecasex.PageInfo, error)
FindFileByID(context.Context, id.AssetID, *usecase.Operator) (*asset.File, error)
FindFilesByIDs(context.Context, id.AssetIDList, *usecase.Operator) (map[id.AssetID]*asset.File, error)
nourbalaha marked this conversation as resolved.
Show resolved Hide resolved
DownloadByID(context.Context, id.AssetID, *usecase.Operator) (io.ReadCloser, error)
GetURL(*asset.Asset) string
Create(context.Context, CreateAssetParam, *usecase.Operator) (*asset.Asset, *asset.File, error)
Expand Down
1 change: 1 addition & 0 deletions server/internal/usecase/repo/asset.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ type Asset interface {

type AssetFile interface {
FindByID(context.Context, id.AssetID) (*asset.File, error)
FindByIDs(context.Context, id.AssetIDList) (map[id.AssetID]*asset.File, error)
Save(context.Context, id.AssetID, *asset.File) error
SaveFlat(context.Context, id.AssetID, *asset.File, []*asset.File) error
}
8 changes: 8 additions & 0 deletions server/pkg/asset/list.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package asset

import (
"github.com/reearth/reearth-cms/server/pkg/id"
"github.com/reearth/reearthx/util"
"github.com/samber/lo"
"golang.org/x/exp/slices"
Expand All @@ -27,3 +28,10 @@ func (l List) Map() Map {
return a.ID(), a
})
}

func (l List) IDs() (ids id.AssetIDList) {
for _, a := range l {
ids = ids.Add(a.ID())
}
return
}
10 changes: 10 additions & 0 deletions server/pkg/asset/list_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package asset
import (
"testing"

"github.com/reearth/reearth-cms/server/pkg/id"
"github.com/reearth/reearthx/account/accountdomain"
"github.com/stretchr/testify/assert"
)
Expand Down Expand Up @@ -51,3 +52,12 @@ func TestList_Map(t *testing.T) {
}, List{a, nil}.Map())
assert.Equal(t, Map{}, List(nil).Map())
}

func TestList_IDs(t *testing.T) {
pid := NewProjectID()
uid := accountdomain.NewUserID()
a1 := New().NewID().Project(pid).CreatedByUser(uid).Size(1000).Thread(NewThreadID()).NewUUID().MustBuild()
a2 := New().NewID().Project(pid).CreatedByUser(uid).Size(1000).Thread(NewThreadID()).NewUUID().MustBuild()
al := List{a1, a2}
assert.Equal(t, al.IDs(), id.AssetIDList{a1.ID(), a2.ID()})
}
Loading