mirror of
https://github.com/pocketbase/pocketbase.git
synced 2025-02-13 16:31:59 +02:00
before updateding test data
This commit is contained in:
parent
a426484916
commit
6e9d000426
@ -169,19 +169,37 @@ func (api *collectionApi) delete(c echo.Context) error {
|
|||||||
return handlerErr
|
return handlerErr
|
||||||
}
|
}
|
||||||
|
|
||||||
// @todo add event
|
|
||||||
func (api *collectionApi) bulkImport(c echo.Context) error {
|
func (api *collectionApi) bulkImport(c echo.Context) error {
|
||||||
form := forms.NewCollectionsImport(api.app)
|
form := forms.NewCollectionsImport(api.app)
|
||||||
|
|
||||||
// load request
|
// load request data
|
||||||
if err := c.Bind(form); err != nil {
|
if err := c.Bind(form); err != nil {
|
||||||
return rest.NewBadRequestError("Failed to load the submitted data due to invalid formatting.", err)
|
return rest.NewBadRequestError("Failed to load the submitted data due to invalid formatting.", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
submitErr := form.Submit()
|
event := &core.CollectionsImportEvent{
|
||||||
if submitErr != nil {
|
HttpContext: c,
|
||||||
return rest.NewBadRequestError("Failed to import the submitted collections.", submitErr)
|
Collections: form.Collections,
|
||||||
}
|
}
|
||||||
|
|
||||||
return c.NoContent(http.StatusNoContent)
|
// import collections
|
||||||
|
submitErr := form.Submit(func(next forms.InterceptorNextFunc) forms.InterceptorNextFunc {
|
||||||
|
return func() error {
|
||||||
|
return api.app.OnCollectionsBeforeImportRequest().Trigger(event, func(e *core.CollectionsImportEvent) error {
|
||||||
|
form.Collections = e.Collections // ensures that the form always has the latest changes
|
||||||
|
|
||||||
|
if err := next(); err != nil {
|
||||||
|
return rest.NewBadRequestError("Failed to import the submitted collections.", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return e.HttpContext.NoContent(http.StatusNoContent)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
if submitErr == nil {
|
||||||
|
api.app.OnCollectionsAfterImportRequest().Trigger(event)
|
||||||
|
}
|
||||||
|
|
||||||
|
return submitErr
|
||||||
}
|
}
|
||||||
|
@ -440,3 +440,57 @@ func TestCollectionUpdate(t *testing.T) {
|
|||||||
scenario.Test(t)
|
scenario.Test(t)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestCollectionImport(t *testing.T) {
|
||||||
|
scenarios := []tests.ApiScenario{
|
||||||
|
{
|
||||||
|
Name: "unauthorized",
|
||||||
|
Method: http.MethodPut,
|
||||||
|
Url: "/api/collections/import",
|
||||||
|
ExpectedStatus: 401,
|
||||||
|
ExpectedContent: []string{`"data":{}`},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "authorized as user",
|
||||||
|
Method: http.MethodPut,
|
||||||
|
Url: "/api/collections/import",
|
||||||
|
RequestHeaders: map[string]string{
|
||||||
|
"Authorization": "User eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjRkMDE5N2NjLTJiNGEtM2Y4My1hMjZiLWQ3N2JjODQyM2QzYyIsInR5cGUiOiJ1c2VyIiwiZXhwIjoxODkzNDc0MDAwfQ.Wq5ac1q1f5WntIzEngXk22ydMj-eFgvfSRg7dhmPKic",
|
||||||
|
},
|
||||||
|
ExpectedStatus: 401,
|
||||||
|
ExpectedContent: []string{`"data":{}`},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "authorized as admin + empty collections",
|
||||||
|
Method: http.MethodPut,
|
||||||
|
Url: "/api/collections/import",
|
||||||
|
Body: strings.NewReader(`{"collections":[]}`),
|
||||||
|
RequestHeaders: map[string]string{
|
||||||
|
"Authorization": "Admin eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjJiNGE5N2NjLTNmODMtNGQwMS1hMjZiLTNkNzdiYzg0MmQzYyIsInR5cGUiOiJhZG1pbiIsImV4cCI6MTg3MzQ2Mjc5Mn0.AtRtXR6FHBrCUGkj5OffhmxLbSZaQ4L_Qgw4gfoHyfo",
|
||||||
|
},
|
||||||
|
ExpectedStatus: 400,
|
||||||
|
ExpectedContent: []string{
|
||||||
|
`"data":{`,
|
||||||
|
`"collections":{"code":"validation_required"`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "authorized as admin + deleting collections",
|
||||||
|
Method: http.MethodPut,
|
||||||
|
Url: "/api/collections/import",
|
||||||
|
Body: strings.NewReader(`{"collections":[]}`),
|
||||||
|
RequestHeaders: map[string]string{
|
||||||
|
"Authorization": "Admin eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjJiNGE5N2NjLTNmODMtNGQwMS1hMjZiLTNkNzdiYzg0MmQzYyIsInR5cGUiOiJhZG1pbiIsImV4cCI6MTg3MzQ2Mjc5Mn0.AtRtXR6FHBrCUGkj5OffhmxLbSZaQ4L_Qgw4gfoHyfo",
|
||||||
|
},
|
||||||
|
ExpectedStatus: 400,
|
||||||
|
ExpectedContent: []string{
|
||||||
|
`"data":{`,
|
||||||
|
`"collections":{"code":"validation_required"`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, scenario := range scenarios {
|
||||||
|
scenario.Test(t)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
45
core/app.go
45
core/app.go
@ -119,7 +119,7 @@ type App interface {
|
|||||||
// sending a password reset email to an admin.
|
// sending a password reset email to an admin.
|
||||||
//
|
//
|
||||||
// Could be used to send your own custom email template if
|
// Could be used to send your own custom email template if
|
||||||
// hook.StopPropagation is returned in one of its listeners.
|
// [hook.StopPropagation] is returned in one of its listeners.
|
||||||
OnMailerBeforeAdminResetPasswordSend() *hook.Hook[*MailerAdminEvent]
|
OnMailerBeforeAdminResetPasswordSend() *hook.Hook[*MailerAdminEvent]
|
||||||
|
|
||||||
// OnMailerAfterAdminResetPasswordSend hook is triggered after
|
// OnMailerAfterAdminResetPasswordSend hook is triggered after
|
||||||
@ -130,7 +130,7 @@ type App interface {
|
|||||||
// sending a password reset email to a user.
|
// sending a password reset email to a user.
|
||||||
//
|
//
|
||||||
// Could be used to send your own custom email template if
|
// Could be used to send your own custom email template if
|
||||||
// hook.StopPropagation is returned in one of its listeners.
|
// [hook.StopPropagation] is returned in one of its listeners.
|
||||||
OnMailerBeforeUserResetPasswordSend() *hook.Hook[*MailerUserEvent]
|
OnMailerBeforeUserResetPasswordSend() *hook.Hook[*MailerUserEvent]
|
||||||
|
|
||||||
// OnMailerAfterUserResetPasswordSend hook is triggered after
|
// OnMailerAfterUserResetPasswordSend hook is triggered after
|
||||||
@ -141,7 +141,7 @@ type App interface {
|
|||||||
// sending a verification email to a user.
|
// sending a verification email to a user.
|
||||||
//
|
//
|
||||||
// Could be used to send your own custom email template if
|
// Could be used to send your own custom email template if
|
||||||
// hook.StopPropagation is returned in one of its listeners.
|
// [hook.StopPropagation] is returned in one of its listeners.
|
||||||
OnMailerBeforeUserVerificationSend() *hook.Hook[*MailerUserEvent]
|
OnMailerBeforeUserVerificationSend() *hook.Hook[*MailerUserEvent]
|
||||||
|
|
||||||
// OnMailerAfterUserVerificationSend hook is triggered after a user
|
// OnMailerAfterUserVerificationSend hook is triggered after a user
|
||||||
@ -152,7 +152,7 @@ type App interface {
|
|||||||
// sending a confirmation new address email to a a user.
|
// sending a confirmation new address email to a a user.
|
||||||
//
|
//
|
||||||
// Could be used to send your own custom email template if
|
// Could be used to send your own custom email template if
|
||||||
// hook.StopPropagation is returned in one of its listeners.
|
// [hook.StopPropagation] is returned in one of its listeners.
|
||||||
OnMailerBeforeUserChangeEmailSend() *hook.Hook[*MailerUserEvent]
|
OnMailerBeforeUserChangeEmailSend() *hook.Hook[*MailerUserEvent]
|
||||||
|
|
||||||
// OnMailerAfterUserChangeEmailSend hook is triggered after a user
|
// OnMailerAfterUserChangeEmailSend hook is triggered after a user
|
||||||
@ -192,7 +192,7 @@ type App interface {
|
|||||||
//
|
//
|
||||||
// Could be used to additionally validate the request data or
|
// Could be used to additionally validate the request data or
|
||||||
// implement completely different persistence behavior
|
// implement completely different persistence behavior
|
||||||
// (returning hook.StopPropagation).
|
// (returning [hook.StopPropagation]).
|
||||||
OnSettingsBeforeUpdateRequest() *hook.Hook[*SettingsUpdateEvent]
|
OnSettingsBeforeUpdateRequest() *hook.Hook[*SettingsUpdateEvent]
|
||||||
|
|
||||||
// OnSettingsAfterUpdateRequest hook is triggered after each
|
// OnSettingsAfterUpdateRequest hook is triggered after each
|
||||||
@ -227,7 +227,7 @@ type App interface {
|
|||||||
// Admin create request (after request data load and before model persistence).
|
// Admin create request (after request data load and before model persistence).
|
||||||
//
|
//
|
||||||
// Could be used to additionally validate the request data or implement
|
// Could be used to additionally validate the request data or implement
|
||||||
// completely different persistence behavior (returning hook.StopPropagation).
|
// completely different persistence behavior (returning [hook.StopPropagation]).
|
||||||
OnAdminBeforeCreateRequest() *hook.Hook[*AdminCreateEvent]
|
OnAdminBeforeCreateRequest() *hook.Hook[*AdminCreateEvent]
|
||||||
|
|
||||||
// OnAdminAfterCreateRequest hook is triggered after each
|
// OnAdminAfterCreateRequest hook is triggered after each
|
||||||
@ -238,7 +238,7 @@ type App interface {
|
|||||||
// Admin update request (after request data load and before model persistence).
|
// Admin update request (after request data load and before model persistence).
|
||||||
//
|
//
|
||||||
// Could be used to additionally validate the request data or implement
|
// Could be used to additionally validate the request data or implement
|
||||||
// completely different persistence behavior (returning hook.StopPropagation).
|
// completely different persistence behavior (returning [hook.StopPropagation]).
|
||||||
OnAdminBeforeUpdateRequest() *hook.Hook[*AdminUpdateEvent]
|
OnAdminBeforeUpdateRequest() *hook.Hook[*AdminUpdateEvent]
|
||||||
|
|
||||||
// OnAdminAfterUpdateRequest hook is triggered after each
|
// OnAdminAfterUpdateRequest hook is triggered after each
|
||||||
@ -249,7 +249,7 @@ type App interface {
|
|||||||
// Admin delete request (after model load and before actual deletion).
|
// Admin delete request (after model load and before actual deletion).
|
||||||
//
|
//
|
||||||
// Could be used to additionally validate the request data or implement
|
// Could be used to additionally validate the request data or implement
|
||||||
// completely different delete behavior (returning hook.StopPropagation).
|
// completely different delete behavior (returning [hook.StopPropagation]).
|
||||||
OnAdminBeforeDeleteRequest() *hook.Hook[*AdminDeleteEvent]
|
OnAdminBeforeDeleteRequest() *hook.Hook[*AdminDeleteEvent]
|
||||||
|
|
||||||
// OnAdminAfterDeleteRequest hook is triggered after each
|
// OnAdminAfterDeleteRequest hook is triggered after each
|
||||||
@ -281,7 +281,7 @@ type App interface {
|
|||||||
// create request (after request data load and before model persistence).
|
// create request (after request data load and before model persistence).
|
||||||
//
|
//
|
||||||
// Could be used to additionally validate the request data or implement
|
// Could be used to additionally validate the request data or implement
|
||||||
// completely different persistence behavior (returning hook.StopPropagation).
|
// completely different persistence behavior (returning [hook.StopPropagation]).
|
||||||
OnUserBeforeCreateRequest() *hook.Hook[*UserCreateEvent]
|
OnUserBeforeCreateRequest() *hook.Hook[*UserCreateEvent]
|
||||||
|
|
||||||
// OnUserAfterCreateRequest hook is triggered after each
|
// OnUserAfterCreateRequest hook is triggered after each
|
||||||
@ -292,7 +292,7 @@ type App interface {
|
|||||||
// update request (after request data load and before model persistence).
|
// update request (after request data load and before model persistence).
|
||||||
//
|
//
|
||||||
// Could be used to additionally validate the request data or implement
|
// Could be used to additionally validate the request data or implement
|
||||||
// completely different persistence behavior (returning hook.StopPropagation).
|
// completely different persistence behavior (returning [hook.StopPropagation]).
|
||||||
OnUserBeforeUpdateRequest() *hook.Hook[*UserUpdateEvent]
|
OnUserBeforeUpdateRequest() *hook.Hook[*UserUpdateEvent]
|
||||||
|
|
||||||
// OnUserAfterUpdateRequest hook is triggered after each
|
// OnUserAfterUpdateRequest hook is triggered after each
|
||||||
@ -303,7 +303,7 @@ type App interface {
|
|||||||
// delete request (after model load and before actual deletion).
|
// delete request (after model load and before actual deletion).
|
||||||
//
|
//
|
||||||
// Could be used to additionally validate the request data or implement
|
// Could be used to additionally validate the request data or implement
|
||||||
// completely different delete behavior (returning hook.StopPropagation).
|
// completely different delete behavior (returning [hook.StopPropagation]).
|
||||||
OnUserBeforeDeleteRequest() *hook.Hook[*UserDeleteEvent]
|
OnUserBeforeDeleteRequest() *hook.Hook[*UserDeleteEvent]
|
||||||
|
|
||||||
// OnUserAfterDeleteRequest hook is triggered after each
|
// OnUserAfterDeleteRequest hook is triggered after each
|
||||||
@ -346,7 +346,7 @@ type App interface {
|
|||||||
// create request (after request data load and before model persistence).
|
// create request (after request data load and before model persistence).
|
||||||
//
|
//
|
||||||
// Could be used to additionally validate the request data or implement
|
// Could be used to additionally validate the request data or implement
|
||||||
// completely different persistence behavior (returning hook.StopPropagation).
|
// completely different persistence behavior (returning [hook.StopPropagation]).
|
||||||
OnRecordBeforeCreateRequest() *hook.Hook[*RecordCreateEvent]
|
OnRecordBeforeCreateRequest() *hook.Hook[*RecordCreateEvent]
|
||||||
|
|
||||||
// OnRecordAfterCreateRequest hook is triggered after each
|
// OnRecordAfterCreateRequest hook is triggered after each
|
||||||
@ -357,7 +357,7 @@ type App interface {
|
|||||||
// update request (after request data load and before model persistence).
|
// update request (after request data load and before model persistence).
|
||||||
//
|
//
|
||||||
// Could be used to additionally validate the request data or implement
|
// Could be used to additionally validate the request data or implement
|
||||||
// completely different persistence behavior (returning hook.StopPropagation).
|
// completely different persistence behavior (returning [hook.StopPropagation]).
|
||||||
OnRecordBeforeUpdateRequest() *hook.Hook[*RecordUpdateEvent]
|
OnRecordBeforeUpdateRequest() *hook.Hook[*RecordUpdateEvent]
|
||||||
|
|
||||||
// OnRecordAfterUpdateRequest hook is triggered after each
|
// OnRecordAfterUpdateRequest hook is triggered after each
|
||||||
@ -368,7 +368,7 @@ type App interface {
|
|||||||
// delete request (after model load and before actual deletion).
|
// delete request (after model load and before actual deletion).
|
||||||
//
|
//
|
||||||
// Could be used to additionally validate the request data or implement
|
// Could be used to additionally validate the request data or implement
|
||||||
// completely different delete behavior (returning hook.StopPropagation).
|
// completely different delete behavior (returning [hook.StopPropagation]).
|
||||||
OnRecordBeforeDeleteRequest() *hook.Hook[*RecordDeleteEvent]
|
OnRecordBeforeDeleteRequest() *hook.Hook[*RecordDeleteEvent]
|
||||||
|
|
||||||
// OnRecordAfterDeleteRequest hook is triggered after each
|
// OnRecordAfterDeleteRequest hook is triggered after each
|
||||||
@ -393,7 +393,7 @@ type App interface {
|
|||||||
// create request (after request data load and before model persistence).
|
// create request (after request data load and before model persistence).
|
||||||
//
|
//
|
||||||
// Could be used to additionally validate the request data or implement
|
// Could be used to additionally validate the request data or implement
|
||||||
// completely different persistence behavior (returning hook.StopPropagation).
|
// completely different persistence behavior (returning [hook.StopPropagation]).
|
||||||
OnCollectionBeforeCreateRequest() *hook.Hook[*CollectionCreateEvent]
|
OnCollectionBeforeCreateRequest() *hook.Hook[*CollectionCreateEvent]
|
||||||
|
|
||||||
// OnCollectionAfterCreateRequest hook is triggered after each
|
// OnCollectionAfterCreateRequest hook is triggered after each
|
||||||
@ -404,7 +404,7 @@ type App interface {
|
|||||||
// update request (after request data load and before model persistence).
|
// update request (after request data load and before model persistence).
|
||||||
//
|
//
|
||||||
// Could be used to additionally validate the request data or implement
|
// Could be used to additionally validate the request data or implement
|
||||||
// completely different persistence behavior (returning hook.StopPropagation).
|
// completely different persistence behavior (returning [hook.StopPropagation]).
|
||||||
OnCollectionBeforeUpdateRequest() *hook.Hook[*CollectionUpdateEvent]
|
OnCollectionBeforeUpdateRequest() *hook.Hook[*CollectionUpdateEvent]
|
||||||
|
|
||||||
// OnCollectionAfterUpdateRequest hook is triggered after each
|
// OnCollectionAfterUpdateRequest hook is triggered after each
|
||||||
@ -415,10 +415,21 @@ type App interface {
|
|||||||
// Collection delete request (after model load and before actual deletion).
|
// Collection delete request (after model load and before actual deletion).
|
||||||
//
|
//
|
||||||
// Could be used to additionally validate the request data or implement
|
// Could be used to additionally validate the request data or implement
|
||||||
// completely different delete behavior (returning hook.StopPropagation).
|
// completely different delete behavior (returning [hook.StopPropagation]).
|
||||||
OnCollectionBeforeDeleteRequest() *hook.Hook[*CollectionDeleteEvent]
|
OnCollectionBeforeDeleteRequest() *hook.Hook[*CollectionDeleteEvent]
|
||||||
|
|
||||||
// OnCollectionAfterDeleteRequest hook is triggered after each
|
// OnCollectionAfterDeleteRequest hook is triggered after each
|
||||||
// successful API Collection delete request.
|
// successful API Collection delete request.
|
||||||
OnCollectionAfterDeleteRequest() *hook.Hook[*CollectionDeleteEvent]
|
OnCollectionAfterDeleteRequest() *hook.Hook[*CollectionDeleteEvent]
|
||||||
|
|
||||||
|
// OnCollectionsBeforeImportRequest hook is triggered before each API
|
||||||
|
// collections import request (after request data load and before the actual import).
|
||||||
|
//
|
||||||
|
// Could be used to additionally validate the imported collections or
|
||||||
|
// to implement completely different import behavior (returning [hook.StopPropagation]).
|
||||||
|
OnCollectionsBeforeImportRequest() *hook.Hook[*CollectionsImportEvent]
|
||||||
|
|
||||||
|
// OnCollectionsAfterImportRequest hook is triggered after each
|
||||||
|
// successful API collections import request.
|
||||||
|
OnCollectionsAfterImportRequest() *hook.Hook[*CollectionsImportEvent]
|
||||||
}
|
}
|
||||||
|
44
core/base.go
44
core/base.go
@ -109,14 +109,16 @@ type BaseApp struct {
|
|||||||
onRecordAfterDeleteRequest *hook.Hook[*RecordDeleteEvent]
|
onRecordAfterDeleteRequest *hook.Hook[*RecordDeleteEvent]
|
||||||
|
|
||||||
// collection api event hooks
|
// collection api event hooks
|
||||||
onCollectionsListRequest *hook.Hook[*CollectionsListEvent]
|
onCollectionsListRequest *hook.Hook[*CollectionsListEvent]
|
||||||
onCollectionViewRequest *hook.Hook[*CollectionViewEvent]
|
onCollectionViewRequest *hook.Hook[*CollectionViewEvent]
|
||||||
onCollectionBeforeCreateRequest *hook.Hook[*CollectionCreateEvent]
|
onCollectionBeforeCreateRequest *hook.Hook[*CollectionCreateEvent]
|
||||||
onCollectionAfterCreateRequest *hook.Hook[*CollectionCreateEvent]
|
onCollectionAfterCreateRequest *hook.Hook[*CollectionCreateEvent]
|
||||||
onCollectionBeforeUpdateRequest *hook.Hook[*CollectionUpdateEvent]
|
onCollectionBeforeUpdateRequest *hook.Hook[*CollectionUpdateEvent]
|
||||||
onCollectionAfterUpdateRequest *hook.Hook[*CollectionUpdateEvent]
|
onCollectionAfterUpdateRequest *hook.Hook[*CollectionUpdateEvent]
|
||||||
onCollectionBeforeDeleteRequest *hook.Hook[*CollectionDeleteEvent]
|
onCollectionBeforeDeleteRequest *hook.Hook[*CollectionDeleteEvent]
|
||||||
onCollectionAfterDeleteRequest *hook.Hook[*CollectionDeleteEvent]
|
onCollectionAfterDeleteRequest *hook.Hook[*CollectionDeleteEvent]
|
||||||
|
onCollectionsBeforeImportRequest *hook.Hook[*CollectionsImportEvent]
|
||||||
|
onCollectionsAfterImportRequest *hook.Hook[*CollectionsImportEvent]
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewBaseApp creates and returns a new BaseApp instance
|
// NewBaseApp creates and returns a new BaseApp instance
|
||||||
@ -201,14 +203,16 @@ func NewBaseApp(dataDir string, encryptionEnv string, isDebug bool) *BaseApp {
|
|||||||
onRecordAfterDeleteRequest: &hook.Hook[*RecordDeleteEvent]{},
|
onRecordAfterDeleteRequest: &hook.Hook[*RecordDeleteEvent]{},
|
||||||
|
|
||||||
// collection API event hooks
|
// collection API event hooks
|
||||||
onCollectionsListRequest: &hook.Hook[*CollectionsListEvent]{},
|
onCollectionsListRequest: &hook.Hook[*CollectionsListEvent]{},
|
||||||
onCollectionViewRequest: &hook.Hook[*CollectionViewEvent]{},
|
onCollectionViewRequest: &hook.Hook[*CollectionViewEvent]{},
|
||||||
onCollectionBeforeCreateRequest: &hook.Hook[*CollectionCreateEvent]{},
|
onCollectionBeforeCreateRequest: &hook.Hook[*CollectionCreateEvent]{},
|
||||||
onCollectionAfterCreateRequest: &hook.Hook[*CollectionCreateEvent]{},
|
onCollectionAfterCreateRequest: &hook.Hook[*CollectionCreateEvent]{},
|
||||||
onCollectionBeforeUpdateRequest: &hook.Hook[*CollectionUpdateEvent]{},
|
onCollectionBeforeUpdateRequest: &hook.Hook[*CollectionUpdateEvent]{},
|
||||||
onCollectionAfterUpdateRequest: &hook.Hook[*CollectionUpdateEvent]{},
|
onCollectionAfterUpdateRequest: &hook.Hook[*CollectionUpdateEvent]{},
|
||||||
onCollectionBeforeDeleteRequest: &hook.Hook[*CollectionDeleteEvent]{},
|
onCollectionBeforeDeleteRequest: &hook.Hook[*CollectionDeleteEvent]{},
|
||||||
onCollectionAfterDeleteRequest: &hook.Hook[*CollectionDeleteEvent]{},
|
onCollectionAfterDeleteRequest: &hook.Hook[*CollectionDeleteEvent]{},
|
||||||
|
onCollectionsBeforeImportRequest: &hook.Hook[*CollectionsImportEvent]{},
|
||||||
|
onCollectionsAfterImportRequest: &hook.Hook[*CollectionsImportEvent]{},
|
||||||
}
|
}
|
||||||
|
|
||||||
app.registerDefaultHooks()
|
app.registerDefaultHooks()
|
||||||
@ -687,6 +691,14 @@ func (app *BaseApp) OnCollectionAfterDeleteRequest() *hook.Hook[*CollectionDelet
|
|||||||
return app.onCollectionAfterDeleteRequest
|
return app.onCollectionAfterDeleteRequest
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (app *BaseApp) OnCollectionsBeforeImportRequest() *hook.Hook[*CollectionsImportEvent] {
|
||||||
|
return app.onCollectionsBeforeImportRequest
|
||||||
|
}
|
||||||
|
|
||||||
|
func (app *BaseApp) OnCollectionsAfterImportRequest() *hook.Hook[*CollectionsImportEvent] {
|
||||||
|
return app.onCollectionsAfterImportRequest
|
||||||
|
}
|
||||||
|
|
||||||
// -------------------------------------------------------------------
|
// -------------------------------------------------------------------
|
||||||
// Helpers
|
// Helpers
|
||||||
// -------------------------------------------------------------------
|
// -------------------------------------------------------------------
|
||||||
|
@ -216,6 +216,11 @@ type CollectionDeleteEvent struct {
|
|||||||
Collection *models.Collection
|
Collection *models.Collection
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type CollectionsImportEvent struct {
|
||||||
|
HttpContext echo.Context
|
||||||
|
Collections []*models.Collection
|
||||||
|
}
|
||||||
|
|
||||||
// -------------------------------------------------------------------
|
// -------------------------------------------------------------------
|
||||||
// File API events data
|
// File API events data
|
||||||
// -------------------------------------------------------------------
|
// -------------------------------------------------------------------
|
||||||
|
@ -128,11 +128,11 @@ func (dao *Dao) Delete(m models.Model) error {
|
|||||||
|
|
||||||
// Save upserts (update or create if primary key is not set) the provided model.
|
// Save upserts (update or create if primary key is not set) the provided model.
|
||||||
func (dao *Dao) Save(m models.Model) error {
|
func (dao *Dao) Save(m models.Model) error {
|
||||||
if !m.IsNew() {
|
if m.IsNew() {
|
||||||
return dao.update(m)
|
return dao.create(m)
|
||||||
}
|
}
|
||||||
|
|
||||||
return dao.create(m)
|
return dao.update(m)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (dao *Dao) update(m models.Model) error {
|
func (dao *Dao) update(m models.Model) error {
|
||||||
|
@ -158,6 +158,33 @@ func TestDaoSaveCreate(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestDaoSaveWithInsertId(t *testing.T) {
|
||||||
|
testApp, _ := tests.NewTestApp()
|
||||||
|
defer testApp.Cleanup()
|
||||||
|
|
||||||
|
model := &models.Admin{}
|
||||||
|
model.Id = "test"
|
||||||
|
model.Email = "test_new@example.com"
|
||||||
|
model.MarkAsNew()
|
||||||
|
if err := testApp.Dao().Save(model); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// refresh
|
||||||
|
model, _ = testApp.Dao().FindAdminById("test")
|
||||||
|
|
||||||
|
if model == nil {
|
||||||
|
t.Fatal("Failed to find admin with id 'test'")
|
||||||
|
}
|
||||||
|
|
||||||
|
expectedHooks := []string{"OnModelBeforeCreate", "OnModelAfterCreate"}
|
||||||
|
for _, h := range expectedHooks {
|
||||||
|
if v, ok := testApp.EventCalls[h]; !ok || v != 1 {
|
||||||
|
t.Fatalf("Expected event %s to be called exactly one time, got %d", h, v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestDaoSaveUpdate(t *testing.T) {
|
func TestDaoSaveUpdate(t *testing.T) {
|
||||||
testApp, _ := tests.NewTestApp()
|
testApp, _ := tests.NewTestApp()
|
||||||
defer testApp.Cleanup()
|
defer testApp.Cleanup()
|
||||||
@ -184,6 +211,61 @@ func TestDaoSaveUpdate(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type dummyColumnValueMapper struct {
|
||||||
|
models.Admin
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *dummyColumnValueMapper) ColumnValueMap() map[string]any {
|
||||||
|
return map[string]any{
|
||||||
|
"email": a.Email,
|
||||||
|
"passwordHash": a.PasswordHash,
|
||||||
|
"tokenKey": "custom_token_key",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDaoSaveWithColumnValueMapper(t *testing.T) {
|
||||||
|
testApp, _ := tests.NewTestApp()
|
||||||
|
defer testApp.Cleanup()
|
||||||
|
|
||||||
|
model := &dummyColumnValueMapper{}
|
||||||
|
model.Id = "test_mapped_id" // explicitly set an id
|
||||||
|
model.Email = "test_mapped_create@example.com"
|
||||||
|
model.TokenKey = "test_unmapped_token_key" // not used in the map
|
||||||
|
model.SetPassword("123456")
|
||||||
|
model.MarkAsNew()
|
||||||
|
if err := testApp.Dao().Save(model); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
createdModel, _ := testApp.Dao().FindAdminById("test_mapped_id")
|
||||||
|
if createdModel == nil {
|
||||||
|
t.Fatal("[create] Failed to find model with id 'test_mapped_id'")
|
||||||
|
}
|
||||||
|
if createdModel.Email != model.Email {
|
||||||
|
t.Fatalf("Expected model with email %q, got %q", model.Email, createdModel.Email)
|
||||||
|
}
|
||||||
|
if createdModel.TokenKey != "custom_token_key" {
|
||||||
|
t.Fatalf("Expected model with tokenKey %q, got %q", "custom_token_key", createdModel.TokenKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
model.Email = "test_mapped_update@example.com"
|
||||||
|
model.Avatar = 9 // not mapped and expect to be ignored
|
||||||
|
if err := testApp.Dao().Save(model); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
updatedModel, _ := testApp.Dao().FindAdminById("test_mapped_id")
|
||||||
|
if updatedModel == nil {
|
||||||
|
t.Fatal("[update] Failed to find model with id 'test_mapped_id'")
|
||||||
|
}
|
||||||
|
if updatedModel.Email != model.Email {
|
||||||
|
t.Fatalf("Expected model with email %q, got %q", model.Email, createdModel.Email)
|
||||||
|
}
|
||||||
|
if updatedModel.Avatar != 0 {
|
||||||
|
t.Fatalf("Expected model avatar 0, got %v", updatedModel.Avatar)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestDaoDelete(t *testing.T) {
|
func TestDaoDelete(t *testing.T) {
|
||||||
testApp, _ := tests.NewTestApp()
|
testApp, _ := tests.NewTestApp()
|
||||||
defer testApp.Cleanup()
|
defer testApp.Cleanup()
|
||||||
|
@ -70,11 +70,18 @@ func (form *CollectionsImport) Validate() error {
|
|||||||
//
|
//
|
||||||
// All operations are wrapped in a single transaction that are
|
// All operations are wrapped in a single transaction that are
|
||||||
// rollbacked on the first encountered error.
|
// rollbacked on the first encountered error.
|
||||||
func (form *CollectionsImport) Submit() error {
|
//
|
||||||
|
// You can optionally provide a list of InterceptorFunc to further
|
||||||
|
// modify the form behavior before persisting it.
|
||||||
|
func (form *CollectionsImport) Submit(interceptors ...InterceptorFunc) error {
|
||||||
if err := form.Validate(); err != nil {
|
if err := form.Validate(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return runInterceptors(form.submit, interceptors...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (form *CollectionsImport) submit() error {
|
||||||
return form.config.TxDao.RunInTransaction(func(txDao *daos.Dao) error {
|
return form.config.TxDao.RunInTransaction(func(txDao *daos.Dao) error {
|
||||||
oldCollections := []*models.Collection{}
|
oldCollections := []*models.Collection{}
|
||||||
if err := txDao.CollectionQuery().All(&oldCollections); err != nil {
|
if err := txDao.CollectionQuery().All(&oldCollections); err != nil {
|
||||||
|
@ -34,25 +34,55 @@ func TestBaseModelHasId(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestBaseModelGetId(t *testing.T) {
|
func TestBaseModelId(t *testing.T) {
|
||||||
m0 := models.BaseModel{}
|
m := models.BaseModel{}
|
||||||
if m0.GetId() != "" {
|
|
||||||
t.Fatalf("Expected zero id value, got %v", m0.GetId())
|
if m.GetId() != "" {
|
||||||
|
t.Fatalf("Expected empty id value, got %v", m.GetId())
|
||||||
}
|
}
|
||||||
|
|
||||||
id := "abc"
|
m.SetId("test")
|
||||||
m1 := models.BaseModel{Id: id}
|
|
||||||
if m1.GetId() != id {
|
if m.GetId() != "test" {
|
||||||
t.Fatalf("Expected id %v, got %v", id, m1.GetId())
|
t.Fatalf("Expected %q id, got %v", "test", m.GetId())
|
||||||
|
}
|
||||||
|
|
||||||
|
m.RefreshId()
|
||||||
|
|
||||||
|
if len(m.GetId()) != 15 {
|
||||||
|
t.Fatalf("Expected 15 chars id, got %v", m.GetId())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestBaseModelRefreshId(t *testing.T) {
|
func TestBaseModelIsNew(t *testing.T) {
|
||||||
m := models.BaseModel{}
|
m0 := models.BaseModel{}
|
||||||
m.RefreshId()
|
m1 := models.BaseModel{Id: ""}
|
||||||
|
m2 := models.BaseModel{Id: "test"}
|
||||||
|
m3 := models.BaseModel{}
|
||||||
|
m3.MarkAsNew()
|
||||||
|
m4 := models.BaseModel{Id: "test"}
|
||||||
|
m4.MarkAsNew()
|
||||||
|
m5 := models.BaseModel{Id: "test"}
|
||||||
|
m5.MarkAsNew()
|
||||||
|
m5.UnmarkAsNew()
|
||||||
|
|
||||||
if m.GetId() == "" {
|
scenarios := []struct {
|
||||||
t.Fatalf("Expected nonempty id value, got %v", m.GetId())
|
model models.BaseModel
|
||||||
|
expected bool
|
||||||
|
}{
|
||||||
|
{m0, true},
|
||||||
|
{m1, true},
|
||||||
|
{m2, false},
|
||||||
|
{m3, true},
|
||||||
|
{m4, true},
|
||||||
|
{m5, false},
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, s := range scenarios {
|
||||||
|
result := s.model.IsNew()
|
||||||
|
if result != s.expected {
|
||||||
|
t.Errorf("(%d) Expected IsNew %v, got %v", i, s.expected, result)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
10
tests/app.go
10
tests/app.go
@ -313,6 +313,16 @@ func NewTestApp() (*TestApp, error) {
|
|||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
|
|
||||||
|
t.OnCollectionsBeforeImportRequest().Add(func(e *core.CollectionsImportEvent) error {
|
||||||
|
t.EventCalls["OnCollectionsBeforeImportRequest"]++
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
|
t.OnCollectionsAfterImportRequest().Add(func(e *core.CollectionsImportEvent) error {
|
||||||
|
t.EventCalls["OnCollectionsAfterImportRequest"]++
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
t.OnAdminsListRequest().Add(func(e *core.AdminsListEvent) error {
|
t.OnAdminsListRequest().Add(func(e *core.AdminsListEvent) error {
|
||||||
t.EventCalls["OnAdminsListRequest"]++
|
t.EventCalls["OnAdminsListRequest"]++
|
||||||
return nil
|
return nil
|
||||||
|
@ -1 +1 @@
|
|||||||
PB_BACKEND_URL = http://localhost:8090
|
PB_BACKEND_URL = http://127.0.0.1:8090
|
||||||
|
2
ui/dist/index.html
vendored
2
ui/dist/index.html
vendored
@ -5,7 +5,7 @@
|
|||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<meta
|
<meta
|
||||||
http-equiv="Content-Security-Policy"
|
http-equiv="Content-Security-Policy"
|
||||||
content="default-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' http://localhost:* data:; connect-src 'self' http://localhost:*; script-src 'self' 'sha256-GRUzBA7PzKYug7pqxv5rJaec5bwDCw1Vo6/IXwvD3Tc='"
|
content="default-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' http://127.0.0.1:* data:; connect-src 'self' http://127.0.0.1:*; script-src 'self' 'sha256-GRUzBA7PzKYug7pqxv5rJaec5bwDCw1Vo6/IXwvD3Tc='"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<title>PocketBase</title>
|
<title>PocketBase</title>
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<meta
|
<meta
|
||||||
http-equiv="Content-Security-Policy"
|
http-equiv="Content-Security-Policy"
|
||||||
content="default-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' http://localhost:* data:; connect-src 'self' http://localhost:*; script-src 'self' 'sha256-GRUzBA7PzKYug7pqxv5rJaec5bwDCw1Vo6/IXwvD3Tc='"
|
content="default-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' http://127.0.0.1:* data:; connect-src 'self' http://127.0.0.1:*; script-src 'self' 'sha256-GRUzBA7PzKYug7pqxv5rJaec5bwDCw1Vo6/IXwvD3Tc='"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<title>PocketBase</title>
|
<title>PocketBase</title>
|
||||||
|
28
ui/package-lock.json
generated
28
ui/package-lock.json
generated
@ -938,9 +938,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/regexparam": {
|
"node_modules/regexparam": {
|
||||||
"version": "2.0.0",
|
"version": "2.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/regexparam/-/regexparam-2.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/regexparam/-/regexparam-2.0.1.tgz",
|
||||||
"integrity": "sha512-gJKwd2MVPWHAIFLsaYDZfyKzHNS4o7E/v8YmNf44vmeV2e4YfVoDToTOKTvE7ab68cRJ++kLuEXJBaEeJVt5ow==",
|
"integrity": "sha512-zRgSaYemnNYxUv+/5SeoHI0eJIgTL/A2pUtXUPLHQxUldagouJ9p+K6IbIZ/JiQuCEv2E2B1O11SjVQy3aMCkw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=8"
|
"node": ">=8"
|
||||||
@ -1062,12 +1062,12 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/svelte-spa-router": {
|
"node_modules/svelte-spa-router": {
|
||||||
"version": "3.2.0",
|
"version": "3.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/svelte-spa-router/-/svelte-spa-router-3.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/svelte-spa-router/-/svelte-spa-router-3.3.0.tgz",
|
||||||
"integrity": "sha512-igemo5Vs82TGBBw+DjWt6qKameXYzNs6aDXcTxou5XbEvOjiRcAM6MLkdVRCatn6u8r42dE99bt/br7T4qe/AQ==",
|
"integrity": "sha512-cwRNe7cxD43sCvSfEeaKiNZg3FCizGxeMcf7CPiWRP3jKXjEma3vxyyuDtPOam6nWbVxl9TNM3hlE/i87ZlqcQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"regexparam": "2.0.0"
|
"regexparam": "2.0.1"
|
||||||
},
|
},
|
||||||
"funding": {
|
"funding": {
|
||||||
"url": "https://github.com/sponsors/ItalyPaleAle"
|
"url": "https://github.com/sponsors/ItalyPaleAle"
|
||||||
@ -1714,9 +1714,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"regexparam": {
|
"regexparam": {
|
||||||
"version": "2.0.0",
|
"version": "2.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/regexparam/-/regexparam-2.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/regexparam/-/regexparam-2.0.1.tgz",
|
||||||
"integrity": "sha512-gJKwd2MVPWHAIFLsaYDZfyKzHNS4o7E/v8YmNf44vmeV2e4YfVoDToTOKTvE7ab68cRJ++kLuEXJBaEeJVt5ow==",
|
"integrity": "sha512-zRgSaYemnNYxUv+/5SeoHI0eJIgTL/A2pUtXUPLHQxUldagouJ9p+K6IbIZ/JiQuCEv2E2B1O11SjVQy3aMCkw==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"resolve": {
|
"resolve": {
|
||||||
@ -1797,12 +1797,12 @@
|
|||||||
"requires": {}
|
"requires": {}
|
||||||
},
|
},
|
||||||
"svelte-spa-router": {
|
"svelte-spa-router": {
|
||||||
"version": "3.2.0",
|
"version": "3.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/svelte-spa-router/-/svelte-spa-router-3.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/svelte-spa-router/-/svelte-spa-router-3.3.0.tgz",
|
||||||
"integrity": "sha512-igemo5Vs82TGBBw+DjWt6qKameXYzNs6aDXcTxou5XbEvOjiRcAM6MLkdVRCatn6u8r42dE99bt/br7T4qe/AQ==",
|
"integrity": "sha512-cwRNe7cxD43sCvSfEeaKiNZg3FCizGxeMcf7CPiWRP3jKXjEma3vxyyuDtPOam6nWbVxl9TNM3hlE/i87ZlqcQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"regexparam": "2.0.0"
|
"regexparam": "2.0.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"to-regex-range": {
|
"to-regex-range": {
|
||||||
|
@ -1,5 +1,8 @@
|
|||||||
// https://github.com/google/diff-match-patch
|
/**
|
||||||
// https://github.com/google/diff-match-patch/blob/master/LICENSE
|
* diff-match-patch
|
||||||
|
* https://github.com/google/diff-match-patch
|
||||||
|
* https://github.com/google/diff-match-patch/blob/master/LICENSE
|
||||||
|
*/
|
||||||
var diff_match_patch=function(){this.Diff_Timeout=1;this.Diff_EditCost=4;this.Match_Threshold=.5;this.Match_Distance=1E3;this.Patch_DeleteThreshold=.5;this.Patch_Margin=4;this.Match_MaxBits=32},DIFF_DELETE=-1,DIFF_INSERT=1,DIFF_EQUAL=0;diff_match_patch.Diff=function(a,b){this[0]=a;this[1]=b};diff_match_patch.Diff.prototype.length=2;diff_match_patch.Diff.prototype.toString=function(){return this[0]+","+this[1]};
|
var diff_match_patch=function(){this.Diff_Timeout=1;this.Diff_EditCost=4;this.Match_Threshold=.5;this.Match_Distance=1E3;this.Patch_DeleteThreshold=.5;this.Patch_Margin=4;this.Match_MaxBits=32},DIFF_DELETE=-1,DIFF_INSERT=1,DIFF_EQUAL=0;diff_match_patch.Diff=function(a,b){this[0]=a;this[1]=b};diff_match_patch.Diff.prototype.length=2;diff_match_patch.Diff.prototype.toString=function(){return this[0]+","+this[1]};
|
||||||
diff_match_patch.prototype.diff_main=function(a,b,c,d){"undefined"==typeof d&&(d=0>=this.Diff_Timeout?Number.MAX_VALUE:(new Date).getTime()+1E3*this.Diff_Timeout);if(null==a||null==b)throw Error("Null input. (diff_main)");if(a==b)return a?[new diff_match_patch.Diff(DIFF_EQUAL,a)]:[];"undefined"==typeof c&&(c=!0);var e=c,f=this.diff_commonPrefix(a,b);c=a.substring(0,f);a=a.substring(f);b=b.substring(f);f=this.diff_commonSuffix(a,b);var g=a.substring(a.length-f);a=a.substring(0,a.length-f);b=b.substring(0,
|
diff_match_patch.prototype.diff_main=function(a,b,c,d){"undefined"==typeof d&&(d=0>=this.Diff_Timeout?Number.MAX_VALUE:(new Date).getTime()+1E3*this.Diff_Timeout);if(null==a||null==b)throw Error("Null input. (diff_main)");if(a==b)return a?[new diff_match_patch.Diff(DIFF_EQUAL,a)]:[];"undefined"==typeof c&&(c=!0);var e=c,f=this.diff_commonPrefix(a,b);c=a.substring(0,f);a=a.substring(f);b=b.substring(f);f=this.diff_commonSuffix(a,b);var g=a.substring(a.length-f);a=a.substring(0,a.length-f);b=b.substring(0,
|
||||||
b.length-f);a=this.diff_compute_(a,b,e,d);c&&a.unshift(new diff_match_patch.Diff(DIFF_EQUAL,c));g&&a.push(new diff_match_patch.Diff(DIFF_EQUAL,g));this.diff_cleanupMerge(a);return a};
|
b.length-f);a=this.diff_compute_(a,b,e,d);c&&a.unshift(new diff_match_patch.Diff(DIFF_EQUAL,c));g&&a.push(new diff_match_patch.Diff(DIFF_EQUAL,g));this.diff_cleanupMerge(a);return a};
|
||||||
|
@ -107,6 +107,22 @@
|
|||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<div class="inline-flex">
|
||||||
|
<span class="label label-warning">Optional</span>
|
||||||
|
<span>id</span>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<span class="label">String</span>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<strong>15 characters string</strong> to store as record ID.
|
||||||
|
<br />
|
||||||
|
If not set, it will be auto generated.
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
{#each collection?.schema as field (field.name)}
|
{#each collection?.schema as field (field.name)}
|
||||||
<tr>
|
<tr>
|
||||||
<td>
|
<td>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user