| 
									
										
										
										
											2024-09-30 00:23:19 +08:00
										 |  |  | package core | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | import ( | 
					
						
							|  |  |  | 	"encoding/json" | 
					
						
							|  |  |  | 	"fmt" | 
					
						
							| 
									
										
										
										
											2024-11-04 16:51:32 +08:00
										 |  |  | 	"strconv" | 
					
						
							| 
									
										
										
										
											2024-09-30 00:23:19 +08:00
										 |  |  | 	"strings" | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-11-04 16:51:32 +08:00
										 |  |  | 	"github.com/pocketbase/dbx" | 
					
						
							| 
									
										
										
										
											2024-09-30 00:23:19 +08:00
										 |  |  | 	"github.com/pocketbase/pocketbase/tools/dbutils" | 
					
						
							|  |  |  | 	"github.com/pocketbase/pocketbase/tools/hook" | 
					
						
							|  |  |  | 	"github.com/pocketbase/pocketbase/tools/security" | 
					
						
							|  |  |  | 	"github.com/pocketbase/pocketbase/tools/types" | 
					
						
							|  |  |  | 	"github.com/spf13/cast" | 
					
						
							|  |  |  | ) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | var ( | 
					
						
							|  |  |  | 	_ Model        = (*Collection)(nil) | 
					
						
							|  |  |  | 	_ DBExporter   = (*Collection)(nil) | 
					
						
							|  |  |  | 	_ FilesManager = (*Collection)(nil) | 
					
						
							|  |  |  | ) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | const ( | 
					
						
							|  |  |  | 	CollectionTypeBase = "base" | 
					
						
							|  |  |  | 	CollectionTypeAuth = "auth" | 
					
						
							|  |  |  | 	CollectionTypeView = "view" | 
					
						
							|  |  |  | ) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | const systemHookIdCollection = "__pbCollectionSystemHook__" | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func (app *BaseApp) registerCollectionHooks() { | 
					
						
							|  |  |  | 	app.OnModelValidate().Bind(&hook.Handler[*ModelEvent]{ | 
					
						
							|  |  |  | 		Id: systemHookIdCollection, | 
					
						
							|  |  |  | 		Func: func(me *ModelEvent) error { | 
					
						
							|  |  |  | 			if ce, ok := newCollectionEventFromModelEvent(me); ok { | 
					
						
							|  |  |  | 				return me.App.OnCollectionValidate().Trigger(ce, func(ce *CollectionEvent) error { | 
					
						
							|  |  |  | 					syncModelEventWithCollectionEvent(me, ce) | 
					
						
							|  |  |  | 					return me.Next() | 
					
						
							|  |  |  | 				}) | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			return me.Next() | 
					
						
							|  |  |  | 		}, | 
					
						
							|  |  |  | 		Priority: -99, | 
					
						
							|  |  |  | 	}) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	app.OnModelCreate().Bind(&hook.Handler[*ModelEvent]{ | 
					
						
							|  |  |  | 		Id: systemHookIdCollection, | 
					
						
							|  |  |  | 		Func: func(me *ModelEvent) error { | 
					
						
							|  |  |  | 			if ce, ok := newCollectionEventFromModelEvent(me); ok { | 
					
						
							|  |  |  | 				return me.App.OnCollectionCreate().Trigger(ce, func(ce *CollectionEvent) error { | 
					
						
							|  |  |  | 					syncModelEventWithCollectionEvent(me, ce) | 
					
						
							|  |  |  | 					return me.Next() | 
					
						
							|  |  |  | 				}) | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			return me.Next() | 
					
						
							|  |  |  | 		}, | 
					
						
							|  |  |  | 		Priority: -99, | 
					
						
							|  |  |  | 	}) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	app.OnModelCreateExecute().Bind(&hook.Handler[*ModelEvent]{ | 
					
						
							|  |  |  | 		Id: systemHookIdCollection, | 
					
						
							|  |  |  | 		Func: func(me *ModelEvent) error { | 
					
						
							|  |  |  | 			if ce, ok := newCollectionEventFromModelEvent(me); ok { | 
					
						
							|  |  |  | 				return me.App.OnCollectionCreateExecute().Trigger(ce, func(ce *CollectionEvent) error { | 
					
						
							|  |  |  | 					syncModelEventWithCollectionEvent(me, ce) | 
					
						
							|  |  |  | 					return me.Next() | 
					
						
							|  |  |  | 				}) | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			return me.Next() | 
					
						
							|  |  |  | 		}, | 
					
						
							|  |  |  | 		Priority: -99, | 
					
						
							|  |  |  | 	}) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	app.OnModelAfterCreateSuccess().Bind(&hook.Handler[*ModelEvent]{ | 
					
						
							|  |  |  | 		Id: systemHookIdCollection, | 
					
						
							|  |  |  | 		Func: func(me *ModelEvent) error { | 
					
						
							|  |  |  | 			if ce, ok := newCollectionEventFromModelEvent(me); ok { | 
					
						
							|  |  |  | 				return me.App.OnCollectionAfterCreateSuccess().Trigger(ce, func(ce *CollectionEvent) error { | 
					
						
							|  |  |  | 					syncModelEventWithCollectionEvent(me, ce) | 
					
						
							|  |  |  | 					return me.Next() | 
					
						
							|  |  |  | 				}) | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			return me.Next() | 
					
						
							|  |  |  | 		}, | 
					
						
							|  |  |  | 		Priority: -99, | 
					
						
							|  |  |  | 	}) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	app.OnModelAfterCreateError().Bind(&hook.Handler[*ModelErrorEvent]{ | 
					
						
							|  |  |  | 		Id: systemHookIdCollection, | 
					
						
							|  |  |  | 		Func: func(me *ModelErrorEvent) error { | 
					
						
							|  |  |  | 			if ce, ok := newCollectionErrorEventFromModelErrorEvent(me); ok { | 
					
						
							|  |  |  | 				return me.App.OnCollectionAfterCreateError().Trigger(ce, func(ce *CollectionErrorEvent) error { | 
					
						
							|  |  |  | 					syncModelErrorEventWithCollectionErrorEvent(me, ce) | 
					
						
							|  |  |  | 					return me.Next() | 
					
						
							|  |  |  | 				}) | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			return me.Next() | 
					
						
							|  |  |  | 		}, | 
					
						
							|  |  |  | 		Priority: -99, | 
					
						
							|  |  |  | 	}) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	app.OnModelUpdate().Bind(&hook.Handler[*ModelEvent]{ | 
					
						
							|  |  |  | 		Id: systemHookIdCollection, | 
					
						
							|  |  |  | 		Func: func(me *ModelEvent) error { | 
					
						
							|  |  |  | 			if ce, ok := newCollectionEventFromModelEvent(me); ok { | 
					
						
							|  |  |  | 				return me.App.OnCollectionUpdate().Trigger(ce, func(ce *CollectionEvent) error { | 
					
						
							|  |  |  | 					syncModelEventWithCollectionEvent(me, ce) | 
					
						
							|  |  |  | 					return me.Next() | 
					
						
							|  |  |  | 				}) | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			return me.Next() | 
					
						
							|  |  |  | 		}, | 
					
						
							|  |  |  | 		Priority: -99, | 
					
						
							|  |  |  | 	}) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	app.OnModelUpdateExecute().Bind(&hook.Handler[*ModelEvent]{ | 
					
						
							|  |  |  | 		Id: systemHookIdCollection, | 
					
						
							|  |  |  | 		Func: func(me *ModelEvent) error { | 
					
						
							|  |  |  | 			if ce, ok := newCollectionEventFromModelEvent(me); ok { | 
					
						
							|  |  |  | 				return me.App.OnCollectionUpdateExecute().Trigger(ce, func(ce *CollectionEvent) error { | 
					
						
							|  |  |  | 					syncModelEventWithCollectionEvent(me, ce) | 
					
						
							|  |  |  | 					return me.Next() | 
					
						
							|  |  |  | 				}) | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			return me.Next() | 
					
						
							|  |  |  | 		}, | 
					
						
							|  |  |  | 		Priority: -99, | 
					
						
							|  |  |  | 	}) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	app.OnModelAfterUpdateSuccess().Bind(&hook.Handler[*ModelEvent]{ | 
					
						
							|  |  |  | 		Id: systemHookIdCollection, | 
					
						
							|  |  |  | 		Func: func(me *ModelEvent) error { | 
					
						
							|  |  |  | 			if ce, ok := newCollectionEventFromModelEvent(me); ok { | 
					
						
							|  |  |  | 				return me.App.OnCollectionAfterUpdateSuccess().Trigger(ce, func(ce *CollectionEvent) error { | 
					
						
							|  |  |  | 					syncModelEventWithCollectionEvent(me, ce) | 
					
						
							|  |  |  | 					return me.Next() | 
					
						
							|  |  |  | 				}) | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			return me.Next() | 
					
						
							|  |  |  | 		}, | 
					
						
							|  |  |  | 		Priority: -99, | 
					
						
							|  |  |  | 	}) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	app.OnModelAfterUpdateError().Bind(&hook.Handler[*ModelErrorEvent]{ | 
					
						
							|  |  |  | 		Id: systemHookIdCollection, | 
					
						
							|  |  |  | 		Func: func(me *ModelErrorEvent) error { | 
					
						
							|  |  |  | 			if ce, ok := newCollectionErrorEventFromModelErrorEvent(me); ok { | 
					
						
							|  |  |  | 				return me.App.OnCollectionAfterUpdateError().Trigger(ce, func(ce *CollectionErrorEvent) error { | 
					
						
							|  |  |  | 					syncModelErrorEventWithCollectionErrorEvent(me, ce) | 
					
						
							|  |  |  | 					return me.Next() | 
					
						
							|  |  |  | 				}) | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			return me.Next() | 
					
						
							|  |  |  | 		}, | 
					
						
							|  |  |  | 		Priority: -99, | 
					
						
							|  |  |  | 	}) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	app.OnModelDelete().Bind(&hook.Handler[*ModelEvent]{ | 
					
						
							|  |  |  | 		Id: systemHookIdCollection, | 
					
						
							|  |  |  | 		Func: func(me *ModelEvent) error { | 
					
						
							|  |  |  | 			if ce, ok := newCollectionEventFromModelEvent(me); ok { | 
					
						
							|  |  |  | 				return me.App.OnCollectionDelete().Trigger(ce, func(ce *CollectionEvent) error { | 
					
						
							|  |  |  | 					syncModelEventWithCollectionEvent(me, ce) | 
					
						
							|  |  |  | 					return me.Next() | 
					
						
							|  |  |  | 				}) | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			return me.Next() | 
					
						
							|  |  |  | 		}, | 
					
						
							|  |  |  | 		Priority: -99, | 
					
						
							|  |  |  | 	}) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	app.OnModelDeleteExecute().Bind(&hook.Handler[*ModelEvent]{ | 
					
						
							|  |  |  | 		Id: systemHookIdCollection, | 
					
						
							|  |  |  | 		Func: func(me *ModelEvent) error { | 
					
						
							|  |  |  | 			if ce, ok := newCollectionEventFromModelEvent(me); ok { | 
					
						
							|  |  |  | 				return me.App.OnCollectionDeleteExecute().Trigger(ce, func(ce *CollectionEvent) error { | 
					
						
							|  |  |  | 					syncModelEventWithCollectionEvent(me, ce) | 
					
						
							|  |  |  | 					return me.Next() | 
					
						
							|  |  |  | 				}) | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			return me.Next() | 
					
						
							|  |  |  | 		}, | 
					
						
							|  |  |  | 		Priority: -99, | 
					
						
							|  |  |  | 	}) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	app.OnModelAfterDeleteSuccess().Bind(&hook.Handler[*ModelEvent]{ | 
					
						
							|  |  |  | 		Id: systemHookIdCollection, | 
					
						
							|  |  |  | 		Func: func(me *ModelEvent) error { | 
					
						
							|  |  |  | 			if ce, ok := newCollectionEventFromModelEvent(me); ok { | 
					
						
							|  |  |  | 				return me.App.OnCollectionAfterDeleteSuccess().Trigger(ce, func(ce *CollectionEvent) error { | 
					
						
							|  |  |  | 					syncModelEventWithCollectionEvent(me, ce) | 
					
						
							|  |  |  | 					return me.Next() | 
					
						
							|  |  |  | 				}) | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			return me.Next() | 
					
						
							|  |  |  | 		}, | 
					
						
							|  |  |  | 		Priority: -99, | 
					
						
							|  |  |  | 	}) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	app.OnModelAfterDeleteError().Bind(&hook.Handler[*ModelErrorEvent]{ | 
					
						
							|  |  |  | 		Id: systemHookIdCollection, | 
					
						
							|  |  |  | 		Func: func(me *ModelErrorEvent) error { | 
					
						
							|  |  |  | 			if ce, ok := newCollectionErrorEventFromModelErrorEvent(me); ok { | 
					
						
							|  |  |  | 				return me.App.OnCollectionAfterDeleteError().Trigger(ce, func(ce *CollectionErrorEvent) error { | 
					
						
							|  |  |  | 					syncModelErrorEventWithCollectionErrorEvent(me, ce) | 
					
						
							|  |  |  | 					return me.Next() | 
					
						
							|  |  |  | 				}) | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			return me.Next() | 
					
						
							|  |  |  | 		}, | 
					
						
							|  |  |  | 		Priority: -99, | 
					
						
							|  |  |  | 	}) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	//  --------------------------------------------------------------
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	app.OnCollectionValidate().Bind(&hook.Handler[*CollectionEvent]{ | 
					
						
							|  |  |  | 		Id:       systemHookIdCollection, | 
					
						
							|  |  |  | 		Func:     onCollectionValidate, | 
					
						
							|  |  |  | 		Priority: 99, | 
					
						
							|  |  |  | 	}) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	app.OnCollectionCreate().Bind(&hook.Handler[*CollectionEvent]{ | 
					
						
							|  |  |  | 		Id:       systemHookIdCollection, | 
					
						
							|  |  |  | 		Func:     onCollectionSave, | 
					
						
							|  |  |  | 		Priority: -99, | 
					
						
							|  |  |  | 	}) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	app.OnCollectionUpdate().Bind(&hook.Handler[*CollectionEvent]{ | 
					
						
							|  |  |  | 		Id:       systemHookIdCollection, | 
					
						
							|  |  |  | 		Func:     onCollectionSave, | 
					
						
							|  |  |  | 		Priority: -99, | 
					
						
							|  |  |  | 	}) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	app.OnCollectionCreateExecute().Bind(&hook.Handler[*CollectionEvent]{ | 
					
						
							|  |  |  | 		Id:   systemHookIdCollection, | 
					
						
							|  |  |  | 		Func: onCollectionSaveExecute, | 
					
						
							|  |  |  | 		// execute as latest as possible, aka. closer to the db action to minimize the transactions lock time
 | 
					
						
							|  |  |  | 		Priority: 99, | 
					
						
							|  |  |  | 	}) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	app.OnCollectionUpdateExecute().Bind(&hook.Handler[*CollectionEvent]{ | 
					
						
							|  |  |  | 		Id:       systemHookIdCollection, | 
					
						
							|  |  |  | 		Func:     onCollectionSaveExecute, | 
					
						
							|  |  |  | 		Priority: 99, // execute as latest as possible, aka. closer to the db action to minimize the transactions lock time
 | 
					
						
							|  |  |  | 	}) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	app.OnCollectionDeleteExecute().Bind(&hook.Handler[*CollectionEvent]{ | 
					
						
							|  |  |  | 		Id:       systemHookIdCollection, | 
					
						
							|  |  |  | 		Func:     onCollectionDeleteExecute, | 
					
						
							|  |  |  | 		Priority: 99, // execute as latest as possible, aka. closer to the db action to minimize the transactions lock time
 | 
					
						
							|  |  |  | 	}) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// reload cache on failure
 | 
					
						
							|  |  |  | 	// ---
 | 
					
						
							|  |  |  | 	onErrorReloadCachedCollections := func(ce *CollectionErrorEvent) error { | 
					
						
							|  |  |  | 		if err := ce.App.ReloadCachedCollections(); err != nil { | 
					
						
							| 
									
										
										
										
											2024-10-18 12:55:25 +08:00
										 |  |  | 			ce.App.Logger().Warn("Failed to reload collections cache after collection change error", "error", err) | 
					
						
							| 
									
										
										
										
											2024-09-30 00:23:19 +08:00
										 |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		return ce.Next() | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	app.OnCollectionAfterCreateError().Bind(&hook.Handler[*CollectionErrorEvent]{ | 
					
						
							|  |  |  | 		Id:       systemHookIdCollection, | 
					
						
							|  |  |  | 		Func:     onErrorReloadCachedCollections, | 
					
						
							|  |  |  | 		Priority: -99, | 
					
						
							|  |  |  | 	}) | 
					
						
							|  |  |  | 	app.OnCollectionAfterUpdateError().Bind(&hook.Handler[*CollectionErrorEvent]{ | 
					
						
							|  |  |  | 		Id:       systemHookIdCollection, | 
					
						
							|  |  |  | 		Func:     onErrorReloadCachedCollections, | 
					
						
							|  |  |  | 		Priority: -99, | 
					
						
							|  |  |  | 	}) | 
					
						
							|  |  |  | 	app.OnCollectionAfterDeleteError().Bind(&hook.Handler[*CollectionErrorEvent]{ | 
					
						
							|  |  |  | 		Id:       systemHookIdCollection, | 
					
						
							|  |  |  | 		Func:     onErrorReloadCachedCollections, | 
					
						
							|  |  |  | 		Priority: -99, | 
					
						
							|  |  |  | 	}) | 
					
						
							|  |  |  | 	// ---
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	app.OnBootstrap().Bind(&hook.Handler[*BootstrapEvent]{ | 
					
						
							|  |  |  | 		Id: systemHookIdCollection, | 
					
						
							|  |  |  | 		Func: func(e *BootstrapEvent) error { | 
					
						
							|  |  |  | 			if err := e.Next(); err != nil { | 
					
						
							|  |  |  | 				return err | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			if err := e.App.ReloadCachedCollections(); err != nil { | 
					
						
							|  |  |  | 				return fmt.Errorf("failed to load collections cache: %w", err) | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			return nil | 
					
						
							|  |  |  | 		}, | 
					
						
							|  |  |  | 		Priority: 99, // execute as latest as possible
 | 
					
						
							|  |  |  | 	}) | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // @todo experiment eventually replacing the rules *string with a struct?
 | 
					
						
							|  |  |  | type baseCollection struct { | 
					
						
							|  |  |  | 	BaseModel | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	disableIntegrityChecks bool | 
					
						
							| 
									
										
										
										
											2024-11-04 16:51:32 +08:00
										 |  |  | 	autogeneratedId        string | 
					
						
							| 
									
										
										
										
											2024-09-30 00:23:19 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	ListRule   *string `db:"listRule" json:"listRule" form:"listRule"` | 
					
						
							|  |  |  | 	ViewRule   *string `db:"viewRule" json:"viewRule" form:"viewRule"` | 
					
						
							|  |  |  | 	CreateRule *string `db:"createRule" json:"createRule" form:"createRule"` | 
					
						
							|  |  |  | 	UpdateRule *string `db:"updateRule" json:"updateRule" form:"updateRule"` | 
					
						
							|  |  |  | 	DeleteRule *string `db:"deleteRule" json:"deleteRule" form:"deleteRule"` | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// RawOptions represents the raw serialized collection option loaded from the DB.
 | 
					
						
							|  |  |  | 	// NB! This field shouldn't be modified manually. It is automatically updated
 | 
					
						
							|  |  |  | 	// with the collection type specific option before save.
 | 
					
						
							|  |  |  | 	RawOptions types.JSONRaw `db:"options" json:"-" xml:"-" form:"-"` | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	Name    string                  `db:"name" json:"name" form:"name"` | 
					
						
							|  |  |  | 	Type    string                  `db:"type" json:"type" form:"type"` | 
					
						
							|  |  |  | 	Fields  FieldsList              `db:"fields" json:"fields" form:"fields"` | 
					
						
							|  |  |  | 	Indexes types.JSONArray[string] `db:"indexes" json:"indexes" form:"indexes"` | 
					
						
							|  |  |  | 	Created types.DateTime          `db:"created" json:"created"` | 
					
						
							|  |  |  | 	Updated types.DateTime          `db:"updated" json:"updated"` | 
					
						
							| 
									
										
										
										
											2024-10-24 13:37:22 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	// System prevents the collection rename, deletion and rules change.
 | 
					
						
							|  |  |  | 	// It is used primarily for internal purposes for collections like "_superusers", "_externalAuths", etc.
 | 
					
						
							|  |  |  | 	System bool `db:"system" json:"system" form:"system"` | 
					
						
							| 
									
										
										
										
											2024-09-30 00:23:19 +08:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Collection defines the table, fields and various options related to a set of records.
 | 
					
						
							|  |  |  | type Collection struct { | 
					
						
							|  |  |  | 	baseCollection | 
					
						
							|  |  |  | 	collectionAuthOptions | 
					
						
							|  |  |  | 	collectionViewOptions | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // NewCollection initializes and returns a new Collection model with the specified type and name.
 | 
					
						
							| 
									
										
										
										
											2024-11-03 16:44:48 +08:00
										 |  |  | //
 | 
					
						
							|  |  |  | // It also loads the minimal default configuration for the collection
 | 
					
						
							|  |  |  | // (eg. system fields, indexes, type specific options, etc.).
 | 
					
						
							|  |  |  | func NewCollection(typ, name string, optId ...string) *Collection { | 
					
						
							| 
									
										
										
										
											2024-09-30 00:23:19 +08:00
										 |  |  | 	switch typ { | 
					
						
							|  |  |  | 	case CollectionTypeAuth: | 
					
						
							| 
									
										
										
										
											2024-11-03 16:44:48 +08:00
										 |  |  | 		return NewAuthCollection(name, optId...) | 
					
						
							| 
									
										
										
										
											2024-09-30 00:23:19 +08:00
										 |  |  | 	case CollectionTypeView: | 
					
						
							| 
									
										
										
										
											2024-11-03 16:44:48 +08:00
										 |  |  | 		return NewViewCollection(name, optId...) | 
					
						
							| 
									
										
										
										
											2024-09-30 00:23:19 +08:00
										 |  |  | 	default: | 
					
						
							| 
									
										
										
										
											2024-11-03 16:44:48 +08:00
										 |  |  | 		return NewBaseCollection(name, optId...) | 
					
						
							| 
									
										
										
										
											2024-09-30 00:23:19 +08:00
										 |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // NewBaseCollection initializes and returns a new "base" Collection model.
 | 
					
						
							| 
									
										
										
										
											2024-11-03 16:44:48 +08:00
										 |  |  | //
 | 
					
						
							|  |  |  | // It also loads the minimal default configuration for the collection
 | 
					
						
							|  |  |  | // (eg. system fields, indexes, type specific options, etc.).
 | 
					
						
							|  |  |  | func NewBaseCollection(name string, optId ...string) *Collection { | 
					
						
							| 
									
										
										
										
											2024-09-30 00:23:19 +08:00
										 |  |  | 	m := &Collection{} | 
					
						
							|  |  |  | 	m.Name = name | 
					
						
							|  |  |  | 	m.Type = CollectionTypeBase | 
					
						
							| 
									
										
										
										
											2024-11-03 16:44:48 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-11-04 16:51:32 +08:00
										 |  |  | 	// @todo consider removing once inferred composite literals are supported
 | 
					
						
							| 
									
										
										
										
											2024-11-03 16:44:48 +08:00
										 |  |  | 	if len(optId) > 0 { | 
					
						
							|  |  |  | 		m.Id = optId[0] | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-09-30 00:23:19 +08:00
										 |  |  | 	m.initDefaultId() | 
					
						
							|  |  |  | 	m.initDefaultFields() | 
					
						
							| 
									
										
										
										
											2024-11-03 16:44:48 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-09-30 00:23:19 +08:00
										 |  |  | 	return m | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // NewViewCollection initializes and returns a new "view" Collection model.
 | 
					
						
							| 
									
										
										
										
											2024-11-03 16:44:48 +08:00
										 |  |  | //
 | 
					
						
							|  |  |  | // It also loads the minimal default configuration for the collection
 | 
					
						
							|  |  |  | // (eg. system fields, indexes, type specific options, etc.).
 | 
					
						
							|  |  |  | func NewViewCollection(name string, optId ...string) *Collection { | 
					
						
							| 
									
										
										
										
											2024-09-30 00:23:19 +08:00
										 |  |  | 	m := &Collection{} | 
					
						
							|  |  |  | 	m.Name = name | 
					
						
							|  |  |  | 	m.Type = CollectionTypeView | 
					
						
							| 
									
										
										
										
											2024-11-03 16:44:48 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-11-04 16:51:32 +08:00
										 |  |  | 	// @todo consider removing once inferred composite literals are supported
 | 
					
						
							| 
									
										
										
										
											2024-11-03 16:44:48 +08:00
										 |  |  | 	if len(optId) > 0 { | 
					
						
							|  |  |  | 		m.Id = optId[0] | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-09-30 00:23:19 +08:00
										 |  |  | 	m.initDefaultId() | 
					
						
							|  |  |  | 	m.initDefaultFields() | 
					
						
							| 
									
										
										
										
											2024-11-03 16:44:48 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-09-30 00:23:19 +08:00
										 |  |  | 	return m | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // NewAuthCollection initializes and returns a new "auth" Collection model.
 | 
					
						
							| 
									
										
										
										
											2024-11-03 16:44:48 +08:00
										 |  |  | //
 | 
					
						
							|  |  |  | // It also loads the minimal default configuration for the collection
 | 
					
						
							|  |  |  | // (eg. system fields, indexes, type specific options, etc.).
 | 
					
						
							|  |  |  | func NewAuthCollection(name string, optId ...string) *Collection { | 
					
						
							| 
									
										
										
										
											2024-09-30 00:23:19 +08:00
										 |  |  | 	m := &Collection{} | 
					
						
							|  |  |  | 	m.Name = name | 
					
						
							|  |  |  | 	m.Type = CollectionTypeAuth | 
					
						
							| 
									
										
										
										
											2024-11-03 16:44:48 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-11-04 16:51:32 +08:00
										 |  |  | 	// @todo consider removing once inferred composite literals are supported
 | 
					
						
							| 
									
										
										
										
											2024-11-03 16:44:48 +08:00
										 |  |  | 	if len(optId) > 0 { | 
					
						
							|  |  |  | 		m.Id = optId[0] | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-09-30 00:23:19 +08:00
										 |  |  | 	m.initDefaultId() | 
					
						
							|  |  |  | 	m.initDefaultFields() | 
					
						
							|  |  |  | 	m.setDefaultAuthOptions() | 
					
						
							| 
									
										
										
										
											2024-11-03 16:44:48 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-09-30 00:23:19 +08:00
										 |  |  | 	return m | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // TableName returns the Collection model SQL table name.
 | 
					
						
							|  |  |  | func (m *Collection) TableName() string { | 
					
						
							|  |  |  | 	return "_collections" | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // BaseFilesPath returns the storage dir path used by the collection.
 | 
					
						
							|  |  |  | func (m *Collection) BaseFilesPath() string { | 
					
						
							|  |  |  | 	return m.Id | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // IsBase checks if the current collection has "base" type.
 | 
					
						
							|  |  |  | func (m *Collection) IsBase() bool { | 
					
						
							|  |  |  | 	return m.Type == CollectionTypeBase | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // 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 | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // IntegrityChecks toggles the current collection integrity checks (ex. checking references on delete).
 | 
					
						
							|  |  |  | func (m *Collection) IntegrityChecks(enable bool) { | 
					
						
							|  |  |  | 	m.disableIntegrityChecks = !enable | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // PostScan implements the [dbx.PostScanner] interface to auto unmarshal
 | 
					
						
							|  |  |  | // the raw serialized options into the concrete type specific fields.
 | 
					
						
							|  |  |  | func (m *Collection) PostScan() error { | 
					
						
							|  |  |  | 	if err := m.BaseModel.PostScan(); err != nil { | 
					
						
							|  |  |  | 		return err | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return m.unmarshalRawOptions() | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func (m *Collection) unmarshalRawOptions() error { | 
					
						
							|  |  |  | 	raw, err := m.RawOptions.MarshalJSON() | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		return nil | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	switch m.Type { | 
					
						
							|  |  |  | 	case CollectionTypeView: | 
					
						
							|  |  |  | 		return json.Unmarshal(raw, &m.collectionViewOptions) | 
					
						
							|  |  |  | 	case CollectionTypeAuth: | 
					
						
							|  |  |  | 		return json.Unmarshal(raw, &m.collectionAuthOptions) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return nil | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // UnmarshalJSON implements the [json.Unmarshaler] interface.
 | 
					
						
							|  |  |  | //
 | 
					
						
							|  |  |  | // For new/"blank" Collection models it replaces the model with a factory
 | 
					
						
							|  |  |  | // instance and then unmarshal the provided data one on top of it.
 | 
					
						
							|  |  |  | func (m *Collection) UnmarshalJSON(b []byte) error { | 
					
						
							|  |  |  | 	type alias *Collection | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// initialize the default fields
 | 
					
						
							|  |  |  | 	// (e.g. in case the collection was NOT created using the designated factories)
 | 
					
						
							|  |  |  | 	if m.IsNew() && m.Type == "" { | 
					
						
							|  |  |  | 		minimal := &struct { | 
					
						
							|  |  |  | 			Type string `json:"type"` | 
					
						
							|  |  |  | 			Name string `json:"name"` | 
					
						
							| 
									
										
										
										
											2024-11-05 01:03:33 +08:00
										 |  |  | 			Id   string `json:"id"` | 
					
						
							| 
									
										
										
										
											2024-09-30 00:23:19 +08:00
										 |  |  | 		}{} | 
					
						
							|  |  |  | 		if err := json.Unmarshal(b, minimal); err != nil { | 
					
						
							|  |  |  | 			return err | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-11-05 01:03:33 +08:00
										 |  |  | 		blank := NewCollection(minimal.Type, minimal.Name, minimal.Id) | 
					
						
							| 
									
										
										
										
											2024-09-30 00:23:19 +08:00
										 |  |  | 		*m = *blank | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return json.Unmarshal(b, alias(m)) | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // MarshalJSON implements the [json.Marshaler] interface.
 | 
					
						
							|  |  |  | //
 | 
					
						
							|  |  |  | // Note that non-type related fields are ignored from the serialization
 | 
					
						
							|  |  |  | // (ex. for "view" colections the "auth" fields are skipped).
 | 
					
						
							|  |  |  | func (m Collection) MarshalJSON() ([]byte, error) { | 
					
						
							|  |  |  | 	switch m.Type { | 
					
						
							|  |  |  | 	case CollectionTypeView: | 
					
						
							|  |  |  | 		return json.Marshal(struct { | 
					
						
							|  |  |  | 			baseCollection | 
					
						
							|  |  |  | 			collectionViewOptions | 
					
						
							|  |  |  | 		}{m.baseCollection, m.collectionViewOptions}) | 
					
						
							|  |  |  | 	case CollectionTypeAuth: | 
					
						
							|  |  |  | 		alias := struct { | 
					
						
							|  |  |  | 			baseCollection | 
					
						
							|  |  |  | 			collectionAuthOptions | 
					
						
							|  |  |  | 		}{m.baseCollection, m.collectionAuthOptions} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		// ensure that it is always returned as array
 | 
					
						
							|  |  |  | 		if alias.OAuth2.Providers == nil { | 
					
						
							|  |  |  | 			alias.OAuth2.Providers = []OAuth2ProviderConfig{} | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		// hide secret keys from the serialization
 | 
					
						
							|  |  |  | 		alias.AuthToken.Secret = "" | 
					
						
							|  |  |  | 		alias.FileToken.Secret = "" | 
					
						
							|  |  |  | 		alias.PasswordResetToken.Secret = "" | 
					
						
							|  |  |  | 		alias.EmailChangeToken.Secret = "" | 
					
						
							|  |  |  | 		alias.VerificationToken.Secret = "" | 
					
						
							|  |  |  | 		for i := range alias.OAuth2.Providers { | 
					
						
							|  |  |  | 			alias.OAuth2.Providers[i].ClientSecret = "" | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		return json.Marshal(alias) | 
					
						
							|  |  |  | 	default: | 
					
						
							|  |  |  | 		return json.Marshal(m.baseCollection) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // String returns a string representation of the current collection.
 | 
					
						
							|  |  |  | func (m Collection) String() string { | 
					
						
							|  |  |  | 	raw, _ := json.Marshal(m) | 
					
						
							|  |  |  | 	return string(raw) | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // DBExport prepares and exports the current collection data for db persistence.
 | 
					
						
							|  |  |  | func (m *Collection) DBExport(app App) (map[string]any, error) { | 
					
						
							|  |  |  | 	result := map[string]any{ | 
					
						
							|  |  |  | 		"id":         m.Id, | 
					
						
							|  |  |  | 		"type":       m.Type, | 
					
						
							|  |  |  | 		"listRule":   m.ListRule, | 
					
						
							|  |  |  | 		"viewRule":   m.ViewRule, | 
					
						
							|  |  |  | 		"createRule": m.CreateRule, | 
					
						
							|  |  |  | 		"updateRule": m.UpdateRule, | 
					
						
							|  |  |  | 		"deleteRule": m.DeleteRule, | 
					
						
							|  |  |  | 		"name":       m.Name, | 
					
						
							|  |  |  | 		"fields":     m.Fields, | 
					
						
							|  |  |  | 		"indexes":    m.Indexes, | 
					
						
							|  |  |  | 		"system":     m.System, | 
					
						
							|  |  |  | 		"created":    m.Created, | 
					
						
							|  |  |  | 		"updated":    m.Updated, | 
					
						
							|  |  |  | 		"options":    `{}`, | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	switch m.Type { | 
					
						
							|  |  |  | 	case CollectionTypeView: | 
					
						
							|  |  |  | 		if raw, err := types.ParseJSONRaw(m.collectionViewOptions); err == nil { | 
					
						
							|  |  |  | 			result["options"] = raw | 
					
						
							|  |  |  | 		} else { | 
					
						
							|  |  |  | 			return nil, err | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	case CollectionTypeAuth: | 
					
						
							|  |  |  | 		if raw, err := types.ParseJSONRaw(m.collectionAuthOptions); err == nil { | 
					
						
							|  |  |  | 			result["options"] = raw | 
					
						
							|  |  |  | 		} else { | 
					
						
							|  |  |  | 			return nil, err | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return result, nil | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // GetIndex returns s single Collection index expression by its name.
 | 
					
						
							|  |  |  | func (m *Collection) GetIndex(name string) string { | 
					
						
							|  |  |  | 	for _, idx := range m.Indexes { | 
					
						
							|  |  |  | 		if strings.EqualFold(dbutils.ParseIndex(idx).IndexName, name) { | 
					
						
							|  |  |  | 			return idx | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return "" | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // AddIndex adds a new index into the current collection.
 | 
					
						
							|  |  |  | //
 | 
					
						
							|  |  |  | // If the collection has an existing index matching the new name it will be replaced with the new one.
 | 
					
						
							|  |  |  | func (m *Collection) AddIndex(name string, unique bool, columnsExpr string, optWhereExpr string) { | 
					
						
							|  |  |  | 	m.RemoveIndex(name) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	var idx strings.Builder | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	idx.WriteString("CREATE ") | 
					
						
							|  |  |  | 	if unique { | 
					
						
							|  |  |  | 		idx.WriteString("UNIQUE ") | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	idx.WriteString("INDEX `") | 
					
						
							|  |  |  | 	idx.WriteString(name) | 
					
						
							|  |  |  | 	idx.WriteString("` ") | 
					
						
							|  |  |  | 	idx.WriteString("ON `") | 
					
						
							|  |  |  | 	idx.WriteString(m.Name) | 
					
						
							|  |  |  | 	idx.WriteString("` (") | 
					
						
							|  |  |  | 	idx.WriteString(columnsExpr) | 
					
						
							|  |  |  | 	idx.WriteString(")") | 
					
						
							|  |  |  | 	if optWhereExpr != "" { | 
					
						
							|  |  |  | 		idx.WriteString(" WHERE ") | 
					
						
							|  |  |  | 		idx.WriteString(optWhereExpr) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	m.Indexes = append(m.Indexes, idx.String()) | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // RemoveIndex removes a single index with the specified name from the current collection.
 | 
					
						
							|  |  |  | func (m *Collection) RemoveIndex(name string) { | 
					
						
							|  |  |  | 	for i, idx := range m.Indexes { | 
					
						
							|  |  |  | 		if strings.EqualFold(dbutils.ParseIndex(idx).IndexName, name) { | 
					
						
							|  |  |  | 			m.Indexes = append(m.Indexes[:i], m.Indexes[i+1:]...) | 
					
						
							|  |  |  | 			return | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // delete hook
 | 
					
						
							|  |  |  | // -------------------------------------------------------------------
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func onCollectionDeleteExecute(e *CollectionEvent) error { | 
					
						
							|  |  |  | 	if e.Collection.System { | 
					
						
							|  |  |  | 		return fmt.Errorf("[%s] system collections cannot be deleted", e.Collection.Name) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	defer func() { | 
					
						
							|  |  |  | 		if err := e.App.ReloadCachedCollections(); err != nil { | 
					
						
							|  |  |  | 			e.App.Logger().Warn("Failed to reload collections cache", "error", err) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	}() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if !e.Collection.disableIntegrityChecks { | 
					
						
							|  |  |  | 		// ensure that there aren't any existing references.
 | 
					
						
							|  |  |  | 		// note: the select is outside of the transaction to prevent SQLITE_LOCKED error when mixing read&write in a single transaction
 | 
					
						
							|  |  |  | 		references, err := e.App.FindCollectionReferences(e.Collection, e.Collection.Id) | 
					
						
							|  |  |  | 		if err != nil { | 
					
						
							|  |  |  | 			return fmt.Errorf("[%s] failed to check collection references: %w", e.Collection.Name, err) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		if total := len(references); total > 0 { | 
					
						
							|  |  |  | 			names := make([]string, 0, len(references)) | 
					
						
							|  |  |  | 			for ref := range references { | 
					
						
							|  |  |  | 				names = append(names, ref.Name) | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			return fmt.Errorf("[%s] failed to delete due to existing relation references: %s", e.Collection.Name, strings.Join(names, ", ")) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	originalApp := e.App | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	txErr := e.App.RunInTransaction(func(txApp App) error { | 
					
						
							|  |  |  | 		e.App = txApp | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		// delete the related view or records table
 | 
					
						
							|  |  |  | 		if e.Collection.IsView() { | 
					
						
							|  |  |  | 			if err := txApp.DeleteView(e.Collection.Name); err != nil { | 
					
						
							|  |  |  | 				return err | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		} else { | 
					
						
							|  |  |  | 			if err := txApp.DeleteTable(e.Collection.Name); err != nil { | 
					
						
							|  |  |  | 				return err | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		if !e.Collection.disableIntegrityChecks { | 
					
						
							|  |  |  | 			// trigger views resave to check for dependencies
 | 
					
						
							|  |  |  | 			if err := resaveViewsWithChangedFields(txApp, e.Collection.Id); err != nil { | 
					
						
							|  |  |  | 				return fmt.Errorf("[%s] failed to delete due to existing view dependency: %w", e.Collection.Name, err) | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		// delete
 | 
					
						
							|  |  |  | 		return e.Next() | 
					
						
							|  |  |  | 	}) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	e.App = originalApp | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return txErr | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // save hook
 | 
					
						
							|  |  |  | // -------------------------------------------------------------------
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-11-04 16:51:32 +08:00
										 |  |  | func (c *Collection) idChecksum() string { | 
					
						
							|  |  |  | 	return "pbc_" + crc32Checksum(c.Type+c.Name) | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-09-30 00:23:19 +08:00
										 |  |  | func (c *Collection) initDefaultId() { | 
					
						
							| 
									
										
										
										
											2024-11-04 16:51:32 +08:00
										 |  |  | 	if c.Id == "" { | 
					
						
							|  |  |  | 		c.Id = c.idChecksum() | 
					
						
							|  |  |  | 		c.autogeneratedId = c.Id | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func (c *Collection) updateGeneratedIdIfExists(app App) { | 
					
						
							|  |  |  | 	if !c.IsNew() || | 
					
						
							|  |  |  | 		// the id was explicitly cleared
 | 
					
						
							|  |  |  | 		c.Id == "" || | 
					
						
							|  |  |  | 		// the id was manually set
 | 
					
						
							|  |  |  | 		c.Id != c.autogeneratedId { | 
					
						
							|  |  |  | 		return | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// generate an up-to-date checksum
 | 
					
						
							|  |  |  | 	newId := c.idChecksum() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// add a number to the current id (if already exists)
 | 
					
						
							|  |  |  | 	for i := 2; i < 1000; i++ { | 
					
						
							|  |  |  | 		var exists bool | 
					
						
							|  |  |  | 		_ = app.CollectionQuery().Select("(1)").AndWhere(dbx.HashExp{"id": newId}).Limit(1).Row(&exists) | 
					
						
							|  |  |  | 		if !exists { | 
					
						
							|  |  |  | 			break | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		newId = c.idChecksum() + strconv.Itoa(i) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// no change
 | 
					
						
							|  |  |  | 	if c.Id == newId { | 
					
						
							|  |  |  | 		return | 
					
						
							| 
									
										
										
										
											2024-11-02 17:20:21 +08:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-11-04 16:51:32 +08:00
										 |  |  | 	// replace the old id in the index names (if any)
 | 
					
						
							|  |  |  | 	for i, idx := range c.Indexes { | 
					
						
							|  |  |  | 		parsed := dbutils.ParseIndex(idx) | 
					
						
							|  |  |  | 		original := parsed.IndexName | 
					
						
							|  |  |  | 		parsed.IndexName = strings.ReplaceAll(parsed.IndexName, c.Id, newId) | 
					
						
							|  |  |  | 		if parsed.IndexName != original { | 
					
						
							|  |  |  | 			c.Indexes[i] = parsed.Build() | 
					
						
							|  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2024-09-30 00:23:19 +08:00
										 |  |  | 	} | 
					
						
							| 
									
										
										
										
											2024-11-04 16:51:32 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	// update model id
 | 
					
						
							|  |  |  | 	c.Id = newId | 
					
						
							| 
									
										
										
										
											2024-09-30 00:23:19 +08:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-11-04 16:51:32 +08:00
										 |  |  | func onCollectionSave(e *CollectionEvent) error { | 
					
						
							|  |  |  | 	if e.Collection.Type == "" { | 
					
						
							|  |  |  | 		e.Collection.Type = CollectionTypeBase | 
					
						
							| 
									
										
										
										
											2024-09-30 00:23:19 +08:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-11-04 16:51:32 +08:00
										 |  |  | 	if e.Collection.IsNew() { | 
					
						
							|  |  |  | 		e.Collection.initDefaultId() | 
					
						
							|  |  |  | 		e.Collection.Created = types.NowDateTime() | 
					
						
							| 
									
										
										
										
											2024-09-30 00:23:19 +08:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-11-04 16:51:32 +08:00
										 |  |  | 	e.Collection.Updated = types.NowDateTime() | 
					
						
							| 
									
										
										
										
											2024-09-30 00:23:19 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	// recreate the fields list to ensure that all normalizations
 | 
					
						
							|  |  |  | 	// like default field id are applied
 | 
					
						
							| 
									
										
										
										
											2024-11-04 16:51:32 +08:00
										 |  |  | 	e.Collection.Fields = NewFieldsList(e.Collection.Fields...) | 
					
						
							| 
									
										
										
										
											2024-09-30 00:23:19 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-11-04 16:51:32 +08:00
										 |  |  | 	e.Collection.initDefaultFields() | 
					
						
							| 
									
										
										
										
											2024-09-30 00:23:19 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-11-04 16:51:32 +08:00
										 |  |  | 	if e.Collection.IsAuth() { | 
					
						
							|  |  |  | 		e.Collection.unsetMissingOAuth2MappedFields() | 
					
						
							| 
									
										
										
										
											2024-09-30 00:23:19 +08:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-11-04 16:51:32 +08:00
										 |  |  | 	e.Collection.updateGeneratedIdIfExists(e.App) | 
					
						
							| 
									
										
										
										
											2024-09-30 00:23:19 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	return e.Next() | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func onCollectionSaveExecute(e *CollectionEvent) error { | 
					
						
							|  |  |  | 	defer func() { | 
					
						
							|  |  |  | 		if err := e.App.ReloadCachedCollections(); err != nil { | 
					
						
							|  |  |  | 			e.App.Logger().Warn("Failed to reload collections cache", "error", err) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	}() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	var oldCollection *Collection | 
					
						
							|  |  |  | 	if !e.Collection.IsNew() { | 
					
						
							|  |  |  | 		var err error | 
					
						
							|  |  |  | 		oldCollection, err = e.App.FindCachedCollectionByNameOrId(e.Collection.Id) | 
					
						
							|  |  |  | 		if err != nil { | 
					
						
							|  |  |  | 			return err | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		// invalidate previously issued auth tokens on auth rule change
 | 
					
						
							|  |  |  | 		if oldCollection.AuthRule != e.Collection.AuthRule && | 
					
						
							|  |  |  | 			cast.ToString(oldCollection.AuthRule) != cast.ToString(e.Collection.AuthRule) { | 
					
						
							|  |  |  | 			e.Collection.AuthToken.Secret = security.RandomString(50) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	originalApp := e.App | 
					
						
							|  |  |  | 	txErr := e.App.RunInTransaction(func(txApp App) error { | 
					
						
							|  |  |  | 		e.App = txApp | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		isView := e.Collection.IsView() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		// ensures that the view collection shema is properly loaded
 | 
					
						
							|  |  |  | 		if isView { | 
					
						
							|  |  |  | 			query := e.Collection.ViewQuery | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			// generate collection fields list from the query
 | 
					
						
							|  |  |  | 			viewFields, err := e.App.CreateViewFields(query) | 
					
						
							|  |  |  | 			if err != nil { | 
					
						
							|  |  |  | 				return err | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			// delete old renamed view
 | 
					
						
							|  |  |  | 			if oldCollection != nil { | 
					
						
							|  |  |  | 				if err := e.App.DeleteView(oldCollection.Name); err != nil { | 
					
						
							|  |  |  | 					return err | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			// wrap view query if necessary
 | 
					
						
							|  |  |  | 			query, err = normalizeViewQueryId(e.App, query) | 
					
						
							|  |  |  | 			if err != nil { | 
					
						
							|  |  |  | 				return fmt.Errorf("failed to normalize view query id: %w", err) | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			// (re)create the view
 | 
					
						
							|  |  |  | 			if err := e.App.SaveView(e.Collection.Name, query); err != nil { | 
					
						
							|  |  |  | 				return err | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			// updates newCollection.Fields based on the generated view table info and query
 | 
					
						
							|  |  |  | 			e.Collection.Fields = viewFields | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		// save the Collection model
 | 
					
						
							|  |  |  | 		if err := e.Next(); err != nil { | 
					
						
							|  |  |  | 			return err | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		// sync the changes with the related records table
 | 
					
						
							|  |  |  | 		if !isView { | 
					
						
							|  |  |  | 			if err := e.App.SyncRecordTableSchema(e.Collection, oldCollection); err != nil { | 
					
						
							|  |  |  | 				// note: don't wrap to allow propagating indexes validation.Errors
 | 
					
						
							|  |  |  | 				return err | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		return nil | 
					
						
							|  |  |  | 	}) | 
					
						
							|  |  |  | 	e.App = originalApp | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if txErr != nil { | 
					
						
							|  |  |  | 		return txErr | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// trigger an update for all views with changed fields as a result of the current collection save
 | 
					
						
							|  |  |  | 	// (ignoring view errors to allow users to update the query from the UI)
 | 
					
						
							|  |  |  | 	resaveViewsWithChangedFields(e.App, e.Collection.Id) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return nil | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-11-02 17:20:21 +08:00
										 |  |  | func (c *Collection) initDefaultFields() { | 
					
						
							|  |  |  | 	switch c.Type { | 
					
						
							| 
									
										
										
										
											2024-09-30 00:23:19 +08:00
										 |  |  | 	case CollectionTypeBase: | 
					
						
							| 
									
										
										
										
											2024-11-02 17:20:21 +08:00
										 |  |  | 		c.initIdField() | 
					
						
							| 
									
										
										
										
											2024-09-30 00:23:19 +08:00
										 |  |  | 	case CollectionTypeAuth: | 
					
						
							| 
									
										
										
										
											2024-11-02 17:20:21 +08:00
										 |  |  | 		c.initIdField() | 
					
						
							|  |  |  | 		c.initPasswordField() | 
					
						
							|  |  |  | 		c.initTokenKeyField() | 
					
						
							|  |  |  | 		c.initEmailField() | 
					
						
							|  |  |  | 		c.initEmailVisibilityField() | 
					
						
							|  |  |  | 		c.initVerifiedField() | 
					
						
							| 
									
										
										
										
											2024-09-30 00:23:19 +08:00
										 |  |  | 	case CollectionTypeView: | 
					
						
							|  |  |  | 		// view fields are autogenerated
 | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-11-02 17:20:21 +08:00
										 |  |  | func (c *Collection) initIdField() { | 
					
						
							|  |  |  | 	field, _ := c.Fields.GetByName(FieldNameId).(*TextField) | 
					
						
							| 
									
										
										
										
											2024-09-30 00:23:19 +08:00
										 |  |  | 	if field == nil { | 
					
						
							|  |  |  | 		// create default field
 | 
					
						
							|  |  |  | 		field = &TextField{ | 
					
						
							|  |  |  | 			Name:                FieldNameId, | 
					
						
							|  |  |  | 			System:              true, | 
					
						
							|  |  |  | 			PrimaryKey:          true, | 
					
						
							|  |  |  | 			Required:            true, | 
					
						
							|  |  |  | 			Min:                 15, | 
					
						
							|  |  |  | 			Max:                 15, | 
					
						
							|  |  |  | 			Pattern:             `^[a-z0-9]+$`, | 
					
						
							|  |  |  | 			AutogeneratePattern: `[a-z0-9]{15}`, | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		// prepend it
 | 
					
						
							| 
									
										
										
										
											2024-11-02 17:20:21 +08:00
										 |  |  | 		c.Fields = NewFieldsList(append([]Field{field}, c.Fields...)...) | 
					
						
							| 
									
										
										
										
											2024-09-30 00:23:19 +08:00
										 |  |  | 	} else { | 
					
						
							|  |  |  | 		// enforce system defaults
 | 
					
						
							|  |  |  | 		field.System = true | 
					
						
							|  |  |  | 		field.Required = true | 
					
						
							|  |  |  | 		field.PrimaryKey = true | 
					
						
							|  |  |  | 		field.Hidden = false | 
					
						
							| 
									
										
										
										
											2024-11-19 23:21:25 +08:00
										 |  |  | 		if field.Pattern == "" { | 
					
						
							|  |  |  | 			field.Pattern = `^[a-z0-9]+$` | 
					
						
							|  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2024-09-30 00:23:19 +08:00
										 |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-11-02 17:20:21 +08:00
										 |  |  | func (c *Collection) initPasswordField() { | 
					
						
							|  |  |  | 	field, _ := c.Fields.GetByName(FieldNamePassword).(*PasswordField) | 
					
						
							| 
									
										
										
										
											2024-09-30 00:23:19 +08:00
										 |  |  | 	if field == nil { | 
					
						
							|  |  |  | 		// load default field
 | 
					
						
							| 
									
										
										
										
											2024-11-02 17:20:21 +08:00
										 |  |  | 		c.Fields.Add(&PasswordField{ | 
					
						
							| 
									
										
										
										
											2024-09-30 00:23:19 +08:00
										 |  |  | 			Name:     FieldNamePassword, | 
					
						
							|  |  |  | 			System:   true, | 
					
						
							|  |  |  | 			Hidden:   true, | 
					
						
							|  |  |  | 			Required: true, | 
					
						
							|  |  |  | 			Min:      8, | 
					
						
							|  |  |  | 		}) | 
					
						
							|  |  |  | 	} else { | 
					
						
							|  |  |  | 		// enforce system defaults
 | 
					
						
							|  |  |  | 		field.System = true | 
					
						
							|  |  |  | 		field.Hidden = true | 
					
						
							|  |  |  | 		field.Required = true | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-11-02 17:20:21 +08:00
										 |  |  | func (c *Collection) initTokenKeyField() { | 
					
						
							|  |  |  | 	field, _ := c.Fields.GetByName(FieldNameTokenKey).(*TextField) | 
					
						
							| 
									
										
										
										
											2024-09-30 00:23:19 +08:00
										 |  |  | 	if field == nil { | 
					
						
							|  |  |  | 		// load default field
 | 
					
						
							| 
									
										
										
										
											2024-11-02 17:20:21 +08:00
										 |  |  | 		c.Fields.Add(&TextField{ | 
					
						
							| 
									
										
										
										
											2024-09-30 00:23:19 +08:00
										 |  |  | 			Name:                FieldNameTokenKey, | 
					
						
							|  |  |  | 			System:              true, | 
					
						
							|  |  |  | 			Hidden:              true, | 
					
						
							|  |  |  | 			Min:                 30, | 
					
						
							|  |  |  | 			Max:                 60, | 
					
						
							|  |  |  | 			Required:            true, | 
					
						
							|  |  |  | 			AutogeneratePattern: `[a-zA-Z0-9]{50}`, | 
					
						
							|  |  |  | 		}) | 
					
						
							|  |  |  | 	} else { | 
					
						
							|  |  |  | 		// enforce system defaults
 | 
					
						
							|  |  |  | 		field.System = true | 
					
						
							|  |  |  | 		field.Hidden = true | 
					
						
							|  |  |  | 		field.Required = true | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// ensure that there is a unique index for the field
 | 
					
						
							| 
									
										
										
										
											2024-11-02 17:20:21 +08:00
										 |  |  | 	if !dbutils.HasSingleColumnUniqueIndex(FieldNameTokenKey, c.Indexes) { | 
					
						
							|  |  |  | 		c.Indexes = append(c.Indexes, fmt.Sprintf( | 
					
						
							| 
									
										
										
										
											2024-09-30 00:23:19 +08:00
										 |  |  | 			"CREATE UNIQUE INDEX `%s` ON `%s` (`%s`)", | 
					
						
							| 
									
										
										
										
											2024-11-02 17:20:21 +08:00
										 |  |  | 			c.fieldIndexName(FieldNameTokenKey), | 
					
						
							|  |  |  | 			c.Name, | 
					
						
							| 
									
										
										
										
											2024-09-30 00:23:19 +08:00
										 |  |  | 			FieldNameTokenKey, | 
					
						
							|  |  |  | 		)) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-11-02 17:20:21 +08:00
										 |  |  | func (c *Collection) initEmailField() { | 
					
						
							|  |  |  | 	field, _ := c.Fields.GetByName(FieldNameEmail).(*EmailField) | 
					
						
							| 
									
										
										
										
											2024-09-30 00:23:19 +08:00
										 |  |  | 	if field == nil { | 
					
						
							|  |  |  | 		// load default field
 | 
					
						
							| 
									
										
										
										
											2024-11-02 17:20:21 +08:00
										 |  |  | 		c.Fields.Add(&EmailField{ | 
					
						
							| 
									
										
										
										
											2024-09-30 00:23:19 +08:00
										 |  |  | 			Name:     FieldNameEmail, | 
					
						
							|  |  |  | 			System:   true, | 
					
						
							|  |  |  | 			Required: true, | 
					
						
							|  |  |  | 		}) | 
					
						
							|  |  |  | 	} else { | 
					
						
							|  |  |  | 		// enforce system defaults
 | 
					
						
							|  |  |  | 		field.System = true | 
					
						
							|  |  |  | 		field.Hidden = false // managed by the emailVisibility flag
 | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// ensure that there is a unique index for the email field
 | 
					
						
							| 
									
										
										
										
											2024-11-02 17:20:21 +08:00
										 |  |  | 	if !dbutils.HasSingleColumnUniqueIndex(FieldNameEmail, c.Indexes) { | 
					
						
							|  |  |  | 		c.Indexes = append(c.Indexes, fmt.Sprintf( | 
					
						
							| 
									
										
										
										
											2024-09-30 00:23:19 +08:00
										 |  |  | 			"CREATE UNIQUE INDEX `%s` ON `%s` (`%s`) WHERE `%s` != ''", | 
					
						
							| 
									
										
										
										
											2024-11-02 17:20:21 +08:00
										 |  |  | 			c.fieldIndexName(FieldNameEmail), | 
					
						
							|  |  |  | 			c.Name, | 
					
						
							| 
									
										
										
										
											2024-09-30 00:23:19 +08:00
										 |  |  | 			FieldNameEmail, | 
					
						
							|  |  |  | 			FieldNameEmail, | 
					
						
							|  |  |  | 		)) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-11-02 17:20:21 +08:00
										 |  |  | func (c *Collection) initEmailVisibilityField() { | 
					
						
							|  |  |  | 	field, _ := c.Fields.GetByName(FieldNameEmailVisibility).(*BoolField) | 
					
						
							| 
									
										
										
										
											2024-09-30 00:23:19 +08:00
										 |  |  | 	if field == nil { | 
					
						
							|  |  |  | 		// load default field
 | 
					
						
							| 
									
										
										
										
											2024-11-02 17:20:21 +08:00
										 |  |  | 		c.Fields.Add(&BoolField{ | 
					
						
							| 
									
										
										
										
											2024-09-30 00:23:19 +08:00
										 |  |  | 			Name:   FieldNameEmailVisibility, | 
					
						
							|  |  |  | 			System: true, | 
					
						
							|  |  |  | 		}) | 
					
						
							|  |  |  | 	} else { | 
					
						
							|  |  |  | 		// enforce system defaults
 | 
					
						
							|  |  |  | 		field.System = true | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-11-02 17:20:21 +08:00
										 |  |  | func (c *Collection) initVerifiedField() { | 
					
						
							|  |  |  | 	field, _ := c.Fields.GetByName(FieldNameVerified).(*BoolField) | 
					
						
							| 
									
										
										
										
											2024-09-30 00:23:19 +08:00
										 |  |  | 	if field == nil { | 
					
						
							|  |  |  | 		// load default field
 | 
					
						
							| 
									
										
										
										
											2024-11-02 17:20:21 +08:00
										 |  |  | 		c.Fields.Add(&BoolField{ | 
					
						
							| 
									
										
										
										
											2024-09-30 00:23:19 +08:00
										 |  |  | 			Name:   FieldNameVerified, | 
					
						
							|  |  |  | 			System: true, | 
					
						
							|  |  |  | 		}) | 
					
						
							|  |  |  | 	} else { | 
					
						
							|  |  |  | 		// enforce system defaults
 | 
					
						
							|  |  |  | 		field.System = true | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-11-02 17:20:21 +08:00
										 |  |  | func (c *Collection) fieldIndexName(field string) string { | 
					
						
							| 
									
										
										
										
											2024-09-30 00:23:19 +08:00
										 |  |  | 	name := "idx_" + field + "_" | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-11-02 17:20:21 +08:00
										 |  |  | 	if c.Id != "" { | 
					
						
							|  |  |  | 		name += c.Id | 
					
						
							|  |  |  | 	} else if c.Name != "" { | 
					
						
							|  |  |  | 		name += c.Name | 
					
						
							| 
									
										
										
										
											2024-09-30 00:23:19 +08:00
										 |  |  | 	} else { | 
					
						
							|  |  |  | 		name += security.PseudorandomString(10) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if len(name) > 64 { | 
					
						
							|  |  |  | 		return name[:64] | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return name | 
					
						
							|  |  |  | } |