package object import ( "encoding/json" "time" "github.com/dop251/goja" "github.com/zitadel/logging" "github.com/zitadel/zitadel/internal/actions" "github.com/zitadel/zitadel/internal/domain" "github.com/zitadel/zitadel/internal/query" ) func UserMetadataListFromQuery(c *actions.FieldConfig, metadata *query.UserMetadataList) goja.Value { result := &userMetadataList{ Count: metadata.Count, Sequence: metadata.Sequence, Timestamp: metadata.LastRun, Metadata: make([]*userMetadata, len(metadata.Metadata)), } for i, md := range metadata.Metadata { result.Metadata[i] = &userMetadata{ CreationDate: md.CreationDate, ChangeDate: md.ChangeDate, ResourceOwner: md.ResourceOwner, Sequence: md.Sequence, Key: md.Key, Value: metadataByteArrayToValue(md.Value, c.Runtime), } } return c.Runtime.ToValue(result) } func metadataByteArrayToValue(val []byte, runtime *goja.Runtime) goja.Value { var value interface{} if !json.Valid(val) { var err error val, err = json.Marshal(string(val)) if err != nil { logging.WithError(err).Debug("unable to marshal unknown value") panic(err) } } err := json.Unmarshal(val, &value) if err != nil { logging.WithError(err).Debug("unable to unmarshal into map") panic(err) } return runtime.ToValue(value) } type userMetadataList struct { Count uint64 Sequence uint64 Timestamp time.Time Metadata []*userMetadata } type userMetadata struct { CreationDate time.Time ChangeDate time.Time ResourceOwner string Sequence uint64 Key string Value goja.Value } type MetadataList struct { metadata []*Metadata } type Metadata struct { Key string // Value is for exporting to javascript Value goja.Value // value is for mapping to [domain.Metadata] value []byte } func (md *MetadataList) AppendMetadataFunc(call goja.FunctionCall) goja.Value { if len(call.Arguments) != 2 { panic("exactly 2 (key, value) arguments expected") } value, err := json.Marshal(call.Arguments[1].Export()) if err != nil { logging.WithError(err).Debug("unable to marshal") panic(err) } md.metadata = append(md.metadata, &Metadata{ Key: call.Arguments[0].Export().(string), Value: call.Arguments[1], value: value, }) return nil } func (md *MetadataList) MetadataListFromDomain(runtime *goja.Runtime) interface{} { for i, metadata := range md.metadata { md.metadata[i].Value = metadataByteArrayToValue(metadata.value, runtime) } return &md.metadata } func MetadataListFromDomain(metadata []*domain.Metadata) *MetadataList { list := &MetadataList{metadata: make([]*Metadata, len(metadata))} for i, md := range metadata { list.metadata[i] = &Metadata{ Key: md.Key, value: md.Value, } } return list } func MetadataListToDomain(metadataList *MetadataList) []*domain.Metadata { if metadataList == nil { return nil } list := make([]*domain.Metadata, len(metadataList.metadata)) for i, metadata := range metadataList.metadata { value := metadata.value if len(value) == 0 { value = mapBytesToByteArray(metadata.Value.Export()) } list[i] = &domain.Metadata{ Key: metadata.Key, Value: value, } } return list } // mapBytesToByteArray is used for backwards compatibility of old metadata.push method // converts the Javascript uint8 array which is exported as []interface{} to a []byte func mapBytesToByteArray(i interface{}) []byte { bytes, ok := i.([]interface{}) if !ok { return nil } value := make([]byte, len(bytes)) for i, val := range bytes { b, ok := val.(int64) if !ok { return nil } value[i] = byte(b) } return value }