2023-04-13 10:16:48 +02:00
|
|
|
package share
|
|
|
|
|
|
|
|
import (
|
|
|
|
"time"
|
|
|
|
|
|
|
|
"forge.cadoles.com/arcad/edge/pkg/app"
|
|
|
|
"forge.cadoles.com/arcad/edge/pkg/module/util"
|
2023-09-13 06:03:25 +02:00
|
|
|
"forge.cadoles.com/arcad/edge/pkg/storage/share"
|
2023-04-13 10:16:48 +02:00
|
|
|
"github.com/dop251/goja"
|
|
|
|
"github.com/pkg/errors"
|
|
|
|
)
|
|
|
|
|
|
|
|
const (
|
2023-09-13 06:03:25 +02:00
|
|
|
AnyType share.ValueType = "*"
|
|
|
|
AnyName string = "*"
|
2023-04-13 10:16:48 +02:00
|
|
|
)
|
|
|
|
|
|
|
|
type Module struct {
|
2023-09-13 06:03:25 +02:00
|
|
|
appID app.ID
|
|
|
|
store share.Store
|
2023-04-13 10:16:48 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
func (m *Module) Name() string {
|
|
|
|
return "share"
|
|
|
|
}
|
|
|
|
|
|
|
|
func (m *Module) Export(export *goja.Object) {
|
|
|
|
if err := export.Set("upsertResource", m.upsertResource); err != nil {
|
|
|
|
panic(errors.Wrap(err, "could not set 'upsertResource' function"))
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := export.Set("findResources", m.findResources); err != nil {
|
|
|
|
panic(errors.Wrap(err, "could not set 'findResources' function"))
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := export.Set("deleteAttributes", m.deleteAttributes); err != nil {
|
|
|
|
panic(errors.Wrap(err, "could not set 'deleteAttributes' function"))
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := export.Set("deleteResource", m.deleteResource); err != nil {
|
|
|
|
panic(errors.Wrap(err, "could not set 'deleteResource' function"))
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := export.Set("ANY_TYPE", AnyType); err != nil {
|
|
|
|
panic(errors.Wrap(err, "could not set 'ANY_TYPE' property"))
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := export.Set("ANY_NAME", AnyName); err != nil {
|
|
|
|
panic(errors.Wrap(err, "could not set 'ANY_NAME' property"))
|
|
|
|
}
|
|
|
|
|
2023-09-13 06:03:25 +02:00
|
|
|
if err := export.Set("TYPE_TEXT", share.TypeText); err != nil {
|
2023-04-13 10:16:48 +02:00
|
|
|
panic(errors.Wrap(err, "could not set 'TYPE_TEXT' property"))
|
|
|
|
}
|
|
|
|
|
2023-09-13 06:03:25 +02:00
|
|
|
if err := export.Set("TYPE_NUMBER", share.TypeNumber); err != nil {
|
2023-04-13 10:16:48 +02:00
|
|
|
panic(errors.Wrap(err, "could not set 'TYPE_NUMBER' property"))
|
|
|
|
}
|
|
|
|
|
2023-09-13 06:03:25 +02:00
|
|
|
if err := export.Set("TYPE_BOOL", share.TypeBool); err != nil {
|
2023-04-13 10:16:48 +02:00
|
|
|
panic(errors.Wrap(err, "could not set 'TYPE_BOOL' property"))
|
|
|
|
}
|
|
|
|
|
2023-09-13 06:03:25 +02:00
|
|
|
if err := export.Set("TYPE_PATH", share.TypePath); err != nil {
|
2023-04-13 10:16:48 +02:00
|
|
|
panic(errors.Wrap(err, "could not set 'TYPE_PATH' property"))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (m *Module) upsertResource(call goja.FunctionCall, rt *goja.Runtime) goja.Value {
|
|
|
|
ctx := util.AssertContext(call.Argument(0), rt)
|
|
|
|
resourceID := assertResourceID(call.Argument(1), rt)
|
|
|
|
|
2023-09-13 06:03:25 +02:00
|
|
|
var attributes []share.Attribute
|
2023-04-13 10:16:48 +02:00
|
|
|
if len(call.Arguments) > 2 {
|
|
|
|
attributes = assertAttributes(call.Arguments[2:], rt)
|
|
|
|
} else {
|
2023-09-13 06:03:25 +02:00
|
|
|
attributes = make([]share.Attribute, 0)
|
2023-04-13 10:16:48 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
for _, attr := range attributes {
|
2023-09-13 06:03:25 +02:00
|
|
|
if err := share.AssertType(attr.Value(), attr.Type()); err != nil {
|
2023-04-13 10:16:48 +02:00
|
|
|
panic(rt.ToValue(errors.WithStack(err)))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-09-13 06:03:25 +02:00
|
|
|
resource, err := m.store.UpdateAttributes(ctx, m.appID, resourceID, attributes...)
|
2023-04-13 10:16:48 +02:00
|
|
|
if err != nil {
|
|
|
|
panic(rt.ToValue(errors.WithStack(err)))
|
|
|
|
}
|
|
|
|
|
|
|
|
return rt.ToValue(toGojaResource(resource))
|
|
|
|
}
|
|
|
|
|
|
|
|
func (m *Module) deleteAttributes(call goja.FunctionCall, rt *goja.Runtime) goja.Value {
|
|
|
|
ctx := util.AssertContext(call.Argument(0), rt)
|
|
|
|
resourceID := assertResourceID(call.Argument(1), rt)
|
|
|
|
|
|
|
|
var names []string
|
|
|
|
if len(call.Arguments) > 2 {
|
|
|
|
names = assertStrings(call.Arguments[2:], rt)
|
|
|
|
} else {
|
|
|
|
names = make([]string, 0)
|
|
|
|
}
|
|
|
|
|
2023-09-13 06:03:25 +02:00
|
|
|
err := m.store.DeleteAttributes(ctx, m.appID, resourceID, names...)
|
2023-04-13 10:16:48 +02:00
|
|
|
if err != nil {
|
|
|
|
panic(rt.ToValue(errors.WithStack(err)))
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (m *Module) findResources(call goja.FunctionCall, rt *goja.Runtime) goja.Value {
|
|
|
|
ctx := util.AssertContext(call.Argument(0), rt)
|
|
|
|
|
2023-09-13 06:03:25 +02:00
|
|
|
funcs := make([]share.FindResourcesOptionFunc, 0)
|
2023-04-13 10:16:48 +02:00
|
|
|
|
|
|
|
if len(call.Arguments) > 1 {
|
|
|
|
name := util.AssertString(call.Argument(1), rt)
|
|
|
|
if name != AnyName {
|
2023-09-13 06:03:25 +02:00
|
|
|
funcs = append(funcs, share.WithName(name))
|
2023-04-13 10:16:48 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if len(call.Arguments) > 2 {
|
|
|
|
valueType := assertValueType(call.Argument(2), rt)
|
|
|
|
if valueType != AnyType {
|
2023-09-13 06:03:25 +02:00
|
|
|
funcs = append(funcs, share.WithType(valueType))
|
2023-04-13 10:16:48 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-09-13 06:03:25 +02:00
|
|
|
resources, err := m.store.FindResources(ctx, funcs...)
|
2023-04-13 10:16:48 +02:00
|
|
|
if err != nil {
|
|
|
|
panic(rt.ToValue(errors.WithStack(err)))
|
|
|
|
}
|
|
|
|
|
|
|
|
return rt.ToValue(toGojaResources(resources))
|
|
|
|
}
|
|
|
|
|
|
|
|
func (m *Module) deleteResource(call goja.FunctionCall, rt *goja.Runtime) goja.Value {
|
|
|
|
ctx := util.AssertContext(call.Argument(0), rt)
|
|
|
|
resourceID := assertResourceID(call.Argument(1), rt)
|
|
|
|
|
2023-09-13 06:03:25 +02:00
|
|
|
err := m.store.DeleteResource(ctx, m.appID, resourceID)
|
2023-04-13 10:16:48 +02:00
|
|
|
if err != nil {
|
|
|
|
panic(rt.ToValue(errors.WithStack(err)))
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2023-09-13 06:03:25 +02:00
|
|
|
func ModuleFactory(appID app.ID, store share.Store) app.ServerModuleFactory {
|
2023-04-13 10:16:48 +02:00
|
|
|
return func(server *app.Server) app.ServerModule {
|
|
|
|
return &Module{
|
2023-09-13 06:03:25 +02:00
|
|
|
appID: appID,
|
|
|
|
store: store,
|
2023-04-13 10:16:48 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-09-13 06:03:25 +02:00
|
|
|
func assertResourceID(v goja.Value, r *goja.Runtime) share.ResourceID {
|
2023-04-13 10:16:48 +02:00
|
|
|
value := v.Export()
|
|
|
|
switch typ := value.(type) {
|
|
|
|
case string:
|
2023-09-13 06:03:25 +02:00
|
|
|
return share.ResourceID(typ)
|
|
|
|
case share.ResourceID:
|
2023-04-13 10:16:48 +02:00
|
|
|
return typ
|
|
|
|
default:
|
|
|
|
panic(r.ToValue(errors.Errorf("expected value to be a string or ResourceID, got '%T'", value)))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-09-13 06:03:25 +02:00
|
|
|
func assertAttributes(values []goja.Value, r *goja.Runtime) []share.Attribute {
|
|
|
|
attributes := make([]share.Attribute, len(values))
|
2023-04-13 10:16:48 +02:00
|
|
|
|
|
|
|
for idx, val := range values {
|
|
|
|
export := val.Export()
|
|
|
|
|
|
|
|
rawAttr, ok := export.(map[string]any)
|
|
|
|
if !ok {
|
|
|
|
panic(r.ToValue(errors.Errorf("unexpected attribute value, got '%v'", export)))
|
|
|
|
}
|
|
|
|
|
|
|
|
rawName, exists := rawAttr["name"]
|
|
|
|
if !exists {
|
|
|
|
panic(r.ToValue(errors.Errorf("could not find 'name' property on attribute '%v'", export)))
|
|
|
|
}
|
|
|
|
|
|
|
|
name, ok := rawName.(string)
|
|
|
|
if !ok {
|
|
|
|
panic(r.ToValue(errors.Errorf("unexpected value for attribute property 'name': expected 'string', got '%T'", rawName)))
|
|
|
|
}
|
|
|
|
|
|
|
|
rawType, exists := rawAttr["type"]
|
|
|
|
if !exists {
|
|
|
|
panic(r.ToValue(errors.Errorf("could not find 'type' property on attribute '%v'", export)))
|
|
|
|
}
|
|
|
|
|
2023-09-13 06:03:25 +02:00
|
|
|
var valueType share.ValueType
|
2023-04-13 10:16:48 +02:00
|
|
|
switch typ := rawType.(type) {
|
2023-09-13 06:03:25 +02:00
|
|
|
case share.ValueType:
|
2023-04-13 10:16:48 +02:00
|
|
|
valueType = typ
|
|
|
|
case string:
|
2023-09-13 06:03:25 +02:00
|
|
|
valueType = share.ValueType(typ)
|
2023-04-13 10:16:48 +02:00
|
|
|
|
|
|
|
default:
|
|
|
|
panic(r.ToValue(errors.Errorf("unexpected value for attribute property 'type': expected 'string' or 'ValueType', got '%T'", rawType)))
|
|
|
|
}
|
|
|
|
|
|
|
|
value, exists := rawAttr["value"]
|
|
|
|
if !exists {
|
|
|
|
panic(r.ToValue(errors.Errorf("could not find 'value' property on attribute '%v'", export)))
|
|
|
|
}
|
|
|
|
|
2023-09-13 06:03:25 +02:00
|
|
|
attributes[idx] = share.NewBaseAttribute(
|
2023-04-13 10:16:48 +02:00
|
|
|
name,
|
|
|
|
valueType,
|
|
|
|
value,
|
|
|
|
)
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
return attributes
|
|
|
|
}
|
|
|
|
|
|
|
|
func assertStrings(values []goja.Value, r *goja.Runtime) []string {
|
|
|
|
strings := make([]string, len(values))
|
|
|
|
|
|
|
|
for idx, v := range values {
|
|
|
|
strings[idx] = util.AssertString(v, r)
|
|
|
|
}
|
|
|
|
|
|
|
|
return strings
|
|
|
|
}
|
|
|
|
|
2023-09-13 06:03:25 +02:00
|
|
|
func assertValueType(v goja.Value, r *goja.Runtime) share.ValueType {
|
2023-04-13 10:16:48 +02:00
|
|
|
value := v.Export()
|
|
|
|
switch typ := value.(type) {
|
|
|
|
case string:
|
2023-09-13 06:03:25 +02:00
|
|
|
return share.ValueType(typ)
|
|
|
|
case share.ValueType:
|
2023-04-13 10:16:48 +02:00
|
|
|
return typ
|
|
|
|
default:
|
|
|
|
panic(r.ToValue(errors.Errorf("expected value to be a string or ValueType, got '%T'", value)))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
type gojaResource struct {
|
2023-09-13 06:03:25 +02:00
|
|
|
ID share.ResourceID `goja:"id" json:"id"`
|
2023-04-13 10:16:48 +02:00
|
|
|
Origin app.ID `goja:"origin" json:"origin"`
|
|
|
|
Attributes []*gojaAttribute `goja:"attributes" json:"attributes"`
|
|
|
|
}
|
|
|
|
|
|
|
|
func (r *gojaResource) Has(call goja.FunctionCall, rt *goja.Runtime) goja.Value {
|
|
|
|
name := util.AssertString(call.Argument(0), rt)
|
|
|
|
valueType := assertValueType(call.Argument(1), rt)
|
|
|
|
|
2023-09-13 06:03:25 +02:00
|
|
|
hasAttr := share.HasAttribute(toResource(r), name, valueType)
|
2023-04-13 10:16:48 +02:00
|
|
|
|
|
|
|
return rt.ToValue(hasAttr)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (r *gojaResource) Get(call goja.FunctionCall, rt *goja.Runtime) goja.Value {
|
|
|
|
name := util.AssertString(call.Argument(0), rt)
|
|
|
|
valueType := assertValueType(call.Argument(1), rt)
|
|
|
|
|
|
|
|
var defaultValue any
|
|
|
|
if len(call.Arguments) > 2 {
|
|
|
|
defaultValue = call.Argument(2).Export()
|
|
|
|
}
|
|
|
|
|
2023-09-13 06:03:25 +02:00
|
|
|
attr := share.GetAttribute(toResource(r), name, valueType)
|
2023-04-13 10:16:48 +02:00
|
|
|
|
|
|
|
if attr == nil {
|
|
|
|
return rt.ToValue(defaultValue)
|
|
|
|
}
|
|
|
|
|
|
|
|
return rt.ToValue(attr.Value())
|
|
|
|
}
|
|
|
|
|
|
|
|
type gojaAttribute struct {
|
2023-09-13 06:03:25 +02:00
|
|
|
Name string `goja:"name" json:"name"`
|
|
|
|
Type share.ValueType `goja:"type" json:"type"`
|
|
|
|
Value any `goja:"value" json:"value"`
|
|
|
|
CreatedAt time.Time `goja:"createdAt" json:"createdAt"`
|
|
|
|
UpdatedAt time.Time `goja:"updatedAt" json:"updatedAt"`
|
2023-04-13 10:16:48 +02:00
|
|
|
}
|
|
|
|
|
2023-09-13 06:03:25 +02:00
|
|
|
func toGojaResource(res share.Resource) *gojaResource {
|
2023-04-13 10:16:48 +02:00
|
|
|
attributes := make([]*gojaAttribute, len(res.Attributes()))
|
|
|
|
|
|
|
|
for idx, attr := range res.Attributes() {
|
|
|
|
attributes[idx] = &gojaAttribute{
|
|
|
|
Name: attr.Name(),
|
|
|
|
Type: attr.Type(),
|
|
|
|
Value: attr.Value(),
|
|
|
|
CreatedAt: attr.CreatedAt(),
|
|
|
|
UpdatedAt: attr.UpdatedAt(),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return &gojaResource{
|
|
|
|
ID: res.ID(),
|
|
|
|
Origin: res.Origin(),
|
|
|
|
Attributes: attributes,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-09-13 06:03:25 +02:00
|
|
|
func toGojaResources(resources []share.Resource) []*gojaResource {
|
2023-04-13 10:16:48 +02:00
|
|
|
gojaResources := make([]*gojaResource, len(resources))
|
|
|
|
for idx, res := range resources {
|
|
|
|
gojaResources[idx] = toGojaResource(res)
|
|
|
|
}
|
|
|
|
return gojaResources
|
|
|
|
}
|
|
|
|
|
2023-09-13 06:03:25 +02:00
|
|
|
func toResource(res *gojaResource) share.Resource {
|
|
|
|
return share.NewBaseResource(
|
2023-04-13 10:16:48 +02:00
|
|
|
res.Origin,
|
|
|
|
res.ID,
|
|
|
|
toAttributes(res.Attributes)...,
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
2023-09-13 06:03:25 +02:00
|
|
|
func toAttributes(gojaAttributes []*gojaAttribute) []share.Attribute {
|
|
|
|
attributes := make([]share.Attribute, len(gojaAttributes))
|
2023-04-13 10:16:48 +02:00
|
|
|
|
|
|
|
for idx, gojaAttr := range gojaAttributes {
|
2023-09-13 06:03:25 +02:00
|
|
|
attr := share.NewBaseAttribute(
|
2023-04-13 10:16:48 +02:00
|
|
|
gojaAttr.Name,
|
|
|
|
gojaAttr.Type,
|
|
|
|
gojaAttr.Value,
|
|
|
|
)
|
|
|
|
|
|
|
|
attr.SetCreatedAt(gojaAttr.CreatedAt)
|
|
|
|
attr.SetUpdatedAt(gojaAttr.UpdatedAt)
|
|
|
|
|
|
|
|
attributes[idx] = attr
|
|
|
|
}
|
|
|
|
|
|
|
|
return attributes
|
|
|
|
}
|