342 lines
8.5 KiB
Go
342 lines
8.5 KiB
Go
|
package share
|
||
|
|
||
|
import (
|
||
|
"time"
|
||
|
|
||
|
"forge.cadoles.com/arcad/edge/pkg/app"
|
||
|
"forge.cadoles.com/arcad/edge/pkg/module/util"
|
||
|
"github.com/dop251/goja"
|
||
|
"github.com/pkg/errors"
|
||
|
)
|
||
|
|
||
|
const (
|
||
|
AnyType ValueType = "*"
|
||
|
AnyName string = "*"
|
||
|
)
|
||
|
|
||
|
type Module struct {
|
||
|
appID app.ID
|
||
|
repository Repository
|
||
|
}
|
||
|
|
||
|
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"))
|
||
|
}
|
||
|
|
||
|
if err := export.Set("TYPE_TEXT", TypeText); err != nil {
|
||
|
panic(errors.Wrap(err, "could not set 'TYPE_TEXT' property"))
|
||
|
}
|
||
|
|
||
|
if err := export.Set("TYPE_NUMBER", TypeNumber); err != nil {
|
||
|
panic(errors.Wrap(err, "could not set 'TYPE_NUMBER' property"))
|
||
|
}
|
||
|
|
||
|
if err := export.Set("TYPE_BOOL", TypeBool); err != nil {
|
||
|
panic(errors.Wrap(err, "could not set 'TYPE_BOOL' property"))
|
||
|
}
|
||
|
|
||
|
if err := export.Set("TYPE_PATH", TypePath); err != nil {
|
||
|
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)
|
||
|
|
||
|
var attributes []Attribute
|
||
|
if len(call.Arguments) > 2 {
|
||
|
attributes = assertAttributes(call.Arguments[2:], rt)
|
||
|
} else {
|
||
|
attributes = make([]Attribute, 0)
|
||
|
}
|
||
|
|
||
|
for _, attr := range attributes {
|
||
|
if err := AssertType(attr.Value(), attr.Type()); err != nil {
|
||
|
panic(rt.ToValue(errors.WithStack(err)))
|
||
|
}
|
||
|
}
|
||
|
|
||
|
resource, err := m.repository.UpdateAttributes(ctx, m.appID, resourceID, attributes...)
|
||
|
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)
|
||
|
}
|
||
|
|
||
|
err := m.repository.DeleteAttributes(ctx, m.appID, resourceID, names...)
|
||
|
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)
|
||
|
|
||
|
funcs := make([]FindResourcesOptionFunc, 0)
|
||
|
|
||
|
if len(call.Arguments) > 1 {
|
||
|
name := util.AssertString(call.Argument(1), rt)
|
||
|
if name != AnyName {
|
||
|
funcs = append(funcs, WithName(name))
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if len(call.Arguments) > 2 {
|
||
|
valueType := assertValueType(call.Argument(2), rt)
|
||
|
if valueType != AnyType {
|
||
|
funcs = append(funcs, WithType(valueType))
|
||
|
}
|
||
|
}
|
||
|
|
||
|
resources, err := m.repository.FindResources(ctx, funcs...)
|
||
|
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)
|
||
|
|
||
|
err := m.repository.DeleteResource(ctx, m.appID, resourceID)
|
||
|
if err != nil {
|
||
|
panic(rt.ToValue(errors.WithStack(err)))
|
||
|
}
|
||
|
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
func ModuleFactory(appID app.ID, repository Repository) app.ServerModuleFactory {
|
||
|
return func(server *app.Server) app.ServerModule {
|
||
|
return &Module{
|
||
|
appID: appID,
|
||
|
repository: repository,
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func assertResourceID(v goja.Value, r *goja.Runtime) ResourceID {
|
||
|
value := v.Export()
|
||
|
switch typ := value.(type) {
|
||
|
case string:
|
||
|
return ResourceID(typ)
|
||
|
case ResourceID:
|
||
|
return typ
|
||
|
default:
|
||
|
panic(r.ToValue(errors.Errorf("expected value to be a string or ResourceID, got '%T'", value)))
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func assertAttributes(values []goja.Value, r *goja.Runtime) []Attribute {
|
||
|
attributes := make([]Attribute, len(values))
|
||
|
|
||
|
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)))
|
||
|
}
|
||
|
|
||
|
var valueType ValueType
|
||
|
switch typ := rawType.(type) {
|
||
|
case ValueType:
|
||
|
valueType = typ
|
||
|
case string:
|
||
|
valueType = ValueType(typ)
|
||
|
|
||
|
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)))
|
||
|
}
|
||
|
|
||
|
attributes[idx] = NewBaseAttribute(
|
||
|
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
|
||
|
}
|
||
|
|
||
|
func assertValueType(v goja.Value, r *goja.Runtime) ValueType {
|
||
|
value := v.Export()
|
||
|
switch typ := value.(type) {
|
||
|
case string:
|
||
|
return ValueType(typ)
|
||
|
case ValueType:
|
||
|
return typ
|
||
|
default:
|
||
|
panic(r.ToValue(errors.Errorf("expected value to be a string or ValueType, got '%T'", value)))
|
||
|
}
|
||
|
}
|
||
|
|
||
|
type gojaResource struct {
|
||
|
ID ResourceID `goja:"id" json:"id"`
|
||
|
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)
|
||
|
|
||
|
hasAttr := HasAttribute(toResource(r), name, valueType)
|
||
|
|
||
|
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()
|
||
|
}
|
||
|
|
||
|
attr := GetAttribute(toResource(r), name, valueType)
|
||
|
|
||
|
if attr == nil {
|
||
|
return rt.ToValue(defaultValue)
|
||
|
}
|
||
|
|
||
|
return rt.ToValue(attr.Value())
|
||
|
}
|
||
|
|
||
|
type gojaAttribute struct {
|
||
|
Name string `goja:"name" json:"name"`
|
||
|
Type 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"`
|
||
|
}
|
||
|
|
||
|
func toGojaResource(res Resource) *gojaResource {
|
||
|
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,
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func toGojaResources(resources []Resource) []*gojaResource {
|
||
|
gojaResources := make([]*gojaResource, len(resources))
|
||
|
for idx, res := range resources {
|
||
|
gojaResources[idx] = toGojaResource(res)
|
||
|
}
|
||
|
return gojaResources
|
||
|
}
|
||
|
|
||
|
func toResource(res *gojaResource) Resource {
|
||
|
return NewBaseResource(
|
||
|
res.Origin,
|
||
|
res.ID,
|
||
|
toAttributes(res.Attributes)...,
|
||
|
)
|
||
|
}
|
||
|
|
||
|
func toAttributes(gojaAttributes []*gojaAttribute) []Attribute {
|
||
|
attributes := make([]Attribute, len(gojaAttributes))
|
||
|
|
||
|
for idx, gojaAttr := range gojaAttributes {
|
||
|
attr := NewBaseAttribute(
|
||
|
gojaAttr.Name,
|
||
|
gojaAttr.Type,
|
||
|
gojaAttr.Value,
|
||
|
)
|
||
|
|
||
|
attr.SetCreatedAt(gojaAttr.CreatedAt)
|
||
|
attr.SetUpdatedAt(gojaAttr.UpdatedAt)
|
||
|
|
||
|
attributes[idx] = attr
|
||
|
}
|
||
|
|
||
|
return attributes
|
||
|
}
|