added view collection type
This commit is contained in:
+34
-2
@@ -17,6 +17,7 @@ var (
|
||||
const (
|
||||
CollectionTypeBase = "base"
|
||||
CollectionTypeAuth = "auth"
|
||||
CollectionTypeView = "view"
|
||||
)
|
||||
|
||||
type Collection struct {
|
||||
@@ -52,11 +53,16 @@ func (m *Collection) IsBase() bool {
|
||||
return m.Type == CollectionTypeBase
|
||||
}
|
||||
|
||||
// IsBase checks if the current collection has "auth" type.
|
||||
// IsAuth checks if the current collection has "auth" type.
|
||||
func (m *Collection) IsAuth() bool {
|
||||
return m.Type == CollectionTypeAuth
|
||||
}
|
||||
|
||||
// IsView checks if the current collection has "view" type.
|
||||
func (m *Collection) IsView() bool {
|
||||
return m.Type == CollectionTypeView
|
||||
}
|
||||
|
||||
// MarshalJSON implements the [json.Marshaler] interface.
|
||||
func (m Collection) MarshalJSON() ([]byte, error) {
|
||||
type alias Collection // prevent recursion
|
||||
@@ -82,6 +88,14 @@ func (m *Collection) AuthOptions() CollectionAuthOptions {
|
||||
return result
|
||||
}
|
||||
|
||||
// ViewOptions decodes the current collection options and returns them
|
||||
// as new [CollectionViewOptions] instance.
|
||||
func (m *Collection) ViewOptions() CollectionViewOptions {
|
||||
result := CollectionViewOptions{}
|
||||
m.DecodeOptions(&result)
|
||||
return result
|
||||
}
|
||||
|
||||
// NormalizeOptions updates the current collection options with a
|
||||
// new normalized state based on the collection type.
|
||||
func (m *Collection) NormalizeOptions() error {
|
||||
@@ -89,6 +103,8 @@ func (m *Collection) NormalizeOptions() error {
|
||||
switch m.Type {
|
||||
case CollectionTypeAuth:
|
||||
typedOptions = m.AuthOptions()
|
||||
case CollectionTypeView:
|
||||
typedOptions = m.ViewOptions()
|
||||
default:
|
||||
typedOptions = m.BaseOptions()
|
||||
}
|
||||
@@ -143,7 +159,7 @@ func (m *Collection) SetOptions(typedOptions any) error {
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
|
||||
// CollectionAuthOptions defines the "base" Collection.Options fields.
|
||||
// CollectionBaseOptions defines the "base" Collection.Options fields.
|
||||
type CollectionBaseOptions struct {
|
||||
}
|
||||
|
||||
@@ -152,6 +168,8 @@ func (o CollectionBaseOptions) Validate() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
|
||||
// CollectionAuthOptions defines the "auth" Collection.Options fields.
|
||||
type CollectionAuthOptions struct {
|
||||
ManageRule *string `form:"manageRule" json:"manageRule"`
|
||||
@@ -184,3 +202,17 @@ func (o CollectionAuthOptions) Validate() error {
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
|
||||
// CollectionViewOptions defines the "view" Collection.Options fields.
|
||||
type CollectionViewOptions struct {
|
||||
Query string `form:"query" json:"query"`
|
||||
}
|
||||
|
||||
// Validate implements [validation.Validatable] interface.
|
||||
func (o CollectionViewOptions) Validate() error {
|
||||
return validation.ValidateStruct(&o,
|
||||
validation.Field(&o.Query, validation.Required),
|
||||
)
|
||||
}
|
||||
|
||||
+99
-11
@@ -97,11 +97,11 @@ func TestCollectionMarshalJSON(t *testing.T) {
|
||||
for _, s := range scenarios {
|
||||
result, err := s.collection.MarshalJSON()
|
||||
if err != nil {
|
||||
t.Errorf("(%s) Unexpected error %v", s.name, err)
|
||||
t.Errorf("[%s] Unexpected error %v", s.name, err)
|
||||
continue
|
||||
}
|
||||
if string(result) != s.expected {
|
||||
t.Errorf("(%s) Expected\n%v \ngot \n%v", s.name, s.expected, string(result))
|
||||
t.Errorf("[%s] Expected\n%v \ngot \n%v", s.name, s.expected, string(result))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -143,7 +143,7 @@ func TestCollectionBaseOptions(t *testing.T) {
|
||||
}
|
||||
|
||||
if strEncoded := string(encoded); strEncoded != s.expected {
|
||||
t.Errorf("(%s) Expected \n%v \ngot \n%v", s.name, s.expected, strEncoded)
|
||||
t.Errorf("[%s] Expected \n%v \ngot \n%v", s.name, s.expected, strEncoded)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -188,7 +188,52 @@ func TestCollectionAuthOptions(t *testing.T) {
|
||||
}
|
||||
|
||||
if strEncoded := string(encoded); strEncoded != s.expected {
|
||||
t.Errorf("(%s) Expected \n%v \ngot \n%v", s.name, s.expected, strEncoded)
|
||||
t.Errorf("[%s] Expected \n%v \ngot \n%v", s.name, s.expected, strEncoded)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestCollectionViewOptions(t *testing.T) {
|
||||
options := types.JsonMap{"query": "select id from demo1", "minPasswordLength": 4}
|
||||
expectedSerialization := `{"query":"select id from demo1"}`
|
||||
|
||||
scenarios := []struct {
|
||||
name string
|
||||
collection models.Collection
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
"no type",
|
||||
models.Collection{Options: options},
|
||||
expectedSerialization,
|
||||
},
|
||||
{
|
||||
"unknown type",
|
||||
models.Collection{Type: "anything", Options: options},
|
||||
expectedSerialization,
|
||||
},
|
||||
{
|
||||
"different type",
|
||||
models.Collection{Type: models.CollectionTypeBase, Options: options},
|
||||
expectedSerialization,
|
||||
},
|
||||
{
|
||||
"view type",
|
||||
models.Collection{Type: models.CollectionTypeView, Options: options},
|
||||
expectedSerialization,
|
||||
},
|
||||
}
|
||||
|
||||
for _, s := range scenarios {
|
||||
result := s.collection.ViewOptions()
|
||||
|
||||
encoded, err := json.Marshal(result)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if strEncoded := string(encoded); strEncoded != s.expected {
|
||||
t.Errorf("[%s] Expected \n%v \ngot \n%v", s.name, s.expected, strEncoded)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -218,7 +263,7 @@ func TestNormalizeOptions(t *testing.T) {
|
||||
|
||||
for _, s := range scenarios {
|
||||
if err := s.collection.NormalizeOptions(); err != nil {
|
||||
t.Errorf("(%s) Unexpected error %v", s.name, err)
|
||||
t.Errorf("[%s] Unexpected error %v", s.name, err)
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -228,7 +273,7 @@ func TestNormalizeOptions(t *testing.T) {
|
||||
}
|
||||
|
||||
if strEncoded := string(encoded); strEncoded != s.expected {
|
||||
t.Errorf("(%s) Expected \n%v \ngot \n%v", s.name, s.expected, strEncoded)
|
||||
t.Errorf("[%s] Expected \n%v \ngot \n%v", s.name, s.expected, strEncoded)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -286,7 +331,7 @@ func TestSetOptions(t *testing.T) {
|
||||
|
||||
for _, s := range scenarios {
|
||||
if err := s.collection.SetOptions(s.options); err != nil {
|
||||
t.Errorf("(%s) Unexpected error %v", s.name, err)
|
||||
t.Errorf("[%s] Unexpected error %v", s.name, err)
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -296,7 +341,7 @@ func TestSetOptions(t *testing.T) {
|
||||
}
|
||||
|
||||
if strEncoded := string(encoded); strEncoded != s.expected {
|
||||
t.Errorf("(%s) Expected \n%v \ngot \n%v", s.name, s.expected, strEncoded)
|
||||
t.Errorf("[%s] Expected \n%v \ngot \n%v", s.name, s.expected, strEncoded)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -378,18 +423,61 @@ func TestCollectionAuthOptionsValidate(t *testing.T) {
|
||||
// parse errors
|
||||
errs, ok := result.(validation.Errors)
|
||||
if !ok && result != nil {
|
||||
t.Errorf("(%s) Failed to parse errors %v", s.name, result)
|
||||
t.Errorf("[%s] Failed to parse errors %v", s.name, result)
|
||||
continue
|
||||
}
|
||||
|
||||
if len(errs) != len(s.expectedErrors) {
|
||||
t.Errorf("(%s) Expected error keys %v, got errors \n%v", s.name, s.expectedErrors, result)
|
||||
t.Errorf("[%s] Expected error keys %v, got errors \n%v", s.name, s.expectedErrors, result)
|
||||
continue
|
||||
}
|
||||
|
||||
for key := range errs {
|
||||
if !list.ExistInSlice(key, s.expectedErrors) {
|
||||
t.Errorf("(%s) Unexpected error key %q in \n%v", s.name, key, errs)
|
||||
t.Errorf("[%s] Unexpected error key %q in \n%v", s.name, key, errs)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestCollectionViewOptionsValidate(t *testing.T) {
|
||||
scenarios := []struct {
|
||||
name string
|
||||
options models.CollectionViewOptions
|
||||
expectedErrors []string
|
||||
}{
|
||||
{
|
||||
"empty",
|
||||
models.CollectionViewOptions{},
|
||||
[]string{"query"},
|
||||
},
|
||||
{
|
||||
"valid data",
|
||||
models.CollectionViewOptions{
|
||||
Query: "test123",
|
||||
},
|
||||
[]string{},
|
||||
},
|
||||
}
|
||||
|
||||
for _, s := range scenarios {
|
||||
result := s.options.Validate()
|
||||
|
||||
// parse errors
|
||||
errs, ok := result.(validation.Errors)
|
||||
if !ok && result != nil {
|
||||
t.Errorf("[%s] Failed to parse errors %v", s.name, result)
|
||||
continue
|
||||
}
|
||||
|
||||
if len(errs) != len(s.expectedErrors) {
|
||||
t.Errorf("[%s] Expected error keys %v, got errors \n%v", s.name, s.expectedErrors, result)
|
||||
continue
|
||||
}
|
||||
|
||||
for key := range errs {
|
||||
if !list.ExistInSlice(key, s.expectedErrors) {
|
||||
t.Errorf("[%s] Unexpected error key %q in \n%v", s.name, key, errs)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+12
-2
@@ -59,6 +59,9 @@ func nullStringMapValue(data dbx.NullStringMap, key string) any {
|
||||
|
||||
// NewRecordFromNullStringMap initializes a single new Record model
|
||||
// with data loaded from the provided NullStringMap.
|
||||
//
|
||||
// Note that this method is intended to load and Scan data from a database
|
||||
// result and calls PostScan() which marks the record as "not new".
|
||||
func NewRecordFromNullStringMap(collection *Collection, data dbx.NullStringMap) *Record {
|
||||
resultMap := make(map[string]any, len(data))
|
||||
|
||||
@@ -89,6 +92,9 @@ func NewRecordFromNullStringMap(collection *Collection, data dbx.NullStringMap)
|
||||
|
||||
// NewRecordsFromNullStringMaps initializes a new Record model for
|
||||
// each row in the provided NullStringMap slice.
|
||||
//
|
||||
// Note that this method is intended to load and Scan data from a database
|
||||
// result and calls PostScan() for each record marking them as "not new".
|
||||
func NewRecordsFromNullStringMaps(collection *Collection, rows []dbx.NullStringMap) []*Record {
|
||||
result := make([]*Record, len(rows))
|
||||
|
||||
@@ -469,8 +475,12 @@ func (m *Record) PublicExport() map[string]any {
|
||||
|
||||
// export base model fields
|
||||
result[schema.FieldNameId] = m.GetId()
|
||||
result[schema.FieldNameCreated] = m.GetCreated()
|
||||
result[schema.FieldNameUpdated] = m.GetUpdated()
|
||||
if created := m.GetCreated(); !m.Collection().IsView() || !created.IsZero() {
|
||||
result[schema.FieldNameCreated] = created
|
||||
}
|
||||
if updated := m.GetUpdated(); !m.Collection().IsView() || !updated.IsZero() {
|
||||
result[schema.FieldNameUpdated] = updated
|
||||
}
|
||||
|
||||
// add helper collection reference fields
|
||||
result[schema.FieldNameCollectionId] = m.collection.Id
|
||||
|
||||
@@ -139,7 +139,7 @@ type SchemaField struct {
|
||||
func (f *SchemaField) ColDefinition() string {
|
||||
switch f.Type {
|
||||
case FieldTypeNumber:
|
||||
return "REAL DEFAULT 0"
|
||||
return "NUMERIC DEFAULT 0"
|
||||
case FieldTypeBool:
|
||||
return "BOOLEAN DEFAULT FALSE"
|
||||
case FieldTypeJson:
|
||||
|
||||
@@ -67,7 +67,7 @@ func TestSchemaFieldColDefinition(t *testing.T) {
|
||||
},
|
||||
{
|
||||
schema.SchemaField{Type: schema.FieldTypeNumber, Name: "test"},
|
||||
"REAL DEFAULT 0",
|
||||
"NUMERIC DEFAULT 0",
|
||||
},
|
||||
{
|
||||
schema.SchemaField{Type: schema.FieldTypeBool, Name: "test"},
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
package models
|
||||
|
||||
import "github.com/pocketbase/pocketbase/tools/types"
|
||||
|
||||
type TableInfoRow struct {
|
||||
// the `db:"pk"` tag has special semantic so we cannot rename
|
||||
// the original field without specifying a custom mapper
|
||||
PK int
|
||||
|
||||
Index int `db:"cid"`
|
||||
Name string `db:"name"`
|
||||
Type string `db:"type"`
|
||||
NotNull bool `db:"notnull"`
|
||||
DefaultValue types.JsonRaw `db:"dflt_value"`
|
||||
}
|
||||
Reference in New Issue
Block a user